Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

textInput pass 3 - adding some styling for background, fix cursor \n issue, and some more cleanup #578

Merged
merged 29 commits into from
Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
919cd66
start
hanbollar Apr 10, 2024
2bed989
move the one use set text color out of utils
hanbollar Apr 10, 2024
c9e57e1
fix
hanbollar Apr 10, 2024
a6d417a
temp save
hanbollar Apr 10, 2024
33142b1
Merge branch 'main' into hb-input-pass3
hanbollar Apr 10, 2024
88cc49c
save
hanbollar Apr 10, 2024
00b784b
fix the focus/blur not existing call
hanbollar Apr 10, 2024
7e7e915
temp save
hanbollar Apr 10, 2024
2a86af0
fixed cursor movement around \n
hanbollar Apr 10, 2024
9f7e060
cleanup && default backcolor
hanbollar Apr 10, 2024
16fb093
start cleanup
hanbollar Apr 10, 2024
fb10049
everything filled out
hanbollar Apr 10, 2024
5a6d27e
prettier-fix
hanbollar Apr 10, 2024
3317e19
temp save - oging to need defaults
hanbollar Apr 10, 2024
7ee79a3
start on the cursor pos
hanbollar Apr 10, 2024
7010f19
temp save
hanbollar Apr 12, 2024
da70a56
Merge branch 'main' into hb-input-pass3
hanbollar Apr 12, 2024
dc524b4
update build
hanbollar Apr 12, 2024
20cd7ff
background style
hanbollar Apr 12, 2024
3169b39
update
hanbollar Apr 12, 2024
2c4a3ac
update the text.html documentation description
hanbollar Apr 12, 2024
75fbf97
more writing
hanbollar Apr 12, 2024
bebf69b
prettier-fix
hanbollar Apr 12, 2024
01056fc
lint-fix
hanbollar Apr 12, 2024
eaddbd3
remove the debug flag
hanbollar Apr 12, 2024
d30e6e1
found some words to swap
hanbollar Apr 13, 2024
9abd6ef
address comments
hanbollar Apr 13, 2024
ca9aec0
it's happy so remove old doc.body.appendChild line
hanbollar Apr 13, 2024
88169e5
fix for <></> to show up in the text
hanbollar Apr 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
![The MRjs logo, an indigo and purple bowtie.](https://docs.mrjs.io/static/mrjs-logo.svg)

An extensible library of Web Components for the spatial web.

[![npm run build](https://github.com/Volumetrics-io/mrjs/actions/workflows/build.yml/badge.svg)](https://github.com/Volumetrics-io/mrjs/actions/workflows/build.yml) [![npm run test](https://github.com/Volumetrics-io/mrjs/actions/workflows/test.yml/badge.svg)](https://github.com/Volumetrics-io/mrjs/actions/workflows/test.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/Volumetrics-io/mrjs/blob/main/LICENSE)
Expand Down
14 changes: 7 additions & 7 deletions dist/mr.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion samples/examples/models.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body >
<mr-app debug="true"> <!--stats="true">-->
<mr-app> <!-- debug="true"> !--stats="true">-->
<mr-panel id="panel" class="layout" data-comp-anchor="type: fixed;">
<mr-div id="navbar">
<mr-a href="https://mrjs.io" class="mrjs">
Expand Down
8 changes: 6 additions & 2 deletions samples/examples/text-style.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
@font-face {
font-family: 'Roboto';
src: url('/assets/fonts/Roboto-Regular.ttf') format('truetype');
src: url('/examples-assets/fonts/Roboto-Regular.ttf') format('truetype');
font-weight: 100 900;
}

@font-face {
font-family: 'Bricolage';
src: url('/assets/fonts/BricolageGrotesque.ttf') format('truetype');
src: url('/examples-assets/fonts/BricolageGrotesque.ttf') format('truetype');
font-weight: 100 900;
}

Expand Down Expand Up @@ -194,6 +194,10 @@ mr-text {
color: rgba(24, 24, 24, 0.75);
}

.text-section {
margin-bottom: 90px;
}

.label-2 {
font-family: var(--font-title);
font-size: 1.5vw;
Expand Down
44 changes: 25 additions & 19 deletions samples/examples/text.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
<html>
<head>
<meta charset="utf-8">
<title>mr.js - models</title>
<title>mr.js - text</title>
<meta name="description" content="mr.js example - text">
<script src="/mr.js"></script>
<link rel="stylesheet" type="text/css" href="models-style.css" />
<link rel="stylesheet" type="text/css" href="text-style.css" />
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body >
<mr-app debug="true">
<mr-app> <!--debug="true" -->
<mr-panel id="panel" class="layout" data-comp-anchor="type: fixed;">
<mr-div id="navbar">
<mr-a href="https://mrjs.io" class="mrjs">
Expand All @@ -29,14 +29,14 @@
</mr-div>

<mr-text class="col-2 title" >
Text - TODOTODO
Text and TextInputs
</mr-text>

<mr-text class="col-2">
Text - todo description
Text runs the internet, hence it being very important when it comes to web-library creation. It includes features such as readable text and input fields for user interaction.
</mr-text>

<mr-text class="col-2 label">
<mr-text class="col-2 subtitle">
Examples
</mr-text>

Expand Down Expand Up @@ -64,34 +64,40 @@
<mr-text class="col-2 label">
mr-textarea
</mr-text>
<mr-text class="col-2">Try typing in the below...</mr-text>
<mr-textarea class="col-2" style="z-index:3;" placeholder="Hi hello I'm a bunch of text. I'm on one line with no breaks \n\nIm on another line now.">
</mr-textarea>

<mr-text class="col-2 label" >
textfield
mr-textfield
</mr-text>
<!-- <mr-textfield class="col-2">
Hi hello I'm a bunch of text. I'm on one line with no breaks
<mr-text class="col-2">Try typing in the below...</mr-text>
<mr-textfield class="col-2">
</mr-textfield>

Im on another line now.
</mr-textfield> -->
<mr-text class="col-2 subtitle" >
Text
</mr-text>

<!-- <mr-text class="col-2 subtitle" >
model
<mr-text class="col-2">
Written pure text is achieved using the text component, `mr-text` with the text written within the tags (<...>words go here<.../>).
</mr-text>

<mr-text class="col-2 subtitle" >
TextArea
</mr-text>

<mr-text class="col-2">
Model loading is achieved using the model component, `mr-model`, it has one attribute:
TextArea is a multi-line input version of `mr-text`, extending from the MRTextInput class. It is achieved using the `mr-textarea` component and follows the usual textarea attributes.
</mr-text>

<mr-text class="label col-2">
src
<mr-text class="col-2 subtitle" >
TextField
</mr-text>

<mr-text class="col-2">
You fill this out as you would any `src` attribute on an <img/> or <video/> html tag.
That is, with the path to the file to be loaded (for example `/assets/foo.glb`)
</mr-text> -->
TextField is a one-line input version of `mr-text`, extending from the MRTextInput class. It is achieved using the `mr-textfield` component and mimics the general 'input' html element.
</mr-text>

</mr-panel>

Expand Down
47 changes: 34 additions & 13 deletions src/core/componentSystems/TextSystem.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { getSelectionRects, preloadFont } from 'troika-three-text';

import { MRSystem } from 'mrjs/core/MRSystem';
import { MRButtonEntity } from 'mrjs/core/entities/MRButtonEntity';
import { MREntity } from 'mrjs/core/MREntity';
import { MRSystem } from 'mrjs/core/MRSystem';
import { MRTextEntity } from 'mrjs/core/entities/MRTextEntity';
import { MRTextInputEntity } from 'mrjs/core/entities/MRTextInputEntity';
import { MRTextFieldEntity } from 'mrjs/core/entities/MRTextFieldEntity';
Expand Down Expand Up @@ -126,6 +126,12 @@ export class TextSystem extends MRSystem {
entity.textObj.maxHeight = entity.height;
entity.textObj.position.setX(-entity.width / 2);
entity.textObj.position.setY(entity.height / 2);
// background positioning and dimensions
// Always want background to be slightly bigger for input field 'niceness', but
// we dont want this to be specifically based on the margin since the user might
// have other purposes for the margin css attribute.
entity.background.scale.x = entity.textObj.scale.x + mrjsUtils.css.pxToThree(30);
entity.background.scale.y = entity.textObj.scale.y + mrjsUtils.css.pxToThree(30);
// cursor positioning and dimensions
entity.cursorStartingPosition.x = entity.textObj.position.x;
entity.cursorStartingPosition.y = entity.textObj.position.y - entity.cursorHeight / 2;
Expand Down Expand Up @@ -155,17 +161,6 @@ export class TextSystem extends MRSystem {
}
};

/**
* @function
* @description The per-frame system update call for all text items including updates for style and cleaning of content for special characters.
* @param {number} deltaTime - given timestep to be used for any feature changes
* @param {object} frame - given frame information to be used for any feature changes
*/
update(deltaTime, frame) {
// For this system, since we have the 'per entity' and 'per scene event' update calls,
// we dont need a main update call here.
}

/**
* @function
* @description Updates the style for the text's information based on compStyle and inputted css elements.
Expand All @@ -186,7 +181,11 @@ export class TextSystem extends MRSystem {
textObj.lineHeight = this.getLineHeight(entity.compStyle.lineHeight, entity);

// Color and opacity
mrjsUtils.color.setTEXTObject3DColor(textObj, entity.compStyle.color, entity.compStyle.opacity ?? 1);
// TODO - swap this to use the mrjsUtils.color.setObject3DColor function in future.
// For now since that creates a weird affect for styling (white edges), leaving as the
// current implementation. This probably just means there's a default style css thing
// we need to change before we swap.
this.setTextObject3DColor(textObj, entity.compStyle.color);

// Whitespace and Wrapping
textObj.whiteSpace = entity.compStyle.whiteSpace ?? textObj.whiteSpace;
Expand Down Expand Up @@ -324,4 +323,26 @@ export class TextSystem extends MRSystem {

return obj;
}

/**
* @function
* @description Sets the text object3D color.
* @param {object} object3D - the threejs object representation of the troika textt to be colored
* @param {string} color - the string representation of the color in rgba, hex, or name ('red') form
* @param {string} default_color - fallback color used if the system does not understand the color parameter. Defaults to black.
*/
setTextObject3DColor = function (object3D, color, default_color = '#000') {
if (color.includes('rgba')) {
const rgba = color
.substring(5, color.length - 1)
.split(',')
.map((part) => parseFloat(part.trim()));
object3D.material.color.setStyle(`rgb(${rgba[0]}, ${rgba[1]}, ${rgba[2]})`);

object3D.material.opacity = rgba[3];
} else {
object3D.material.color.setStyle(color ?? '#000');
}
object3D.material.needsUpdate = true;
};
}
79 changes: 33 additions & 46 deletions src/core/entities/MRTextAreaEntity.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,56 +12,64 @@ import { MRTextInputEntity } from 'mrjs/core/entities/MRTextInputEntity';
export class MRTextAreaEntity extends MRTextInputEntity {
/**
* @class
* @description Constructor for the MRTextInputEntity entity component.
*/
constructor() {
super();
// Define additional properties for handling multiline text and scrolling
this.lineHeight = 1.2; // Default line height, can be adjusted as needed
this.scrollOffset = 0; // The vertical scroll position
this.maxVisibleLines = 10; // Maximum number of lines visible without scrolling
this.object3D.name = 'textArea';
}

/**
*
* @function
* @description Called by connected to make sure the hiddenInput dom element is created as expected.
*/
createHiddenInputElement() {
// setup
const inputElement = document.createElement('textarea');

// style
inputElement.style.position = 'absolute';
inputElement.style.left = '-9999px'; // Position off-screen
inputElement.style.height = '1px';
inputElement.style.width = '1px';
inputElement.style.overflow = 'hidden';
document.body.appendChild(inputElement); // Ensure it's part of the DOM for event capturing

// Ensure it's part of the DOM for event capturing
this.shadowRoot.appendChild(inputElement);
this.hiddenInput = inputElement;
}

/**
*
* @function
* @description Called by connected after createHiddenInputElement to fill
* it in with the user's given attribute information.
*/
fillInHiddenInputElementWithUserData() {
// name: The name associated with the <textarea> for form submission and backend processing.
this.hiddenInput.name = this.getAttribute('name') ?? undefined;
this.hiddenInput.setAttribute('name', this.getAttribute('name') ?? undefined);
// rows and cols: These attributes control the size of the <textarea> in terms of the number of text rows and columns visible.
this.hiddenInput.rows = this.getAttribute('rows') ?? undefined;
this.hiddenInput.setAttribute('rows', this.getAttribute('rows') ?? undefined);
this.hiddenInput.setAttribute('cols', this.getAttribute('cols') ?? undefined);
// placeholder: Provides a hint to the user about what they should type into the <textarea>.
this.hiddenInput.placeholder = this.getAttribute('placeholder') ?? undefined;
this.hiddenInput.setAttribute('placeholder', this.getAttribute('placeholder') ?? '');
// readonly: Makes the <textarea> uneditable, allowing the text to be only read, not modified.
this.hiddenInput.readonly = this.getAttribute('readonly') ?? undefined;
this.hiddenInput.setAttribute('readonly', this.getAttribute('readonly') ?? false);
// disabled: Disables the text area so it cannot be interacted with or submitted.
this.hiddenInput.disabled = this.getAttribute('disabled') ?? undefined;
this.hiddenInput.setAttribute('disabled', this.getAttribute('disabled') ?? false);
// maxlength: Specifies the maximum number of characters that the user can enter.
this.hiddenInput.maxlength = this.getAttribute('maxlength') ?? undefined;
this.hiddenInput.setAttribute('maxlength', this.getAttribute('maxlength') ?? undefined);
// wrap: Controls how text is wrapped in the textarea, with values like soft and hard affecting form submission.
this.hiddenInput.wrap = this.getAttribute('wrap') ?? undefined;
this.hiddenInput.setAttribute('wrap', this.getAttribute('wrap') ?? undefined);
// overflowwrap : Controls how wrap breaks, at whitespace characters or in the middle of words.
this.hiddenInput.overflowWrap = this.getAttribute('overflowWrap') ?? undefined;
this.hiddenInput.setAttribute('overflowWrap', this.getAttribute('overflowWrap') ?? undefined);
// whitespace : Controls if text wraps with the overflowWrap feature or not.
this.hiddenInput.whiteSpace = this.getAttribute('whitespace') ?? undefined;
this.hiddenInput.setAttribute('whitespace', this.getAttribute('whitespace') ?? undefined);
}

/**
*
* @function
* @description Used on event trigger to update the textObj visual based on
* the hiddenInput DOM element.
*/
updateTextDisplay() {
// XXX - add scrolling logic in here for areas where text is greater than
Expand All @@ -71,9 +79,8 @@ export class MRTextAreaEntity extends MRTextInputEntity {
}

/**
* Handles keydown events for scrolling and cursor navigation. Note
* that this is different than an input event which for our purposes,
* handles the non-navigation key-presses.
* @function
* @description Called by the keydown event trigger. Handles the arrow key movements.
* @param {event} event - the keydown event
*/
handleKeydown(event) {
Expand All @@ -82,30 +89,12 @@ export class MRTextAreaEntity extends MRTextInputEntity {
const isUpArrow = keyCode === 38;
const isRightArrow = keyCode === 39;
const isDownArrow = keyCode === 40;
const isBackspace = keyCode === 8;
const isDelete = keyCode === 46;
const isEnter = keyCode === 13;

if (!(isLeftArrow || isUpArrow || isRightArrow || isDownArrow || isBackspace || isDelete || isEnter)) {
// not special event, then handle as normal input
this.updateTextDisplay();
}

let fromCursorMove = !(isBackspace || isDelete || isEnter);

// Handle Special Keys, then Up/Down, then Left/Right
// as some may trigger the others being required
// based on assumed implementations for them for the
// textarea dom element.

if (isBackspace || isDelete) {
this.updateTextDisplay();
this.updateCursorPosition(fromCursorMove);
return;
} else if (isEnter) {
this.updateCursorPosition(fromCursorMove, true);
return;
}
// We need to handle the up/down arrows in a special way here; otherwise,
// they'll default to the left/right implementation.
//
// And in all cases, we need to update the selction points and the cursor
// position here.

// Some shared variables
const cursorIndex = this.hiddenInput.selectionStart;
Expand Down Expand Up @@ -156,9 +145,7 @@ export class MRTextAreaEntity extends MRTextInputEntity {
this.hiddenInput.selectionEnd = this.hiddenInput.selectionStart;

// Ensure the cursor position is updated to reflect the current caret position
setTimeout(() => {
this.updateCursorPosition(fromCursorMove);
}, 0);
this.updateCursorPosition(true);
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/core/entities/MRTextEntity.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,10 @@ export class MRTextEntity extends MRDivEntity {
}

/**
*
* @param {object} textObj - the textobj
* @function
* @description Helper method for debugging the textObj visual information
* @param {object} textObj - the textobj threejs renderable visual. Handled by troika.
* If no object is passed, defers to the textObj within this entity.
*/
printCurrentTextDebugInfo(textObj) {
if (!textObj) {
Expand Down
Loading