feat(all): 迁移扩展相关功能

This commit is contained in:
plum
2026-04-08 15:34:43 +08:00
parent ad2dd979eb
commit d7c0dba569
278 changed files with 25207 additions and 62 deletions
@@ -0,0 +1,71 @@
//JSDoc related imports
/* eslint-disable no-unused-vars */
import MeshUIBaseElement from './../../core/elements/MeshUIBaseElement';
/* eslint-enable no-unused-vars */
/**
* Job:
* - recording components required updates
* - trigger those updates when 'update' is called
*
* This module is a bit special. It is, with FontLibrary, one of the only modules in the 'component'
* directory not to be used in component composition (Object.assign).
*
* When MeshUIComponent is instanciated, it calls UpdateManager.register().
*
* Then when MeshUIComponent receives new attributes, it doesn't update the component right away.
* Instead, it calls UpdateManager.requestUpdate(), so that the component is updated when the user
* decides it (usually in the render loop).
*
* This is best for performance, because when a UI is created, thousands of componants can
* potentially be instantiated. If they called updates function on their ancestors right away,
* a given component could be updated thousands of times in one frame, which is very ineficient.
*
* Instead, redundant update request are moot, the component will update once when the use calls
* update() in their render loop.
*/
export default class UpdateManager {
static register( component ) {
if ( !this.elements.includes( component ) ) {
this.elements.push( component );
}
}
static remove( component ) {
const index = this.elements.indexOf( component );
if ( index !== -1 ) {
this.elements.splice( index, 1 );
}
}
static update() {
for ( const UIElement of this.elements ) {
UIElement.update();
UIElement.process(); // Natural process
UIElement.process(); // Actual process (optional) - For auto size and stretch
UIElement.render();
}
}
}
/**
* @internal
* @type {Array.<MeshUIBaseElement>}
*/
UpdateManager.elements = [];
@@ -0,0 +1,68 @@
/** List the default values of the lib components */
const _values = {
fog: false,
fontFamily: null,
fontSize: 0.05,
fontKerning: 'auto',
fontStyle: 'normal',
fontWeight : 'normal',
offset: 0.005,
lineHeight: 1.2,
lineBreak: '- ,.:?!\n',// added '\n' to also acts as friendly breaks when white-space:normal
whiteSpace: 'pre-line',
flexDirection : 'column',
justifyContent : 'start',
alignItems : 'start',
backgroundImage: null,
textAlign : 'left',
boxSizing: 'content-box',
position: 'static',
color: 0xffffff,
fontColor: 0xffffff,
fontOpacity: 1,
opacity: 1,
fontPXRange: 4,
fontSupersampling: true,
fontSmooth: 'antialiased',
borderRadius: 0,
borderWidth: 0,
borderColor: 'black',
borderOpacity: 1,
backgroundSize: "cover",
backgroundColor: 0x000000,
backgroundOpacity: 0,
overflow: 'visible',
letterSpacing: 0,
invertAlpha : false,
segments: 1
};
/**
* @param {import('./../core/elements/MeshUIBaseElement').Options} overrideProperties
*/
export const set = function ( overrideProperties ) {
for ( const property in overrideProperties ) {
_values[property] = overrideProperties[property];
}
}
/**
*
* @param {string} property
* @return {any}
*/
export const get = function ( property ) {
if( !Object.prototype.hasOwnProperty.call( _values, property) ) {
console.warn( `ThreeMeshUI::DefaultValues is trying to retrieve non-existing property '${property}'`);
}
return _values[property];
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,262 @@
/**
* This is the abstract/base class / interface of any inline
* Inline can be positioned according to text rules
*/
export default class Inline {
constructor() {
/** @protected */ this._offsetX = 0;
/** @protected */ this._offsetY = 0;
/** @protected */ this._lineBreak = null;
/** @protected */ this._kerning = 0;
/** @protected */ this._fontFactor = 1;
/** @protected */ this._fontSize = 0;
/** @protected */ this._cumulativeWidth = 0;
/** @protected */ this._paddingLeft = 0;
/** @protected */ this._paddingRight = 0;
/** @protected */ this._marginLeft = 0;
/** @protected */ this._marginRight = 0;
}
/**
* @returns {void}
*/
resetOffsets() {
this._offsetX = this._offsetY = 0;
this._cumulativeWidth = 0;
}
/**
* The horizontal distance this inline fills
* @returns {number}
*/
get xadvance() { return 0 }
/**
* The offset x of this inline in a line
* @returns {number}
*/
get xoffset() { return 0 }
/**
* The offset y of this inline in a line
* @returns {number}
*/
get yoffset() { return 0 }
/**
*
* @returns {number}
*/
get width() { return 0 }
/**
*
* @returns {number}
*/
get height() { return 0 }
/**
*
* @param {string|null} value
*/
set lineBreak( value ) {
this._lineBreak = value;
}
/**
*
* @returns {string|null}
*/
get lineBreak() { return this._lineBreak; }
/**
*
* @returns {number}
*/
get anchor() { return 0 }
/**
*
* @returns {number}
*/
get kerning() { return this._kerning * this._fontFactor; }
/**
*
* @param {number} value
*/
set kerning( value ) {
this._kerning = value;
}
/**
*
* @returns {number}
*/
get fontSize() { return this._fontSize }
/**
*
* @param {number} value
*/
set fontSize( value ) {
this._fontSize = value;
}
/**
*
* @returns {number}
*/
get lineHeight() { return 0 }
/**
*
* @returns {number}
*/
get offsetX() { return this._offsetX; }
/**
*
* @param value
*/
set offsetX( value ){
this._offsetX = value;
}
/**
*
* @returns {number}
*/
get offsetY() { return this._offsetY; }
/**
*
* @param {number} value
*/
set offsetY( value ){
this._offsetY = value;
}
/**
*
* @return {number}
*/
get cumulativeWidth() { return this._cumulativeWidth; }
/**
*
* @param {number} value
*/
set cumulativeWidth( value ) {
this._cumulativeWidth = value;
}
/**
*
* @return {number}
*/
get marginLeft() { return this._marginLeft; }
/**
*
* @param {number} value
*/
set marginLeft( value ) {
this._marginLeft = value;
}
/**
*
* @return {number}
*/
get marginRight() { return this._marginRight; }
/**
*
* @param {number} value
*/
set marginRight( value ) {
this._marginRight = value;
}
/**
*
* @return {number}
*/
get paddingLeft() { return this._paddingLeft; }
/**
*
* @param {number} value
*/
set paddingLeft( value ) {
this._paddingLeft = value;
}
/**
*
* @return {number}
*/
get paddingRight() { return this._paddingRight; }
/**
*
* @param {number} value
*/
set paddingRight( value ) {
this._paddingRight = value;
}
/**
*
* @returns {number}
*/
get lineBase() { return 0 }
/**
*
* @param {number} value
*/
set fontFactor( value ){
this._fontFactor = value;
}
/**
*
* @returns {number}
*/
get fontFactor() { return this._fontFactor }
}
@@ -0,0 +1,45 @@
//JSDoc related imports
/* eslint-disable no-unused-vars */
import Inline from './Inline';
/* eslint-enable no-unused-vars */
/**
* Line represents an horizontal combination of positioned inlines with additional properties
*/
export default class Line extends Array {
/**
*
* @param {Inline[]} items
*/
constructor(...items) {
super(...items);
/**
* The width of this line
* @type {number}
*/
this.width = 0;
/**
* The maximum lineBase of this line of inlines
* @type {number}
*/
this.lineBase = 0;
/**
* The maximum lineHeight of this line of inlines
* @type {number}
*/
this.lineHeight = 0;
/**
* The vertical position of this line
* @type {number}
*/
this.y = 0;
}
}
@@ -0,0 +1,32 @@
//JSDoc related imports
/* eslint-disable no-unused-vars */
import Line from './Line';
/* eslint-enable no-unused-vars */
/**
* Lines represents a vertical succession of Line
*/
export default class Lines extends Array {
/**
*
* @param {Line} items
*/
constructor(...items) {
super(...items);
/**
* The maximum width of Line items
* @type {number}
*/
this.width = 0;
/**
* The addition of height of any Line
* @type {number}
*/
this.height = 0;
}
}
@@ -0,0 +1,251 @@
import BaseProperty from './BaseProperty';
/**
* Autosize are only trigger when natural size changed
*/
export default class AutoSizePropertyBox extends BaseProperty {
constructor() {
super( 'autosize' );
this._needsProcess = true;
}
process( element ) {
// if( parent ) return;
// has auto size get the height from children
if ( element._width._auto ) _processAutoWidth( element );
if ( element._height._auto ) _processAutoHeight( element );
const stretch = element._alignItems._value === 'stretch';
const stretchChildrenWidth = stretch && element._flexDirection._value.indexOf( 'column' ) !== -1;
const stretchChildrenHeight = stretch && !stretchChildrenWidth;
for ( const box of element._children._boxes ) {
if ( ( box._width._auto && stretchChildrenWidth ) || box._width._relative ) {
box._bounds.setReferenceWidth( box, element._bounds._innerWidth );
}
if ( ( box._height._auto && stretchChildrenHeight ) || box._height._relative ) {
box._bounds.setReferenceHeight( box, element._bounds._innerHeight );
}
}
// // justify stretch - Not that easy
// const stretchD = element._justifyContent._value === 'stretch';
// const stretchChildrenWidthD = stretchD && element._flexDirection._value.indexOf( 'row' ) !== -1;
// const stretchChildrenHeightD = stretchD && !stretchChildrenWidthD;
//
//
// if ( stretchChildrenWidthD ) {
//
// const used = _computeChildrenSideWidth( element );
// const available = element._bounds._innerWidth - used;
// if ( available > 0 ) {
//
// const autoElement = element._children._uis.filter( c => c._width._auto );
// const distributed = available / autoElement.length;
//
// for ( const child of autoElement ) {
//
// const width = child._bounds._offsetWidth + distributed;
// child._bounds.setReferenceWidth( child, width );
//
// }
//
// element._layouter._needsProcess = true;
// element._flexDirection._needsProcess = true;
//
// }
//
// } else if ( stretchChildrenHeightD ) {
//
// const used = _computeChildrenSideHeight( element );
// const available = element._bounds._innerHeight - used;
// if ( available > 0 ) {
//
// const autoElement = element._children._uis.filter( c => c._height._auto );
// const distributed = available / autoElement.length;
//
// for ( const child of autoElement ) {
//
// const height = child._bounds._offsetHeight + distributed;
// child._bounds.setReferenceHeight( child, height );
//
// }
//
// element._layouter._needsProcess = true;
// element._flexDirection._needsProcess = true;
//
// }
//
// }
}
}
function _processAutoWidth( element ) {
// column : retrieve the biggest child width
// row : retrieve the sum of children width
element._bounds.setChildrenWidth( element, _computeAutoWidth( element ) );
}
function _processAutoHeight( element ) {
// column : retrieve the sum of children height
// row : retrieve the biggest child height
element._bounds.setChildrenHeight( element, _computeAutoHeight( element ) );
}
/**
* Retrieve the automatic height from children boxes
* @param {MeshUIBaseElement} element
* @return {number}
*/
function _computeAutoHeight( element ) {
switch ( element._flexDirection._value ) {
case 'row' :
case 'row-reverse' :
return _computeHighestChildHeight( element );
case 'column' :
case 'column-reverse' :
return _computeChildrenSideHeight( element );
}
}
/**
* @param {MeshUIBaseElement} element
* @return {number}
*
*/
function _computeAutoWidth( element ) {
switch ( element._flexDirection._value ) {
case 'row' :
case 'row-reverse' :
return _computeChildrenSideWidth( element );
case 'column' :
case 'column-reverse' :
return _computeHighestChildWidth( element );
}
}
/**
* Return the sum of all this component's children width
* @param {MeshUIBaseElement} element
* @return {number}
*/
function _computeChildrenSideWidth( element ) {
let sumWidth = 0;
for ( const box of element._children._boxes ) {
if ( box._position._value !== 'static' ) continue;
const margin = box._margin._value;
const width = box._bounds._offsetWidth + margin.y + margin.w;
sumWidth += width;
}
return sumWidth;
}
/**
* Return the sum of all this component's children width
* @param {MeshUIBaseElement} element
* @return {number}
*/
function _computeChildrenSideHeight( element ) {
let sumHeight = 0;
for ( const box of element._children._boxes ) {
if ( box._position._value !== 'static' ) continue;
const margin = box._margin._value;
const height = box._bounds._offsetHeight + margin.x + margin.z;
sumHeight += height;
}
return sumHeight;
}
/**
* Returns the highest linear dimension among all the children of the passed component
* MARGIN INCLUDED
* @param {MeshUIBaseElement} element
* @return {number}
*/
function _computeHighestChildWidth( element ) {
let maxWidth = 0;
for ( const box of element._children._boxes ) {
if ( box._position._value !== 'static' ) continue;
const margin = box._margin._value;
const width = box._bounds._offsetWidth + margin.y + margin.w;
if ( width > maxWidth ) maxWidth = width;
}
return maxWidth;
}
/**
* Returns the highest linear dimension among all the children of the passed component
* MARGIN INCLUDED
* @param {MeshUIBaseElement} element
* @return {number}
*/
function _computeHighestChildHeight( element ) {
let maxHeight = 0;
for ( const box of element._children._boxes ) {
if ( box._position._value !== 'static' ) continue;
const margin = box._margin._value;
const height = box._bounds._offsetHeight + margin.x + margin.z;
if ( height > maxHeight ) maxHeight = height;
}
return maxHeight;
}
@@ -0,0 +1,41 @@
import BaseProperty from './BaseProperty';
/**
* Autosize are only trigger when natural size changed
*/
export default class AutoSizePropertyText extends BaseProperty {
constructor() {
super( 'autosize' );
}
process( element ) {
if( element._layouter._value && element._layouter._value.length ) {
const lines = element._layouter._value;
// as this is from children offsetWidth, it means parent innerWidth
const padding = element._padding._value;
const border = element._borderWidth._value;
// has auto size get the height from children
if ( element._width._auto ) {
element._bounds.setOffsetWidth( element, lines.width + padding.w + padding.y + border.w + border.y );
}
if ( element._height._auto ) {
element._bounds.setOffsetHeight( element, lines.height + padding.x + padding.z + border.x + border.z );
}
}
}
}
@@ -0,0 +1,219 @@
import * as DefaultValues from '../DefaultValues';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import MeshUIBaseElement from './../../core/elements/MeshUIBaseElement';
/* eslint-enable no-unused-vars */
export default class BaseProperty {
/**
*
* @param {string} propertyId
* @param {any} [value=null]
* @param primitive
*/
constructor( propertyId, value = null, primitive = true ) {
/**
*
* @type {string}
* @internal
*/
this._id = propertyId;
/**
*
* @type {any}
* @internal
*/
this._value = value;
/**
*
* @type {boolean}
* @internal
*/
this._needsUpdate = true;
/**
*
* @type {boolean}
* @internal
*/
this._needsProcess = false;
/**
*
* @type {boolean}
* @internal
*/
this._needsRender = false;
/**
*
* @type {boolean}
* @protected
*/
this._isPrimitive = primitive;
}
/**
*
* @return {string}
*/
get id() { return this._id; }
/**
*
* @return {any}
*/
get value() { return this._value; }
/**
*
* @param {any} value
*/
set value( value ) {
if ( !this.isValid( value ) ) return;
if ( this._value !== value ) {
this._value = value;
this._needsUpdate = true;
}
}
/* eslint-disable no-unused-vars */
/**
*
* @param element
* @param {Object.<string,any>} out
*/
update( element, out ) { /* eslint-enable no-unused-vars */
// the value has been updated from setter
// if there is no additional logic
// then just output it
// => out[this._id] = this._value;
this.output( out );
// ??
//this.computeOutputValue( element );
// if( this._isPrimitive ) this.output( out );
}
/* eslint-disable no-unused-vars */
/**
* Output this property in a dictionnary
* @param {Object.<string,any>} out
*/
output( out ) { /* eslint-enable no-unused-vars */
// ie:
// out['borderRadius'] = this;
// out[this._id] = this._value;
}
/**
*
* @param {Out} out
*/
_outputValue( out ) {
out[ this._id ] = this._value;
}
/* eslint-disable no-unused-vars */
/**
* Execute additional process after all properties have been updated
* @param {MeshUIBaseElement} element
*/
process( element ) { /* eslint-enable no-unused-vars */ }
/* eslint-disable no-unused-vars */
/**
* Execute additional process after all properties have been updated
* @param {MeshUIBaseElement} element
*/
render( element ) { /* eslint-enable no-unused-vars */ }
/**
*
* @param {MeshUIBaseElement} element
*/
getInheritedInput( element ) {
if ( this._value !== 'inherit' ) return this._value;
const parent = element._parent._value;
if ( parent && parent[ `_${this._id}` ] ) {
return parent[ `_${this._id}` ].getInheritedInput( parent )
}
return this.getDefaultValue();
}
/**
*
* @return {any}
*/
getDefaultValue() {
return DefaultValues.get( this._id );
}
/* eslint-disable no-unused-vars */
/**
*
* @param {any} value
* @return {boolean}
*/
isValid( value ) { /* eslint-enable no-unused-vars */
return true;
}
/**
*
*/
emptyStrategyLogic() {
throw new Error( `ThreeMeshUI::${this.constructor.name} has empty strategy. Update has not been processed.` );
}
requestUpdate() {
this._needsUpdate = true;
}
requestProcess() {
this._needsProcess = false;
}
requestRender() {
this._needsRender = false;
}
}
/**
* @typedef Out
* @type {Object & Object.<string,any>}
*/
@@ -0,0 +1,43 @@
import BaseProperty from './BaseProperty';
export default class BooleanProperty extends BaseProperty {
/**
*
* @param {string} propertyId
* @param {any} [value=null]
*/
constructor( propertyId, value = true ) {
super( propertyId, value, true );
/**
* @override
* @type {boolean}
* @private
*/
this._value = value;
this.output = this._outputValue;
}
/**
*
* @param {boolean} value
*/
set value( value ) {
this._value = value;
this._needsUpdate = true;
}
/**
*
* @return {boolean}
*/
get value() { return this._value; }
}
@@ -0,0 +1,399 @@
import BaseProperty from './BaseProperty';
import { Vector3 } from 'three';
import { numberEquals } from '../../utils/NumberUtils';
export default class BoundsBox extends BaseProperty {
constructor() {
super( 'bounds', null, false );
/**
*
* @type {Vector3}
* @internal
*/
this._size = new Vector3( 1, 1, 1 );
/**
*
* @type {number}
* @internal
*/
this._offsetWidth = 0;
/**
*
* @type {number}
* @internal
*/
this._offsetHeight = 0;
/**
*
* @type {number}
* @internal
*/
this._innerWidth = 0;
/**
*
* @type {number}
* @internal
*/
this._innerHeight = 0;
/**
*
* @type {number}
* @internal
*/
this._centerX = 0.5;
/**
*
* @type {number}
* @internal
*/
this._centerY = 0.5;
this._needsProcess = true;
}
/**
* Set the value of the width 100%
* @param element
* @param value
*/
setReferenceWidth( element, value ) {
const width = element._width;
const padding = element._padding._value;
const borderWidth = element._borderWidth._value;
const margin = element._margin._value;
const factor = width._auto ? 1 : width._value;
// const newOffsetWidth = (value * factor) - (margin.y + margin.w);
const newOffsetWidth = (value * factor) - (margin.y + margin.w);
if ( numberEquals( newOffsetWidth, this._offsetWidth ) ) return;
this._offsetWidth = newOffsetWidth;
this._innerWidth = this._offsetWidth - ( padding.y + padding.w + borderWidth.y + borderWidth.w );
this._centerX = _computeCenterX( element );
this._propagateWidth( element );
this._triggerCascadingDependencies( element );
}
/**
* Set the value of the height 100%
* @param element
* @param value
*/
setReferenceHeight( element, value ) {
const height = element._height;
const padding = element._padding._value;
const borderWidth = element._borderWidth._value;
const margin = element._margin._value;
const factor = height._auto ? 1 : height._value;
const newOffsetHeight = (value * factor) - ( margin.x + margin.z );
if ( numberEquals( newOffsetHeight, this._offsetHeight ) ) return;
this._offsetHeight = newOffsetHeight;
this._innerHeight = this._offsetHeight - ( padding.x + padding.z + borderWidth.x + borderWidth.z );
this._centerY = _computeCenterY( element );
this._propagateHeight( element );
this._triggerCascadingDependencies( element );
}
setChildrenWidth( element, value ) {
const padding = element._padding._value;
const border = element._borderWidth._value;
this._innerWidth = value;
this._offsetWidth = this._innerWidth + ( padding.y + padding.w + border.y + border.w )
this._centerX = _computeCenterX( element );
this._propagateWidth( element );
this._triggerCascadingDependencies( element );
}
setChildrenHeight( element, value ) {
const padding = element._padding._value;
const border = element._borderWidth._value;
this._innerHeight = value;
this._offsetHeight = this._innerHeight + ( padding.x + padding.z + border.x + border.z )
this._centerY = _computeCenterY( element );
this._propagateHeight( element );
this._triggerCascadingDependencies( element );
}
/* eslint-disable no-unused-vars */ update( element, out ) { /* eslint-enable no-unused-vars */
const padding = element._padding._value;
const border = element._borderWidth._value;
// only compute new width if explicitely defined
const width = element._width;
if( !width._auto && !width._relative ) {
if ( element._boxSizing._value === 'content-box' ) {
this._innerWidth = width._value;
this._offsetWidth = this._innerWidth + padding.y + padding.w + border.y + border.w;
} else {
this._offsetWidth = width._value;
this._innerWidth = this._offsetWidth - ( padding.y + padding.w + border.y + border.w );
}
this._centerX = _computeCenterX( element );
this._needsProcess = true;
// tells children width has changed
this._propagateWidth( element );
this._triggerCascadingDependencies( element );
}
const height = element._height;
if( !height._auto && !height._relative ) {
if ( element._boxSizing._value === 'content-box' ) {
this._innerHeight = height._value;
this._offsetHeight = this._innerHeight + padding.x + padding.z + border.x + border.z;
} else {
this._offsetHeight = height._value;
this._innerHeight = this._offsetHeight - ( padding.x + padding.z + border.x + border.z );
}
this._centerY = _computeCenterY( element );
this._needsProcess = true;
// tells children height has changed
this._propagateHeight( element );
this._triggerCascadingDependencies( element );
}
}
/* eslint-disable no-unused-vars */ render( element ) { /* eslint-enable no-unused-vars */
this._size.x = this._offsetWidth;
this._size.y = this._offsetHeight;
if( element._backgroundMesh ){
element._backgroundMesh.updateScale();
}
element._renderer._needsRender = true;
}
/**
*
* @param {Object.<string,any>} out
*/
output( out ) {
out[ 'size' ] = this._size;
}
/* eslint-disable no-unused-vars */
/**
* @override
*/
process( element ) { /* eslint-enable no-unused-vars */
// this._triggerCascadingDependencies( element )
//console.log( 'process bounds box', element.name );
// update primitives or unbinded values
// require cascading processes
element._overflow._needsRender = true;
}
/**
*
* @param element
* @internal
*/
_computeChildrenSideWidth( element ) {
return _computeChildrenSideWidth( element );
}
/**
*
* @param element
* @internal
*/
_computeChildrenSideHeight( element ) {
return _computeChildrenSideHeight( element );
}
_propagateWidth( element ) {
for ( let i = 0; i < element._children._boxes.length; i++ ) {
const box = element._children._boxes[ i ];
const width = box._width;
if( width._relative ) box._bounds.setReferenceWidth( box, this._innerWidth );
}
}
_propagateHeight( element ) {
for ( let i = 0; i < element._children._boxes.length; i++ ) {
const box = element._children._boxes[ i ];
const height = box._height;
if( height._relative ) box._bounds.setReferenceHeight( box, this._innerHeight );
}
}
_triggerCascadingDependencies( element ) {
// also change parent when require
if ( element._parent._value ) {
element._parent._value._autoSize._needsProcess = true;
}
element._flexDirection._needsProcess = true;
element._fontSize._needsProcess = true;
element._layouter._needsProcess = true;
this._needsRender = true;
element._borderWidth._needsRender = true;
element._borderRadius._needsRender = true;
element._overflow._needsRender = true;
}
}
/***********************************************************************************************************************
* INTERNAL FUNCTIONS
**********************************************************************************************************************/
/**
* Retrieve the center X according to box sized dimensions
* @param {MeshUIBaseElement} element
* @return {number}
*/
function _computeCenterX( element ) {
const padding = element._padding._value;
const borderWidth = element._borderWidth._value;
const leftSide = padding.w + borderWidth.w;
const rightSide = padding.y + borderWidth.y;
return ( leftSide - rightSide ) / 2;
}
/**
* Retrieve the center Y according to box sized dimensions
* @param {MeshUIBaseElement} element
* @return {number}
*/
function _computeCenterY( element ) {
const padding = element._padding._value;
const borderWidth = element._borderWidth._value;
const topSide = padding.x + borderWidth.x;
const bottomSide = padding.z + borderWidth.z;
return ( bottomSide - topSide ) / 2;
}
/**
* Return the sum of all this component's children width
* @param {MeshUIBaseElement} element
* @return {number}
*/
function _computeChildrenSideWidth( element ) {
return element._children._boxes.reduce( ( accu, child ) => {
// if ( child._bounds._needsProcess ) child._bounds.process( child );
const margin = child._margin._value;
const CHILD_SIZE = child._bounds._offsetWidth + margin.y + margin.w;
return accu + CHILD_SIZE;
}, 0 );
}
/**
* Return the sum of all this component's children width
* @param {MeshUIBaseElement} element
* @return {number}
*/
function _computeChildrenSideHeight( element ) {
return element._children._boxes.reduce( ( accu, child ) => {
// if ( child._bounds._needsProcess ) child._bounds.process( child );
const margin = child._margin._value;
const CHILD_SIZE = child._bounds._offsetHeight + margin.x + margin.z;
return accu + CHILD_SIZE;
}, 0 );
}
@@ -0,0 +1,65 @@
import BaseProperty from './BaseProperty';
import { Vector3 } from 'three';
export default class BoundsInlineBlock extends BaseProperty {
constructor() {
super( 'bounds', null, false );
/**
*
* @type {Vector3}
* @internal
*/
this._size = new Vector3( 1, 1, 1 );
this._offsetWidth = 0;
this._offsetHeight = 0;
this._innerWidth = 0;
this._innerHeight = 0;
}
/* eslint-disable no-unused-vars */ update( element, out ) { /* eslint-enable no-unused-vars */
this.output( out );
this._needsProcess = true;
}
process( element ) {
this._offsetWidth = this._innerWidth = element._inlines._value[0].width;
this._offsetHeight = this._innerHeight = element._inlines._value[0].height;
this._needsRender = true;
element._borderWidth._needsRender = true;
element._borderRadius._needsRender = true;
}
/* eslint-disable no-unused-vars */ render( element ) { /* eslint-enable no-unused-vars */
this._size.x = this._offsetWidth;
this._size.y = this._offsetHeight;
}
/**
*
* @param {Object.<string,any>} out
*/
output( out ) {
out[ 'size' ] = this._size;
}
}
@@ -0,0 +1,14 @@
import BoundsBox from './BoundsBox';
export default class BoundsText extends BoundsBox {
constructor() {
super();
this._innerWidth = Infinity;
this._innerHeight = 0;
}
}
@@ -0,0 +1,87 @@
import BaseProperty from './BaseProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import MeshUIBaseElement from './../../core/elements/MeshUIBaseElement';
import Line from './../../core/elements/glyphs/Line';
import { Vector3 } from 'three';
/* eslint-enable no-unused-vars */
export default class BoxLayouter extends BaseProperty {
constructor() {
super( 'layouter', null, false);
// configure
this._needsUpdate = true;
/**
* @typedef ChildrenPos
* @type {Object & Object.<string,Vector3>}
*/
/**
*
* @type {ChildrenPos}
* @internal
*/
this._childrenPos = {};
}
/* eslint-disable no-unused-vars */
/**
* Updated when :
* - New child added
* - Child removed
* - Child position changed
* - Child visibility changed
* - ...?
* @override
*/
update( element, out ) { /* eslint-enable no-unused-vars */
//console.log( "BoxLayouter update", element.name );
// reset
this._childrenPos = {};
for ( const uiBoxElement of element._children._boxes ) {
//console.log( uiBoxElement._position._value )
if( uiBoxElement._position._value === 'static' ) {
// bind position
this._childrenPos[ uiBoxElement.id ] = uiBoxElement.position;
}
}
}
/**
*
* @override
*/
/* eslint-disable no-unused-vars */ process( element ) { /* eslint-enable no-unused-vars */
// As _childrenPos are bounds with child.position, this is not required anymore
//
// element._position._needsProcess = true;
//
// for ( const box of element._children._boxes ) {
//
// if( this._childrenPos[box.id] ) {
//
// box.position.x = this._childrenPos[box.id].x;
// box.position.y = this._childrenPos[box.id].y;
//
// }
//
// }
}
}
@@ -0,0 +1,30 @@
import BaseProperty from './BaseProperty';
export default class EmptyProperty extends BaseProperty {
/**
*
* @param {string} propertyId
*/
constructor( propertyId = 'untitled' ) {
super( propertyId, undefined, false );
}
/* eslint-disable no-unused-vars */
/**
*
* @param element
* @param {Object.<string,any>} out
*/
update( element , out ) { /* eslint-enable no-unused-vars */ }
/* eslint-disable no-unused-vars */
/**
* Output this property in a dictionnary
* @param {Object.<string,any>} out
*/
output( out ) { /* eslint-enable no-unused-vars */ }
}
@@ -0,0 +1,207 @@
import BaseProperty from './BaseProperty';
import FontVariant from '../../font/FontVariant';
export default class FontProperty extends BaseProperty{
/**
*
* @param {FontVariant} [value=null]
*/
constructor( value = null ) {
super( 'font', value, false);
this._needsUpdate = false;
/**
*
* @type {FontVariant|null}
* @internal
*/
this._fontVariant = null;
/**
* @typedef ReadyClosure
* @type { ()=> void|null }
*/
/**
*
* @type {ReadyClosure}
* @private
*/
this._handleFontReadyClosure = null;
/**
* @override
*/
this.isValid = _isValid;
}
output( out ) {
out[this._id] = this._fontVariant;
}
/* eslint-disable no-unused-vars */
/**
*
* @override
*/
update( element, out ) { /* eslint-enable no-unused-vars */
// if a previous font isset, be sure no event remains
if ( this._fontVariant && !this._fontVariant.isReady ) {
this._fontVariant.removeEventListener( 'ready', this._handleFontReadyClosure );
}
// obtain font from value or from style combinaison
if( this._value && this._value instanceof FontVariant ) {
this._fontVariant = this._value;
} else {
const fontFamily = element._fontFamily._value;
if( fontFamily ) {
this._fontVariant = fontFamily.getVariant(
element._fontWeight._value,
element._fontStyle._value,
);
}
}
if( !this._fontVariant ) return;
this._fontVariant._alterElementProperties( element );
this._handleFontReadyClosure = _readyClosure( element, this );
// new font, means rebuild inlines, now or soon
if ( !this._fontVariant.isReady ) {
// @TODO : clear inlines components
// this.inlines = null;
this._fontVariant.addEventListener( 'ready', this._handleFontReadyClosure );
} else {
this._handleFontReadyClosure();
}
// Set the default material
if( !element._fontMaterial._defaultMaterial || !(element._fontMaterial._defaultMaterial instanceof this._fontVariant.fontMaterial) ) {
element._fontMaterial._defaultMaterial = new this._fontVariant.fontMaterial();
element._fontMaterial._needsUpdate = true;
}
}
/**
* @override
* @param {FontVariant} value
*/
set value( value ) {
if( ! this.isValid( value) ) return;
if( this._value !== value ) {
this._value = value;
this._needsUpdate = true;
}
}
/**
*
* @return {FontVariant}
*/
get value() { return this._value; }
/**
*
* @return {FontVariant|null}
*/
get fontVariant() { return this._fontVariant; }
/**
*
*/
dispose () {
if( this._handleFontReadyClosure ) {
this._fontVariant.removeEventListener( 'ready', this._handleFontReadyClosure );
this._handleFontReadyClosure = null;
}
this._value = null;
this._fontVariant = null;
}
}
/**
*
* @param {number} value
* @return {boolean}
* @private
*/
function _isValid( value ) {
if( ! ( value instanceof FontVariant ) ) {
console.warn(`.font value '${value}' is not valid. It requires a FontVariant instance. Aborted`);
return false;
}
return true;
}
/**
*
* @param {MeshUIBaseElement} element
* @param {FontProperty} fontProperty
* @return {() => void}
* @private
*/
function _readyClosure( element, fontProperty ) {
return function () {
fontProperty._needsUpdate = true;// ? update itself?
element._glyphs._needsProcess = true;
// this._transferToMaterial();
// request parse update and parent layout
// this.update( true, true, false );
// this.getHighestParent().update( false, true, false );
// remove the listener
fontProperty._fontVariant.removeEventListener( 'ready', fontProperty._handleFontReadyClosure );
fontProperty._handleFontReadyClosure = null;
}
}
@@ -0,0 +1,36 @@
import InheritableProperty from './InheritableProperty';
export default class FontSmoothProperty extends InheritableProperty{
constructor() {
super( 'fontSmooth', 'inherit', true);
// configure
this._needsUpdate = false;
this.isValid = _isValid;
this.output = this._outputValue;
}
}
const AVAILABLE_VALUES = ['inherit','none','antialiased'];
/**
*
* @param {string} value
* @return {boolean}
* @private
*/
function _isValid( value ) {
if( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn(`.fontSmoothing value '${value}' is not valid. Aborted`);
return false;
}
return true;
}
@@ -0,0 +1,15 @@
import FontSmoothProperty from './FontSmoothProperty';
export default class FontSmoothPropertyInline extends FontSmoothProperty{
constructor() {
super();
// configure
this.output = this._outputValue;
this._needsUpdate = true;
}
}
@@ -0,0 +1,72 @@
import BaseProperty from './BaseProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import TypographicGlyph from '../../font/TypographicGlyph';
/* eslint-enable no-unused-vars */
export default class GlyphsProperty extends BaseProperty{
constructor() {
super( "glyphs", null, false);
this._needsUpdate = false;
/**
*
* @type {Array.<TypographicGlyph>}
* @private
*/
this._value = null;
// value
// 1. collapsed whiteSpace;
// this._textContent = Whitespace.collapseWhitespaceOnString( this.content, this.getWhiteSpace() );
// 2. glyphs
// this._textContentGlyphs = this._textContent.split( '' ).map( ( char ) => this._font.getTypographicGlyph( char ) );
// 3. Inlines
// this._textContentInlines = this._textContentGlyphs.map( ( glyphBox ) => glyphBox.asInlineGlyph() );
// 4. kerning
// this._buildContentKernings();
// 5.? Apply margin and padding on first and last inlines
// if( this._textContentInlines.length ) {
//
// // First gets left side
// this._textContentInlines[0].paddingLeft = this._padding.w;
// this._textContentInlines[0].marginLeft = this._margin.w;
//
// // Last gets right side
// const lastIndex = this._textContentInlines.length - 1;
// this._textContentInlines[lastIndex].paddingRight = this._padding.y;
// this._textContentInlines[lastIndex].marginRight = this._margin.y;
//
// }
}
process( element ) {
if( !element._font._fontVariant ) return;
if( !element._font._fontVariant.isReady ) return;
this._value = element._whiteSpace._whiteSpacedContent.split( '' ).map( ( char ) => element._font._fontVariant.getTypographicGlyph( char ) );
// @TODO : Even if the value is removed it should trigger a rebuild.
if( this._value ) element._inlines._needsProcess = true;
}
/**
*
* @return {Array.<TypographicGlyph>}
*/
get value() { return this._value; }
}
@@ -0,0 +1,18 @@
import InheritableProperty from './InheritableProperty';
/**
* @property {boolean|"inherit"} value
*/
export default class InheritableBooleanProperty extends InheritableProperty {
/**
*
* @param {string} propertyId
*/
constructor( propertyId) {
super( propertyId, 'inherit', true );
}
}
@@ -0,0 +1,107 @@
import InheritableProperty from './InheritableProperty';
import { alphaTestTransformer } from '../../utils/mediator/transformers/MaterialTransformers';
/**
* @property {Material|null|"inherit"} value
*/
export default class InheritableMaterialProperty extends InheritableProperty {
/**
*
* @param {string} propertyId
*/
constructor( propertyId ) {
super( propertyId, 'inherit', false );
/**
*
* @type {Object.<{m:string, t?:(target:any, targetProperty:string, value:any) => void}>}
* @internal
*/
this._mediation = {};
/**
*
* @type {null}
* @internal
*/
this._defaultMaterial = null;
}
update( element, out ) { /* eslint-enable no-unused-vars */
this._notInheritedValue = this._value;
if ( this._notInheritedValue === 'inherit' ) {
this._notInheritedValue = this.getInheritedInput( element );
} else {
this.propagate( element );
}
// no material
if ( !this._notInheritedValue ) {
// reset mediation
this._mediation = {};
} else if ( this._notInheritedValue.constructor.mediation ) {
this._mediation = { ...this._notInheritedValue.constructor.mediation };
} else {
this._mediation = {
clippingPlanes: { m: 'clippingPlanes' },
fontAlphaTest: { m: 'alphaTest', t: alphaTestTransformer },
fontSide: { m: 'side' },
color: { m: 'color' },
fontOpacity: { m: 'opacity' }
};
}
element._transferToFontMaterial();
// dispatch to children
this._outputValue( out );
}
/**
* @override
*/
getInheritedInput( element ) {
if ( this._value !== 'inherit' ) return this._value;
let recursiveParent = element;
let inheritedValue = null;
while ( recursiveParent._parent._value ) {
recursiveParent = recursiveParent._parent._value;
if ( recursiveParent[ `_${this._id}` ]._value !== 'inherit' ) {
inheritedValue = recursiveParent[ `_${this._id}` ]._value;
break;
}
}
if ( inheritedValue !== null ) {
return inheritedValue;
}
return this.getDefaultValue();
}
getDefaultValue() {
return this._defaultMaterial;
}
}
@@ -0,0 +1,90 @@
import BaseProperty from './BaseProperty';
export default class InheritableProperty extends BaseProperty {
/**
*
* @param {string} propertyId
* @param {any} [value=null]
* @param primitive
*/
constructor( propertyId, value = null, primitive = true ) {
super( propertyId, value, primitive );
// @TODO : I would like to remove this rules ( here )
this.output = this._outputValue;
this._notInheritedValue = null;
}
update( element , out ) { /* eslint-enable no-unused-vars */
this._notInheritedValue = this._value;
if( this._notInheritedValue === 'inherit' )
{
this._notInheritedValue = this.getInheritedInput( element );
}
// else
// {
// this.propagate( element );
// }
// @TODO: Evaluate. This might be too much
this.propagate( element );
this._outputValue( out );
}
propagate( element ) {
// rebuild same properties on children 'inheritance'
for ( const childUIElement of element._children._uis ) {
const property = childUIElement[`_${this._id}`];
if( property !== undefined && property._value === 'inherit' ) {
childUIElement[`_${this._id}`]._needsUpdate = true;
}
}
}
/**
* Output this property in a dictionnary
* @override
*/
_outputValue( out ) { /* eslint-enable no-unused-vars */
out[this._id] = this._notInheritedValue;
}
set value ( value ) {
if( ! this.isValid( value) ) return;
if( this._value !== value ) {
this._value = value;
this._needsUpdate = true;
}
}
/**
*
* @override
* @return {any|"inherit"}
*/
get value() {
if( this._value === 'inherit' ) return this._notInheritedValue;
return this._value;
}
}
@@ -0,0 +1,75 @@
import BaseProperty from './BaseProperty';
export default class InlineJustificator extends BaseProperty {
constructor() {
super( 'inlineJustificator', null, false );
/**
*
* @type {Lines}
* @private
*/
this._value = null;
}
/* eslint-disable no-unused-vars */ update( element, out ) { /* eslint-enable no-unused-vars */ }
/**
*
* @override
*/
process( element ) {
const INNER_HEIGHT = element._bounds._innerHeight;
const lines = element._layouter._value;
const textHeight = Math.abs( lines.height );
// Line vertical positioning
let justificationOffset = ( () => {
switch ( element._alignItems._value ) {
case 'inherit':
case 'start':
return INNER_HEIGHT / 2 ;
case 'end':
return textHeight - ( INNER_HEIGHT / 2 );
case 'stretch': // @TODO : Stretch should trigger an error in own property
case 'center':
return textHeight/2;
}
} )();
//console.log( justificationOffset );
// Apply padding
const padding = element._padding._value;
const border = element._borderWidth._value;
justificationOffset += ( - padding.x + padding.z ) / 2 + ( - border.x + border.z ) / 2;
//
lines.forEach( ( line ) => {
line.y += justificationOffset;
line.forEach( ( inline ) => {
inline.offsetY += justificationOffset;
} );
} );
}
}
@@ -0,0 +1,45 @@
import BaseProperty from './BaseProperty';
export default class InlineLayouter extends BaseProperty {
constructor() {
super( 'layouter', null, false );
/**
*
* @type {MeshUIBaseElement}
* @private
*/
this._value = null;
}
/* eslint-disable no-unused-vars */ update( element, out ) { /* eslint-enable no-unused-vars */
// find the first text parent;
this._value = element._parent.find( (p) => { return p.isUI && p.isText } );
this._needsProcess = true;
}
/* eslint-disable no-unused-vars */
/**
*
* @override
*/
process( element ) { /* eslint-enable no-unused-vars */
// layout has been changed
if( this._value ) {
this._value._layouter._needsProcess = true;
}
}
}
@@ -0,0 +1,78 @@
import BaseProperty from './BaseProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import Inline from '../elements/glyphs/Inline';
/* eslint-enable no-unused-vars */
export default class InlinesProperty extends BaseProperty{
constructor() {
super( "inlines", null, false );
/**
*
* @type {Array.<Inline>}
* @private
*/
this._value = null;
// value
// 3. Inlines
// this._textContentInlines = this._textContentGlyphs.map( ( glyphBox ) => glyphBox.asInlineGlyph() );
// 4. kerning
// this._buildContentKernings();
// 5.? Apply margin and padding on first and last inlines
// if( this._textContentInlines.length ) {
//
// // First gets left side
// this._textContentInlines[0].paddingLeft = this._padding.w;
// this._textContentInlines[0].marginLeft = this._margin.w;
//
// // Last gets right side
// const lastIndex = this._textContentInlines.length - 1;
// this._textContentInlines[lastIndex].paddingRight = this._padding.y;
// this._textContentInlines[lastIndex].marginRight = this._margin.y;
//
// }
}
process( element ) {
this._value = element._glyphs._value.map( ( glyphBox ) => glyphBox.asInlineGlyph() );
if( this._value.length ) {
// First gets left side
this._value[0].paddingLeft = element._padding._value.w;
this._value[0].marginLeft = element._margin._value.w;
// Last gets right side
const lastIndex = this._value.length - 1;
this._value[lastIndex].paddingRight = element._padding._value.y;
this._value[lastIndex].marginRight = element._margin._value.y;
}
element._fontSize._needsProcess = true;
element._lineBreak._needsProcess = true;
element._fontKerning._needsProcess = true;
element._layouter._needsProcess = true;
}
/**
*
* @return {Array.<Inline>}
*/
get value() { return this._value; }
}
@@ -0,0 +1,40 @@
import BaseProperty from './BaseProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import Inline from '../elements/glyphs/Inline';
/* eslint-enable no-unused-vars */
/**
* @property {Array.<InlineGlyph>} value
*/
export default class InlinesPropertyInlineBlock extends BaseProperty{
constructor() {
super( "inlines", null, false );
/**
*
* @type {Array.<Inline>}
* @internal
*/
this._value = [];
}
process( element ) {
// First gets left side
this._value[0].paddingLeft = element._padding._value.w;
this._value[0].marginLeft = element._margin._value.w;
// Last gets right side
const lastIndex = this._value.length - 1;
this._value[lastIndex].paddingRight = element._padding._value.y;
this._value[lastIndex].marginRight = element._margin._value.y;
}
}
@@ -0,0 +1,16 @@
import InheritableProperty from './InheritableProperty';
/**
* Class definition
* @property {boolean|"inherit"} value - propriety description
*
*/
export default class InvertAlphaProperty extends InheritableProperty {
constructor() {
super( 'invertAlpha', 'inherit' );
}
}
@@ -0,0 +1,76 @@
import BaseProperty from './BaseProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
/* eslint-enable no-unused-vars */
export default class LineBreakProperty extends BaseProperty{
constructor( defaultValue = "- ,.:?!\n" ) {
super( "lineBreak", defaultValue, true );
/**
*
* @type {"mandatory"|"possible"|null}
* @private
*/
this._newLineBreakability = null;
}
/* eslint-disable no-unused-vars */ update( element, out ) { /* eslint-enable no-unused-vars */
this._needsProcess = true;
}
process( element ) {
const newLineBreakability = element._whiteSpace._newLineBreakability;
if( !element._inlines._value ) return;
// update inlines properties before inline placements in lines
for ( let i = 0; i < element._inlines._value.length; i++ ) {
const inline = element._inlines._value[ i ];
const char = inline.char;
// Whitespace Breakability ---------------------------------------------------------------------------------------
let lineBreak = null;
// could be inlineBlock without char
if( char !== undefined ) {
// @question : Does it worth to be strategy? I don't really think so
if ( newLineBreakability !== 'nowrap' ) {
if ( this._value.includes( char ) || char.match( /\s/g ) ) lineBreak = 'possible';
}
if ( char.match( /\n/g ) ) {
lineBreak = newLineBreakability;
}
}
inline.lineBreak = lineBreak;
}
}
/**
* @override
* @return {string}
*/
get value() { return this._value; }
}
@@ -0,0 +1,42 @@
import BaseProperty from './BaseProperty';
import { numberEquals } from '../../utils/NumberUtils';
export default class NumberProperty extends BaseProperty{
/**
*
* @param {string} propertyId
* @param {number} [value]
*/
constructor( propertyId, value ) {
super( propertyId, value, true);
this.output = this._outputValue;
}
/**
*
* @param {number} value
*/
set value( value ) {
if( ! this.isValid( value) ) return;
if( numberEquals(this._value, value) ) return;
this._value = value;
this._needsUpdate = true;
}
/**
*
* @return {number}
*/
get value() { return this._value; }
}
@@ -0,0 +1,22 @@
import InheritableProperty from './InheritableProperty';
export default class OffsetProperty extends InheritableProperty {
constructor( ) {
super( 'offset', 'inherit', false );
}
/* eslint-disable no-unused-vars */ update( element, out ) { /* eslint-enable no-unused-vars */
super.update( element, out);
// only process if element has ui parent
if( element._parent._value !== null ) element.position.z = this._notInheritedValue;
}
}
@@ -0,0 +1,73 @@
import BaseProperty from './BaseProperty';
export default class RenderOrderProperty extends BaseProperty{
constructor() {
super( 'renderOrder', 'auto', true);
this.output = this._outputValue;
this._actualValue = 0;
}
/**
*
* @param {number} value
*/
set value( value ) {
if( ! this.isValid( value) ) return;
this._value = value;
this._needsUpdate = true;
}
update( element, out ) {
if( this._value !== 'auto' ) {
this._actualValue = this._value;
} else {
const parent = element._parent._value;
if( parent !== null ) {
const parentIndex = parent._renderOrder._actualValue;
const positionInParent = 1 + parent._children._uis.indexOf( element );
this._actualValue = parentIndex + positionInParent;
}
}
// update any children
for ( const childUIElement of element._children._uis ) {
const property = childUIElement[`_renderOrder`];
if( property._value === 'auto' ) childUIElement[`_renderOrder`]._needsUpdate = true;
}
this._outputValue( out );
}
_outputValue( out ) {
out[this._id] = this._actualValue;
}
/**
*
* @return {number}
*/
get value() { return this._value; }
}
@@ -0,0 +1,42 @@
import { BackSide, DoubleSide, FrontSide } from 'three';
import InheritableProperty from './InheritableProperty';
/**
* @property {number|"inherit"} value
*/
export default class SideProperty extends InheritableProperty {
/**
*
* @param {string} propertyId
*/
constructor( propertyId ) {
super( propertyId, 'inherit', true);
this.isValid = _isValid;
}
}
const AVAILABLE_VALUES = [ FrontSide, BackSide, DoubleSide ];
/**
*
* @param {any} value
* @return {boolean}
* @private
*/
function _isValid( value ) {
if( AVAILABLE_VALUES.indexOf( value) === -1 ){
console.warn(`SideProperty value '${value}' is not valid. Abort`);
return false;
}
return true;
}
@@ -0,0 +1,36 @@
import EmptyProperty from './EmptyProperty';
export default class TextContentDefault extends EmptyProperty{
constructor() {
super( "textContent" );
this._needsUpdate = false;
}
/* eslint-disable no-unused-vars */ set value( v ) { /* eslint-enable no-unused-vars */ }
/* eslint-disable no-unused-vars */ process( element ) { /* eslint-enable no-unused-vars */
let content = "";
for ( let i = 0; i < element.children.length; i++ ) {
const child = element.children[i];
if( child.isUI ) {
content += child.textContent;
}
}
this._value = content;
}
}
@@ -0,0 +1,19 @@
import BaseProperty from './BaseProperty';
export default class TextContentInline extends BaseProperty{
constructor() {
super( "textContent", null, false );
}
/* eslint-disable no-unused-vars */ update( element, out ) { /* eslint-enable no-unused-vars */
element._glyphs._needsUpdate = true;
element._whiteSpace._needsProcess = true;
}
}
@@ -0,0 +1,78 @@
import InlineElement from '../../elements/basic/InlineElement';
import TextContentDefault from './TextContentDefault';
export default class TextContentProperty extends TextContentDefault{
constructor() {
super( "textContent", null, false );
this._needsUpdate = false;
}
set value( value ) {
// If content hasn't change, dont update it
if( this._value !== value ) {
this._value = value;
this._needsUpdate = true;
}
}
/* eslint-disable no-unused-vars */ update( element, out ) { /* eslint-enable no-unused-vars */
// prevent multiple update
this._needsUpdate = false;
// Remove all its children (Inlines)
for ( let i = element.children.length - 1 ; i >= 0; i-- ) {
const child = element.children[ i ];
if( child.isUI ) {
element.remove( child );
child.clear();
}
}
// Rebuild its child list
element._children._uis = [];
// If a value, add a child
if( this._value ) {
const childrenInlines = this.buildInlines( this._value );
if( childrenInlines.length ){
element.add( ...childrenInlines );
}
}
if( element.isInline ) {
element._glyphs._needsUpdate = true;
element._whiteSpace._needsProcess = true;
}
}
/**
*
* @param {string} textContent
* @return {InlineElement[]}
*/
buildInlines( textContent ) {
return [ new InlineElement({name:'anonymousInline',textContent}) ];
}
}
@@ -0,0 +1,251 @@
import BaseProperty from './BaseProperty';
import Lines from '../elements/glyphs/Lines';
import Line from '../elements/glyphs/Line';
export default class TextLayouter extends BaseProperty {
constructor() {
super( 'layouter', null, false );
/**
*
* @type {Lines}
* @private
*/
this._value = null;
}
/* eslint-disable no-unused-vars */ update( element, out ) { /* eslint-enable no-unused-vars */ }
/**
*
* @override
*/
process( element ) {
let INNER_WIDTH = element._width._value;
if ( element._width._auto ) {
INNER_WIDTH = Infinity;
} else {
INNER_WIDTH = element._bounds._innerWidth;
}
// Compute lines
const INTERLINE = element._lineHeight._value;
// Will stock the characters of each line, so that we can
// correct lines position before to merge
const lines = new Lines( new Line() );
let lastInlineOffset = 0;
element._children._inlines.forEach( ( inlineElement ) => {
// Abort condition
if ( !inlineElement._inlines.value ) return;
this._resetInlines( inlineElement );
//////////////////////////////////////////////////////////////
// Compute offset of each children according to its dimensions
//////////////////////////////////////////////////////////////
// @TODO: Fontsize best fit
const FONTSIZE = inlineElement._fontSize._value;
const LETTERSPACING = inlineElement._letterSpacing._value * FONTSIZE;
const WHITESPACE = inlineElement._whiteSpace._value;
const BREAKON = inlineElement._lineBreak._value;
const whiteSpaceOptions = {
WHITESPACE,
LETTERSPACING,
BREAKON,
INNER_WIDTH
}
const inlineWrapper = inlineElement._whiteSpace._inlineWrapper;
lastInlineOffset += inlineElement._margin._value.w + inlineElement._padding._value.w;
inlineElement._inlines.value.forEach( ( inline, i, inlines ) => {
const line = lines[lines.length - 1];
// Line break
const shouldBreak = inlineWrapper(inlines,i,lastInlineOffset, whiteSpaceOptions );
if ( shouldBreak ) {
lines.push( new Line( inline ) );
inline.offsetX = inline.xoffset;
// restart the lastInlineOffset as zero.
if ( inline.width === 0 ) {
lastInlineOffset = 0;
return;
}
// compute lastInlineOffset normally
// except for kerning which won't apply
// as there is visually no lefthanded glyph to kern with
inline.cumulativeWidth = inline.xadvance + LETTERSPACING;
lastInlineOffset = inline.cumulativeWidth;
return;
}
lines[ lines.length - 1 ].push( inline );
inline.offsetX = lastInlineOffset + inline.xoffset + inline.kerning;
inline.cumulativeWidth = inline.xadvance + inline.kerning + LETTERSPACING;
lastInlineOffset += inline.cumulativeWidth;
// in case of lineBreak mandatory
if( line.length-1 === 1) {
if ( line[ line.length - 2 ].width === 0 ) {
// remove the offset of the character following the newline
inline.offsetX -= inline.xoffset;
lastInlineOffset -= inline.xoffset;
}
}
} );
lastInlineOffset += inlineElement._margin._value.y + inlineElement._padding._value.y;
} );
// Compute single line and combined lines dimensions
const inlineCollapser = element._whiteSpace._inlineCollapser;
let width = 0, height =0, lineOffsetY = 0;
// calculates lines
lines.forEach( ( line, i ) => {
// starts by processing whitespace, it will return a collapsed left offset
const whiteSpaceOffset = inlineCollapser( line );
//
let lineHeight = 0;
let lineBase = 0;
line.forEach( ( inline ) => {
lineHeight = Math.max( lineHeight, inline.lineHeight );
lineBase = Math.max( lineBase, inline.lineBase );
inline.offsetX -= whiteSpaceOffset;
});
line.lineHeight = lineHeight;
line.lineBase = lineBase;
// const baseLineDelta = lineHeight - lineBase;
if( i === 0 ){
lineOffsetY = -(lineHeight*INTERLINE - lineHeight) * 0.5;
} else {
lineOffsetY -= lines[i-1].lineHeight*INTERLINE;
}
line.y = lineOffsetY;
line.x = 0;
// process yoffset
line.forEach( ( inline ) => {
inline.offsetY = lineOffsetY - inline.anchor;
if( inline.lineHeight < line.lineHeight ){
inline.offsetY -= line.lineBase- inline.lineBase;
}
});
height += ( line.lineHeight * INTERLINE );
// height += ( line.lineHeight);
//
line.width = 0;
// if this line have inlines
if ( line[ 0 ] ) {
// compute its width: length from firstInline:LEFT to lastInline:RIGHT
// only by the length of its extremities
const lastInline = line[ line.length - 1 ];
// Right + Left ( left is negative )
line.width = ( lastInline.offsetX + lastInline.cumulativeWidth + lastInline.paddingRight + lastInline.marginRight ) + line[ 0 ].offsetX;
width = Math.max( width, line.width);
}
} );
lines.height = height;
lines.width = width;
this._value = lines;
if( INNER_WIDTH === Infinity ) {
element._bounds.setChildrenWidth( element, lines.width );
}
if( element._height._auto ) {
element._bounds.setChildrenHeight( element, lines.height );
}
const parent = element._parent._value;
if( parent ) {
parent._autoSize._needsProcess = true;
parent._flexDirection._needsProcess = true;
}
element._inlineJustificator._needsProcess = true;
element._textAlign._needsProcess = true;
element._overflow._needsUpdate = true;
}
/**
*
* @param inlineElement
* @protected
*/
_resetInlines ( inlineElement ) {
// ensure no collapsed remains
inlineElement._fontSize.process( inlineElement );
}
}
@@ -0,0 +1,50 @@
import BaseProperty from './BaseProperty';
export default class VisibleProperty extends BaseProperty{
/**
*
* @param {string} propertyId
* @param {any} [value=null]
*/
constructor( propertyId, value = true ) {
super( 'visible', value, true );
this._needsUpdate = false;
}
/* eslint-disable no-unused-vars */ update( element, out ) { /* eslint-enable no-unused-vars */
element.visible = this._value;
if( element._parent._value ) {
element._parent._value._children._needsUpdate = true;
}
}
set value( value ) {
if( ! this.isValid( value) ) return;
if( this._value !== value ) {
this._value = value;
this._needsUpdate = true;
}
}
/**
*
* @return {boolean}
*/
get value() { return this._value; }
}
@@ -0,0 +1,11 @@
import BaseProperty from '../BaseProperty';
export default class SegmentsProperty extends BaseProperty {
constructor() {
super( 'segments', 1, false );
}
}
@@ -0,0 +1,13 @@
import SegmentsPropertyText from './SegmentsPropertyText';
export default class SegmentsPropertyInline extends SegmentsPropertyText {
constructor() {
super();
this._value = 'inherit';
}
}
@@ -0,0 +1,52 @@
import SegmentsProperty from './SegmentsProperty';
export default class SegmentsPropertyText extends SegmentsProperty {
constructor() {
super( 'segments', 1, false );
this._notInheritedValue = undefined;
}
/* eslint-disable no-unused-vars */ update( element, out ) { /* eslint-enable no-unused-vars */
this._notInheritedValue = this._value;
if ( this._notInheritedValue === 'inherit' ) {
this._notInheritedValue = this.getInheritedInput( element );
}
element._layouter._needsUpdate = true;
}
/**
*
* @param {number|"inherit"} v
*/
set value( v ) {
if ( this._value === v ) return;
this._value = v;
this._needsUpdate = true;
}
/**
*
* @override
* @return {number}
*/
get value() {
if ( this._value === 'inherit' ) return this._notInheritedValue;
return this._value;
}
}
@@ -0,0 +1,120 @@
import BaseProperty from '../BaseProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import MeshUIBaseElement from '../../elements/MeshUIBaseElement';
/* eslint-enable no-unused-vars */
export default class ChildrenBox extends BaseProperty {
constructor() {
super( 'children', null, false );
/**
*
* @type {Array.<MeshUIBaseElement>}
* @private
*/
this._uis = [];
/**
*
* @type {Array.<MeshUIBaseElement>}
* @internal
*/
this._boxes = [];
}
/* eslint-disable no-unused-vars */
/**
* Update requested when :
* - New child has been added
* - Existing child has been removed
*
* @param element
* @param out
*/
update( element, out ) { /* eslint-enable no-unused-vars */
this._compute( element );
element._layouter._needsUpdate = true;
element._renderOrder._needsUpdate = true;
}
/**
* Process when :
* - Existing child visibility changed
*
* @param element
*/
process( element ) {
this._compute( element );
element._flexDirection._needsProcess = true;
element._layouter._needsProcess = true;
element._overflow._needsRender = true;
}
_compute( element ) {
// Stores all children that are box
this._uis = element.children.filter( child => child.visible && child.isUI );
this._boxes = this._uis.filter( child => child.isBox ).sort( this._sortOrder );
// @TODO: check if it has changes boxes values? with array join to 'fingerprint'?
// computation to remove computation? Does it worth it? When would it worth it?
// // Changed order property of children but doesn't impact the output of boxes => Order have change, okay to have more computation
// // Removed the Added the same element, at the same position => Rare case
// Conclusion : Not worth it at the time of writing
}
/**
*
*/
dispose() {
this._uis = null;
this._boxes = null;
}
/**
*
* Sort children according to their .style.order property or fallback on children index
*
* @param {HTMLElementVR} a
* @param {HTMLElementVR} b
* @return {number}
* @private
*/
_sortOrder = ( a, b ) => {
if( a._order._value < b._order._value ) return -1;
if( a._order._value > b._order._value ) return 1;
// if both children have the same order value, use their children index to order them
if( this._uis.indexOf(a) < this._uis.indexOf(b) ) {
return -1;
}
return 1;
}
}
@@ -0,0 +1,118 @@
import BaseProperty from '../BaseProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import MeshUIBaseElement from '../../elements/MeshUIBaseElement';
/* eslint-enable no-unused-vars */
export default class ChildrenBox extends BaseProperty {
constructor() {
super( 'children', null, false );
/**
*
* @type {Array.<MeshUIBaseElement>}
* @private
*/
this._uis = [];
/**
*
* @type {Array.<MeshUIBaseElement>}
* @internal
*/
this._boxes = [];
}
/* eslint-disable no-unused-vars */
/**
* Update requested when :
* - New child has been added
* - Existing child has been removed
*
* @param element
* @param out
*/
update( element, out ) { /* eslint-enable no-unused-vars */
this._compute( element );
element._layouter._needsUpdate = true;
element._renderOrder._needsUpdate = true;
}
/**
* Process when :
* - Existing child visibility changed
*
* @param element
*/
process( element ) {
this._compute( element );
element._flexDirection._needsProcess = true;
element._layouter._needsProcess = true;
element._overflow._needsRender = true;
}
_compute( element ) {
// Stores all children that are box
this._uis = element.children.filter( child => child.visible && child.isUI );
this._boxes = this._uis.filter( child => child.isBox ).sort( this._sortOrder );
// @TODO: check if it has changes boxes values? with array join to 'fingerprint'?
// computation to remove computation? Does it worth it? When would it worth it?
// // Changed order property of children but doesn't impact the output of boxes => Order have change, okay to have more computation
// // Removed the Added the same element, at the same position => Rare case
// Conclusion : Not worth it at the time of writing
}
/**
*
*/
dispose() {
this._uis = null;
this._boxes = null;
}
/**
*
* Sort children according to their .style.order property or fallback on children index
*
* @param {HTMLElementVR} a
* @param {HTMLElementVR} b
* @return {number}
* @private
*/
_sortOrder = ( a, b ) => {
if( a._order._value < b._order._value ) return -1;
if( a._order._value > b._order._value ) return 1;
// if both children have the same order value, use their children index to order them
if( this._uis.indexOf(a) < this._uis.indexOf(b) ) {
return -1;
}
return 1;
}
}
@@ -0,0 +1,71 @@
import BaseProperty from '../BaseProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import MeshUIBaseElement from '../../elements/MeshUIBaseElement';
/* eslint-enable no-unused-vars */
export default class ChildrenInline extends BaseProperty {
constructor() {
super( 'children', null, false );
/**
*
* @type {Array.<MeshUIBaseElement>}
* @internal
*/
this._uis = [];
}
/* eslint-disable no-unused-vars */
/**
* Update requested when :
* - New child has been added
* - Existing child has been removed
*
* @param element
* @param out
*/
update( element, out ) { /* eslint-enable no-unused-vars */
// this._compute( element );
//
// this._needsProcess = true;
}
/* eslint-disable no-unused-vars */
/**
* Process when :
* - Existing child visibility changed
*
* @param element
*/
process( element ) { /* eslint-enable no-unused-vars */
// this._compute( element );
}
/* eslint-disable no-unused-vars */ _compute( element ) { /* eslint-enable no-unused-vars */
// this._uis = element.children.filter( child => child.visible && child.isUI );
//
// this._inlines = this._uis.filter( child => child.isInline );
}
/**
*
*/
dispose() {
// this._inlines = null;
}
}
@@ -0,0 +1,109 @@
import BaseProperty from '../BaseProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import MeshUIBaseElement from '../../elements/MeshUIBaseElement';
/* eslint-enable no-unused-vars */
export default class ChildrenText extends BaseProperty {
constructor() {
super( 'children', null, false );
/**
*
* @type {Array.<MeshUIBaseElement>}
* @internal
*/
this._uis = [];
/**
*
* @type {Array.<MeshUIBaseElement>}
* @internal
*/
this._inlines = [];
/**
*
* @type {Array.<MeshUIBaseElement>}
* @internal
*/
this._boxes = [];
}
/* eslint-disable no-unused-vars */
/**
* Update requested when :
* - New child has been added
* - Existing child has been removed
*
* @param element
* @param out
*/
update( element, out ) { /* eslint-enable no-unused-vars */
this._compute( element );
this._needsProcess = true;
}
/**
* Process when :
* - Existing child visibility changed
*
* @param element
*/
process( element ) {
// this._compute( element );
element._overflow._needsRender = true;
}
_compute( element ) {
this._uis = element.children.filter( child => child.visible && child.isUI );
this._inlines = this._uis.filter( child => child.isInline ).sort( this._sortOrder );
}
/**
*
*/
dispose() {
this._inlines = null;
}
/**
*
* Sort children according to their .style.order property or fallback on children index
*
* @param {HTMLElementVR} a
* @param {HTMLElementVR} b
* @return {number}
* @private
*/
_sortOrder = ( a, b ) => {
if( a._order._value < b._order._value ) return -1;
if( a._order._value > b._order._value ) return 1;
// if both children have the same order value, use their children index to order them
if( this._uis.indexOf(a) < this._uis.indexOf(b) ) {
return -1;
}
return 1;
}
}
@@ -0,0 +1,102 @@
import BaseProperty from '../BaseProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import { Object3D } from 'three';
/* eslint-enable no-unused-vars */
export default class ParentProperty extends BaseProperty {
constructor() {
super('parent', null, false);
}
/* eslint-disable no-unused-vars */
/**
* Update when :
* - element has been added
* - element has been removed
*
* @param element
* @param out
*/
update( element, out ) {
/* eslint-enable no-unused-vars */
if ( element.parent && element.parent.isUI ) {
this._value = element.parent;
// this.position.z = this.getOffset();
} else {
this._value = null;
}
// @TODO : parentElement
// // set elements as root
// if ( element.isBlock && !this._value ) {
//
// ThreeMeshUI.addRoot( element );
// element.pseudoClassList.add('root');
//
// } else {
//
// ThreeMeshUI.removeRoot( element );
// element.pseudoClassList.remove('root');
//
// }
}
set value( value ) {
console.warn('ParentProperty is readonly');
}
/**
*
* @return {MeshUIBaseElement}
*/
get value() { return this._value; }
/**
*
* @param {(p:Object3D)=>boolean } conditionCallback
*/
find( conditionCallback ) {
if( this._value ) {
if( conditionCallback( this._value) ) {
return this._value;
}
return this._value._parent.find( conditionCallback );
}
return null;
}
/**
*
*/
dispose() {
this._value = null;
}
}
@@ -0,0 +1,25 @@
import BaseProperty from '../BaseProperty';
import Frame from '../../../frame/Frame';
export default class RendererPropertyBox extends BaseProperty{
constructor() {
super( 'renderer' );
}
render( element ) {
if( !element._backgroundMesh ) {
element.setBackgroundMesh( new Frame(element) );
}
element.performAfterUpdate();
}
}
@@ -0,0 +1,35 @@
import BaseProperty from '../BaseProperty';
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils';
import { Mesh } from 'three';
export default class RendererPropertyInline extends BaseProperty {
constructor() {
super('renderer');
}
render(element) {
if (!element._inlines._value || !element._inlines._value.length) return;
const charactersAsGeometries = element._inlines._value.map(
inline =>
element._font._fontVariant.getGeometricGlyph(inline, element)
.translate(inline.offsetX, inline.offsetY, 0)
);
const mergedGeom = mergeGeometries(charactersAsGeometries);
element.setFontMesh(new Mesh(mergedGeom, element.fontMaterial));
element._fontMesh.renderOrder = Infinity;
}
}
@@ -0,0 +1,29 @@
import BaseProperty from '../BaseProperty';
import Frame from '../../../frame/Frame';
export default class RendererPropertyInlineBox extends BaseProperty{
constructor() {
super( 'renderer' );
}
render( element ) {
if( !element._backgroundMesh ) {
element.setBackgroundMesh( new Frame(element) );
}
element._backgroundMesh.position.x = element._inlines._value[0].offsetX + element._inlines._value[0].width/2;
// element._backgroundMesh.position.y = element._inlines._value[0].offsetY + element._inlines._value[0].lineBase/4;
element._backgroundMesh.position.y = element._inlines._value[0].offsetY + element._inlines._value[0].lineBase/2;
element._bounds.render( element );
}
}
@@ -0,0 +1,28 @@
import RendererPropertyBox from './RendererPropertyBox';
export default class RendererPropertyText extends RendererPropertyBox{
constructor() {
super( 'renderer' );
this._needsUpdate = false;
}
render( element ) {
super.render( element );
for ( const inlineElement of element._children._inlines ) {
inlineElement._renderer.render( inlineElement );
}
element.performAfterUpdate();
}
}
@@ -0,0 +1,45 @@
import SubStyleProperty from './SubStyleProperty';
export default class PositionProperty extends SubStyleProperty {
constructor( ) {
super( 'position', 'static', true );
// configure
this._allowsInherit = false;
this._value = 'static';
this._needsUpdate = false;
this.computeOutputValue = this._computeFromInherited;
this.isValidValue = _isValid;
}
_computeFromInherited( element ) {
super._computeFromInherited( element );
//console.log( "Position update")
// require parent to compute children -> bounds -> etc...
if( element._parent._value ) element._parent._value._children._needsProcess = true;
}
}
const AVAILABLE_VALUES = ['static', 'absolute'];
function _isValid( value ) {
if( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn( `(.style) position value '${value}' is not valid. Aborted` );
return false;
}
return true;
}
@@ -0,0 +1,19 @@
import PositionProperty from './PositionProperty';
export default class PositionPropertyBox extends PositionProperty {
constructor( ) {
super( 'position');
}
update( element, out ) {
super.update( element, out );
this._needsProcess = true;
}
}
@@ -0,0 +1,50 @@
import SubStyleProperty from './SubStyleProperty';
import { Color } from 'three';
//JSDoc related imports
/* eslint-disable no-unused-vars */
/* eslint-enable no-unused-vars */
export default class StyleColorProperty extends SubStyleProperty {
constructor( propertyId, defaultValue ) {
super( propertyId, defaultValue, false );
/**
* @type {Color}
* @protected
*/
this._value = new Color();
this.output = this._outputValue;
}
/* eslint-disable no-unused-vars */
/**
* @override
*/
computeOutputValue( element ) { /* eslint-enable no-unused-vars */
if( this._input !== 'inherit' ) {
this._value.set(this._input);
}
}
set inline( value ) {
// Colors are too wide to perform validation checks each time
// if( ! this.isValidValue( value ) ) return;
this._input = this._inline = value;
this._needsUpdate = true;
}
}
@@ -0,0 +1,21 @@
import SubStyleProperty from './SubStyleProperty';
export default class StyleEmptyProperty extends SubStyleProperty {
/**
*
* @param {string} propertyId
*/
constructor( propertyId = 'untitled' ) {
super( propertyId , undefined, false);
this._needsUpdate = false;
}
/* eslint-disable no-unused-vars */ output( out ) {} /* eslint-enable no-unused-vars */
}
@@ -0,0 +1,58 @@
import SubStyleProperty from './SubStyleProperty';
export default class StyleFactorProperty extends SubStyleProperty {
/**
*
* @param {string} propertyId
* @param {any} defaultValue
*/
constructor( propertyId, defaultValue ) {
super( propertyId, defaultValue, true );
this.isValidValue = _isValid;
this._allowsInherit = false;
this._input = defaultValue;
this._value = defaultValue;
this.output = this._outputValue;
this.computeOutputValue = this._computeFromInherited;
}
_outputValue( out ) {
out[this._id] = this._inheritedInput;
}
}
/**
*
* @param {any} value
* @return {boolean}
* @private
*/
function _isValid( value ) {
if ( value < 0 && value > 1.0 ) {
console.warn( `(.style) styleFactorProperty('${this.id}') value '${value}' is not valid)` );
return false;
}
return true;
}
@@ -0,0 +1,25 @@
import SubStyleProperty from './SubStyleProperty';
export default class StyleNumberProperty extends SubStyleProperty {
/**
*
* @param {string} propertyId
* @param {any} defaultValue
*/
constructor( propertyId, defaultValue ) {
super( propertyId, defaultValue );
}
/**
* @override
* @return {Number}
*/
get value() { return this._value; }
}
@@ -0,0 +1,213 @@
import { Vector4 } from 'three';
import SubStyleProperty from './SubStyleProperty';
export default class StyleVector4Property extends SubStyleProperty {
constructor( propertyId, defaultValue ) {
super( propertyId, defaultValue, false );
/**
*
* @type {Vector4}
* @private
*/
this._input = new Vector4(0,0,0,0);
/**
*
* @type {any}
* @internal
*/
this._inline = null;
/**
* @override
* @type {Vector4}
* @protected
*/
this._value = new Vector4(0,0,0,0);
}
/**
* @override
* @return {Vector4}
*/
get value(){
return this._value;
}
/* eslint-disable no-unused-vars */
/**
* @override
*/
computeOutputValue( element ) { /* eslint-enable no-unused-vars */
this._vector4ValueSetter( this._value, this._input );
}
set inline( value ) {
this._vector4ValueSetter( this._input, value );
if( this._input.equals( this._value) ) return;
this._needsUpdate = true;
}
/**
*
* @param {Number} v
*/
set top( v ) {
if( this._input.x === v ) return;
this._input.x = v;
this._needsUpdate = true;
}
/**
*
* @returns {number}
*/
get top() { return this._input.x; }
/**
*
* @param {Number} v
*/
set right( v ) {
if( this._input.y === v ) return;
this._input.y = v;
this._needsUpdate = true;
}
/**
*
* @returns {number}
*/
get right() { return this._input.y; }
/**
*
* @param {Number} v
*/
set bottom( v ) {
if( this._input.z === v ) return;
this._input.z = v;
this._needsUpdate = true;
}
/**
*
* @returns {number}
*/
get bottom() { return this._input.z; }
/**
*
* @param {Number} v
*/
set left( v ) {
if( this._input.w === v ) return;
this._input.w = v;
this._needsUpdate = true;
}
/**
*
* @returns {number}
*/
get left() { return this._input.w; }
dispose(){
this._computed = null;
this._inline = null;
this._input = null;
this._output = null;
}
/**
*
* @param {Vector4} vector4
* @param {Vector4|Array.<Number>|Number|String} value
* @protected
*/
_vector4ValueSetter( vector4, value ) {
if ( value instanceof Vector4 ) {
vector4.copy( value );
return;
}
if ( typeof value === 'string' || value instanceof String ) {
value = value.split( ' ' );
}
if ( Array.isArray( value ) ) {
value = value.map( v => parseFloat( v ) );
switch ( value.length ) {
case 1:
vector4.setScalar( value[ 0 ] );
return;
case 2:
vector4.x = vector4.z = value[ 0 ];
vector4.y = vector4.w = value[ 1 ];
return;
case 3:
vector4.x = value[ 0 ];
vector4.y = value[ 1 ];
vector4.z = value[ 2 ];
return;
case 4:
vector4.x = value[ 0 ];
vector4.y = value[ 1 ];
vector4.z = value[ 2 ];
vector4.w = value[ 3 ];
return;
default:
console.error( 'StyleVector4Property::set() Four Dimension property had more than four values' );
return;
}
}
if ( !isNaN( value ) ) {
vector4.setScalar( value );
}
}
}
@@ -0,0 +1,168 @@
import BaseProperty from '../BaseProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import MeshUIBaseElement from '../../elements/MeshUIBaseElement';
/* eslint-enable no-unused-vars */
/* eslint-disable no-unused-vars */
export default class SubStyleProperty extends BaseProperty{
/**
*
* @param {string} propertyId
* @param {boolean} [primitive=true]
* @param {any} defaultValue
*/
constructor( propertyId, defaultValue, primitive = true) {
super( propertyId, 'unset', primitive );
/**
* @type {any}
* @internal
*/
this._input = 'inherit';
/**
*
* @type {boolean}
* @protected
*/
this._allowsInherit = true;
/**
* The input value that won't be 'inherit'
* @type {any}
* @protected
*/
this._inheritedInput = undefined;
/**
*
* @type {any}
* @internal
*/
this._inline = undefined;
}
/**
*
* @param {MeshUIBaseElement} element
* @param {Object.<string,any> } out
*/
update( element, out ) {
if( !this._allowsInherit ) {
this._inheritedInput = this.getInheritedInput( element );
}
this.computeOutputValue( element );
// rebuild same properties on children 'inheritance'
for ( const childUIElement of element._children._uis ) {
const property = childUIElement[`_${this._id}`];
const target = property._input ? property._input : property._value;
if( target === 'inherit' ) childUIElement[`_${this._id}`]._needsUpdate = true;
}
this.output( out );
}
/**
*
* @param {MeshUIBaseElement} element
*/
computeOutputValue( element ) {
this._value = this._input;
}
/**
*
* @param {MeshUIBaseElement} element
*/
_computeFromInherited( element ) {
this._value = this._inheritedInput;
}
/**
* @override
* @deprecated
* @param {any} v
*/
set value( v ) {
console.warn(".(style) sub-property cannot be directly set. It must comes from inline or computed setter.")
}
/**
*
* @param {any} value
*/
set inline( value ) {
if( ! this.isValidValue( value ) ) return;
if( value === this._inline ) {
// do nothing no update, the value hasn't changed
return;
}
this._input = this._inline = value;
this._needsUpdate = true;
}
/**
*
* @return {any}
*/
get inline() { return this._inline; }
/**
*
* @param {any} value
* @return {boolean}
*/
isValidValue( value ) {
return true;
}
/**
* @param {MeshUIBaseElement} element
*/
getInheritedInput ( element ) {
if( this._input !== 'inherit' ) return this._input;
const parent = element._parent._value;
if( parent ) {
return parent[`_${this._id}`].getInheritedInput( parent )
}
return this.getDefaultValue();
}
}
/* eslint-enable no-unused-vars */
@@ -0,0 +1,39 @@
import StyleColorProperty from '../StyleColorProperty';
export default class BackgroundColorProperty extends StyleColorProperty {
constructor( defaultValue ) {
super( 'backgroundColor', defaultValue, false );
this._input = 'transparent';
this._allowsInherit = false;
}
/* eslint-disable no-unused-vars */
/**
*
* @param {MeshUIBaseElement} element
*/
computeOutputValue( element ) { /* eslint-enable no-unused-vars */
element._backgroundMesh.visible = !(this._input === 'none' || this._input === 'transparent');
if( this._input === 'inherit' ) {
// @TODO: Background should not be inheritable
this._value.set(this.getInheritedInput( element ));
} else if( !(this._input === 'transparent' || this._input === 'none') ) {
this._value.set( this._input );
}
}
}
@@ -0,0 +1,44 @@
import StyleColorProperty from '../StyleColorProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
/* eslint-enable no-unused-vars */
export default class BackgroundColorPropertyInline extends StyleColorProperty {
constructor( defaultValue ) {
super( 'backgroundColor', defaultValue, false );
this._allowsInherit = false;
this._input = 0x000000;
}
/* eslint-disable no-unused-vars */
/**
*
* @param {MeshUIBaseElement} element
*/
computeOutputValue( element ) { /* eslint-enable no-unused-vars */
// @TODO : Changes multiple mesh visibility
// element._backgroundMesh.visible = !(this._input === 'none' || this._input === 'transparent');
if( this._input === 'inherit' ) {
this._value.set(this.getInheritedInput( element ));
} else {
this._value.set( this._input );
}
}
}
@@ -0,0 +1,102 @@
import SubStyleProperty from '../SubStyleProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import { Texture, Vector2 } from 'three';
/* eslint-enable no-unused-vars */
export default class BackgroundImage extends SubStyleProperty {
constructor( defaultValue ) {
super( 'backgroundImage', defaultValue, true );
this._input = null;
// configure
this._allowsInherit = false;
/**
*
* @type {Vector2}
* @internal
*/
this._textureSize = new Vector2( 1,1 );
this.isValidValue = _isValid;
}
/**
* @override
* @return {any|Texture|null}
*/
get value() {
return this._value;
}
output( out ) {
out[this._id] = this._value;
out['tSize'] = this._textureSize;
}
/* eslint-disable no-unused-vars */ computeOutputValue( element ) { /* eslint-enable no-unused-vars */
// @TODO : URL
this._value = this._inheritedInput;
// ?
// out[this.id] = this._value;
if( this._value instanceof Texture && !this._value.image ) {
console.warn( `ThreeMeshUI - .backgroundImage :: Please provide preloaded texture in order to have accurate sizing.`);
return;
}
this._needsProcess = true;
}
/* eslint-disable no-unused-vars */
/**
*
* @param element
*/
process( element ) { /* eslint-enable no-unused-vars */
if( this._value ) {
this._textureSize.set( this._value.image.width, this._value.image.height );
} else {
this._textureSize.set( 1 , 1 );
}
}
}
/* eslint-disable no-unused-vars */
/**
*
* @param {any} value
* @return {boolean}
* @private
*/
function _isValid( value ) { /* eslint-enable no-unused-vars */
// @TODO : Texture or URL() or String or ID ?
//console.log( "todo, validate image value", value);
return true;
}
@@ -0,0 +1,39 @@
import SubStyleProperty from '../SubStyleProperty';
/**
* @property {"cover"|"contain"|"stretch"} value
*/
export default class BackgroundSize extends SubStyleProperty {
constructor( defaultValue ) {
super( 'backgroundSize', defaultValue, true );
this.isValidValue = _isValid;
this.output = this._outputValue;
}
}
const AVAILABLE_VALUES = ['cover','contain','stretch'];
/**
*
* @param {any} value
* @return {boolean}
* @private
*/
function _isValid( value ) {
if( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn( `(.style) backgroundSize value '${value}' is not valid. Aborted` );
return false;
}
return true;
}
@@ -0,0 +1,513 @@
import { Vector2 } from 'three';
import * as Units from '../../../../utils/Units';
import StyleVector4Property from '../StyleVector4Property';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import { Vector4 } from 'three';
/* eslint-enable no-unused-vars */
export default class BorderRadius extends StyleVector4Property {
/**
*
* @param {Vector4} defaultValue
*/
constructor( defaultValue ) {
super( 'borderRadius', defaultValue );
/**
*
* @type {Vector4}
* @private
*/
this._valueUV = this._value.clone();
/**
*
* @type {Vector4}
* @private
*/
this._input = new Vector4(0,0,0,0);
/**
*
* @type {boolean}
* @private
*/
this._mediation = true;
/**
*
* @type {Vector2}
* @private
*/
this._cornerTL = new Vector2(0, 1);
/**
*
* @type {Vector2}
* @private
*/
this._cornerTR = new Vector2(1, 1);
/**
*
* @type {Vector2}
* @private
*/
this._cornerBR = new Vector2(1, 0);
/**
*
* @type {Vector2}
* @private
*/
this._cornerBL = new Vector2(0, 0);
const mediationTop = new BorderRadiusMediator( this._valueUV, [ 'x', 'y' ] ); // bottom
const mediationBottom = new BorderRadiusMediator( this._valueUV, [ 'z', 'w'] ); // top
const mediationLeft = new BorderRadiusMediator( this._valueUV, [ 'x', 'w'] ); // right
const mediationRight = new BorderRadiusMediator( this._valueUV, [ 'y', 'z'] ); // left
mediationTop.complementaryMediation = mediationBottom;
mediationBottom.complementaryMediation = mediationTop;
mediationLeft.complementaryMediation = mediationRight;
mediationRight.complementaryMediation = mediationLeft;
/**
*
* @type {Array.<BorderRadiusMediator>}
* @private
*/
this._sideMediators = [ mediationTop, mediationBottom, mediationLeft, mediationRight ];
this._units = Units.WORLD_UNITS;
}
/**
*
* @param {string} units
*/
set units( units ) {
this._units = Units.validateUnits( units );
this._needsProcess = true;
}
/**
*
* @returns {string}
*/
get units() { return this._units; }
/**
*
* @param {boolean} v
*/
set mediation ( v) {
if( v !== this._mediation ){
this._mediation = v;
this._needsUpdate = true;
}
}
/**
*
* @returns {boolean}
*/
get mediation () { return this._mediation; }
/**
*
* @param {Object.<string,any>} out
*/
output( out ) {
out["cornerTL"] = this._cornerTL;
out["cornerTR"] = this._cornerTR;
out["cornerBR"] = this._cornerBR;
out["cornerBL"] = this._cornerBL;
}
/**
*
* @override
*/
/* eslint-disable no-unused-vars */ computeOutputValue( element ) { /* eslint-enable no-unused-vars */
this._vector4ValueSetter( this._value, this._input );
this._needsProcess = true;
}
/* eslint-disable no-unused-vars */
/**
*
* @override
*/
process( element ){ /* eslint-enable no-unused-vars */
this._needsRender = true;
}
/**
*
* @override
*/
render( element ){
this._valueUV.copy( this._value );
const elementWidth = element._bounds._offsetWidth;
const elementHeight = element._bounds._offsetHeight;
// @TODO: Units process could be strategies
if( this._units === Units.PERCENT ) {
this._valueUV.divideScalar(100);
}
// @TODO: Units process could be strategies
if( this._units === Units.WORLD_UNITS ) {
this._valueUV.divideScalar( Math.min(elementWidth , elementHeight) );
}
/**
* mediate
* Be sure no side is greater than 1 (uv units)
*/
if( this._mediation ) {
do {
this._sideMediators.forEach( srm => srm.computeValue() );
this._sideMediators.sort( ( a, b ) => {
return a.value < b.value ? 1 : -1
} );
if ( this._sideMediators[ 0 ].value > 1.0 ) {
this._sideMediators[ 0 ].mediate();
}
} while ( this._sideMediators[ 0 ].value > 1.0 );
}
let sX = elementWidth > elementHeight ? elementHeight/elementWidth : 1;
let sY = elementWidth < elementHeight ? elementWidth/elementHeight : 1;
// Do not scale pourcentages, allowing ovals
if( this._units === Units.PERCENT ){
sX = sY = 1.0;
}
this._cornerTL.x = this._valueUV.x * sX;
this._cornerTL.y = 1 - (this._valueUV.x * sY );
this._cornerTR.x = 1 - (this._valueUV.y * sX );
this._cornerTR.y = 1 - (this._valueUV.y * sY );
this._cornerBR.x = 1 - (this._valueUV.z * sX );
this._cornerBR.y = this._valueUV.z * sY;
this._cornerBL.x = this._valueUV.w * sX;
this._cornerBL.y = this._valueUV.w * sY;
}
/**
*
*/
dispose() {
for ( const sideMediator of this._sideMediators ) {
sideMediator.dispose();
}
this._sideMediators = null;
this._cornerTL = null;
this._cornerTR = null;
this._cornerBR = null;
this._cornerBL = null;
super.dispose();
}
/**
*
* @param {Number} v
*/
set topLeft( v ) {
if( this._input.x === v ) return;
this._input.x = v;
this._needsUpdate = true;
}
/**
*
* @returns {number}
*/
get topLeft() { return this._input.x; }
/**
*
* @param {Number} v
*/
set topRight( v ) {
if( this._input.y === v ) return;
this._input.y = v;
this._needsUpdate = true;
}
/**
*
* @returns {number}
*/
get topRight() { return this._input.y; }
/**
*
* @param {Number} v
*/
set bottomRight( v ) {
if( this._input.z === v ) return;
this._input.z = v;
this._needsUpdate = true;
}
/**
*
* @returns {number}
*/
get bottomRight() { return this._input.z; }
/**
*
* @param {Number} v
*/
set bottomLeft( v ) {
if( this._input.w === v ) return;
this._input.w = v;
this._needsUpdate = true;
}
/**
*
* @returns {number}
*/
get bottomLeft() { return this._input.w; }
/**
* @override
* @param {Number} v
*/
set top( v ) {
if( this._input.x === v && this._input.y === v ) return;
this._input.x = this._input.y = v;
this._needsUpdate = true;
}
/**
* @override
* @returns {number}
*/
get top() { return (this._input.x + this._input.y) / 2; }
/**
* @override
* @param {Number} v
*/
set right( v ) {
if( this._input.y === v && this._input.z === v ) return;
this._input.y = this._input.z = v;
this._needsUpdate = true;
}
/**
* @override
* @returns {number}
*/
get right() { return (this._input.y + this._input.z) / 2; }
/**
* @override
* @param {Number} v
*/
set bottom( v ) {
if( this._input.z === v && this._input.w === v ) return;
this._input.z = this._input.w = v;
this._needsUpdate = true;
}
/**
* @override
* @returns {number}
*/
get bottom() { return (this._input.z + this._input.w) / 2; }
/**
* @override
* @param {Number} v
*/
set left( v ) {
if( this._input.w === v && this._input.x === v ) return;
this._input.w = this._input.x = v;
this._needsUpdate = true;
}
/**
* @override
* @returns {number}
*/
get left() { return (this._input.w + this._input.x) / 2; }
}
/**
* Job: Contains two border radiuses values of the same side
* If their sums is greater than 1 (uv units) mediation could occurs
*/
class BorderRadiusMediator {
/**
*
* @param {Vector4} borderRadiuses
* @param {Array.<string>} sideProperties
*/
constructor( borderRadiuses, sideProperties ) {
/**
*
* @type {Vector4}
* @private
*/
this._borderRadiuses = borderRadiuses;
/**
*
* @type {Array<string>}
* @private
*/
this._sideProperties = sideProperties;
/**
*
* @type {BorderRadiusMediator|null}
* @private
*/
this._complementaryMediation = null;
/**
*
* @type {number}
* @private
*/
this._value = 0;
}
/**
* The sum of the border radius of that side
* @returns {number}
*/
get value(){ return this._value; }
/**
* A complementary side mediation ie: For top, complementary is bottom
* @param {BorderRadiusMediator} brm
*/
set complementaryMediation( brm ){
this._complementaryMediation = brm;
}
/**
* Adds all side property to compute the value of that side
*/
computeValue(){
let totalRadius = 0;
for ( const radius of this._sideProperties ) {
totalRadius += this._borderRadiuses[radius];
}
this._value = totalRadius;
}
/**
*
* @param {boolean} [mediateOpposite=true]
*/
mediate( mediateOpposite = true ){
// Mediation only occurs when sum of radius are greater than 1 (uv units)
if( this._value < 1.0 ) return;
// Simply divide each component by the sum
for ( const radius of this._sideProperties ) {
this._borderRadiuses[radius] /= this._value;
}
if( mediateOpposite ) {
// and also mediate the complementary
this._complementaryMediation.mediate( false );
}
}
/**
*
*/
dispose() {
this._complementaryMediation = null;
this._borderRadiuses = null;
}
}
@@ -0,0 +1,131 @@
import * as Units from '../../../../utils/Units';
import StyleVector4Property from '../StyleVector4Property';
export default class BorderWidth extends StyleVector4Property{
/**
*
* @param defaultValue
*/
constructor( defaultValue ) {
super ( 'borderWidth', defaultValue, false );
this._valueUV = this._value.clone();
// configure
this.output = this._outputValue;
this._units = Units.WORLD_UNITS;
}
/**
*
* @param {string} units
*/
set units( units ) {
this._units = Units.validateUnits( units );
this._needsUpdate = true;
}
/**
*
* @returns {string}
*/
get units() { return this._units; }
/* eslint-disable no-unused-vars */
/**
*
* @override
*/
computeOutputValue( element) { /* eslint-enable no-unused-vars */
this._vector4ValueSetter( this._value, this._input );
this._needsProcess = true;
element._bounds._needsUpdate = true;
element._layouter._needsUpdate = true;
// for ( let i = 0; i < element._children._uis.length; i++ ) {
// const child = element._children._uis[ i ];
// element._bounds._needsUpdate = true;
// }
}
_outputValue( out ) {
out[this._id] = this._valueUV;
}
/**
*
* @override
*/
process( element ) {
this._needsRender = true;
element._borderRadius._needsRender = true;
}
/**
* @override
*/
render( element ) {
this._valueUV.copy( this._value );
const offsetWidth = element._bounds._offsetWidth;
const offsetHeight = element._bounds._offsetHeight;
// @TODO: Units process could be strategies
if( this._units === Units.PERCENT ){
console.log( "Percent" );
// this._valueUV.divideScalar( 100 );
console.log( this._valueUV );
}
// @TODO: Units process could be strategies
if( this._units === Units.WORLD_UNITS ) {
if( offsetWidth !== 0) {
this._valueUV.w /= offsetWidth;
this._valueUV.y /= offsetWidth;
}
if( offsetHeight !== 0 ) {
this._valueUV.x /= offsetHeight;
this._valueUV.z /= offsetHeight;
}
} else if( this._units === Units.UV ) {
// @TODO: Units process could be strategies
if( offsetWidth !== 0 ) {
const sX = offsetWidth > offsetHeight ? offsetHeight/offsetWidth : 1;
this._valueUV.y *= sX;
this._valueUV.w *= sX;
}
if( offsetHeight !== 0 ) {
const sY = offsetWidth < offsetHeight ? offsetWidth/offsetHeight : 1;
this._valueUV.x *= sY;
this._valueUV.z *= sY;
}
}
}
}
@@ -0,0 +1,51 @@
import SubStyleProperty from '../SubStyleProperty';
export default class BoxSizing extends SubStyleProperty {
constructor( defaultValue ) {
super( 'boxSizing', defaultValue );
// Configure
this._allowsInherit = false;
this.isValidValue = _isValid;
}
computeOutputValue( element ) {
this._value = this._inheritedInput;
element._bounds._needsUpdate = true;
}
}
/**
*
* @type {Array.<string>}
*/
const AVAILABLE_VALUES = ['border-box', 'content-box'];
/**
*
* @param {any} value
* @return {boolean}
* @private
*/
function _isValid( value ) {
if( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn( `(.style) boxSizing value '${value}' is not valid. Aborted` );
return false;
}
return true;
}
@@ -0,0 +1,19 @@
import StyleSideProperty from './StyleSideProperty';
export default class HeightProperty extends StyleSideProperty {
constructor() {
super( 'height' );
}
computeOutputValue( element ) {
super.computeOutputValue( element );
}
}
@@ -0,0 +1,23 @@
import StyleVector4Property from '../StyleVector4Property';
import { Vector4 } from 'three';
export default class MarginProperty extends StyleVector4Property {
constructor() {
super('margin', new Vector4(0,0,0,0) )
}
computeOutputValue( element ) {
super.computeOutputValue( element );
element._renderer._needsRender = true;
if( element._parent._value ){
element._parent._value._flexDirection._needsProcess = true;
}
}
}
@@ -0,0 +1,26 @@
import StyleVector4Property from '../StyleVector4Property';
import { Vector4 } from 'three';
export default class PaddingProperty extends StyleVector4Property {
constructor() {
super('padding', new Vector4(0,0,0,0) )
}
computeOutputValue( element ) {
super.computeOutputValue( element );
element._bounds._needsUpdate = true;
element._bounds._needsRender = true;
element._layouter._needsProcess = true;
element._renderer._needsRender = true;
if( element._parent._value ){
element._parent._value._layouter._needsProcess = true;
}
}
}
@@ -0,0 +1,199 @@
import SubStyleProperty from '../SubStyleProperty';
export default class StyleSideProperty extends SubStyleProperty {
/**
*
* @param {string} propertyId
* @param {number} defaultValue
*/
constructor( propertyId, defaultValue = null ) {
super( propertyId, defaultValue, true );
/**
* @type {any}
* @internal
*/
this._input = 'auto';
/**
*
* @type {boolean}
* @internal
*/
this._auto = true;
/**
*
* @type {boolean}
* @internal
*/
this._relative = false;
this._updateRequired = true;
}
/**
*
* @param {any} value
*/
set inline( value ) {
if( ! this.isValidValue( value ) ) return;
if( value === this._inline ) {
// do nothing no update, the value hasn't changed
return;
}
this._inline = value;
if( this._input === this._inline ) return;
this._parseInput();
}
get inline() { return this._inline; }
_parseInput() {
let updateRequired = true;
// Inline has priority if set
if( this._inline !== undefined && this._inline !== 'unset' ) {
this._input = this._inline;
}
// or fallback on computed
else if( this._computed !== undefined ) {
// do not require an update if the value remains
if( this._computed === this._input ) updateRequired = false;
this._input = this._computed;
}
// or fallback on default value
else {
updateRequired = this._input === 'inherit';
}
if( updateRequired ) {
this._auto = !this._input || this._input === 'auto';
if ( !this._auto ) {
// string can be percentages
// console.log( this._input, typeof this._input, this._input.endsWith('%'))
if ( ( typeof this._input === 'string' || this._input instanceof String ) && this._input.endsWith( '%' ) ) {
this._relative = true;
this._value = 0;
const floatValue = parseFloat( this._input.replace( '%', '' ).trim() );
if ( !isNaN( floatValue ) ) {
this._value = floatValue / 100;
}
} else {
this._relative = false;
this._value = this._input;
}
} else {
this._relative = false;
}
this._needsUpdate = this._updateRequired = updateRequired;
}
}
update( element, out ) {
if( this._updateRequired ) {
this._updateRequired = false;
if( !this._allowsInherit ) {
this._inheritedInput = this.getInheritedInput( element );
}
this.computeOutputValue( element );
// rebuild same properties on children 'inheritance'
for ( const childUIElement of element._children._uis ) {
childUIElement[`_${this._id}`]._needsUpdate = true;
}
this.output( out );
if( element._parent._value ) element._parent._value._layouter._needsProcess = true;
}
}
/* eslint-disable no-unused-vars */ computeOutputValue( element ) { /* eslint-enable no-unused-vars */
element._bounds._needsUpdate = true;
element._renderer._needsRender = true;
// element._autoSize._needsProcess = true;
}
getInheritedInput ( element ) {
if( this._input !== 'inherit' && !this._auto ) return this._input;
const parent = element._parent._value;
if( parent ) {
return parent[`_${this._id}`].getInheritedInput( parent )
}
return this.getDefaultValue();
}
getDefaultValue() {
return 0;
}
/**
*
* @return {number}
*/
get value() { return this._value; }
}
@@ -0,0 +1,13 @@
import StyleSideProperty from './StyleSideProperty';
export default class WidthProperty extends StyleSideProperty {
constructor() {
super( 'width' );
}
}
@@ -0,0 +1,38 @@
import SubStyleProperty from '../SubStyleProperty';
/**
*
* @type {Array.<string>}
*/
const AVAILABLE_VALUES = ['start', 'center', 'end', 'stretch'];
/**
*
* @param {any} value
* @return {boolean}
* @private
*/
const _isValid = function ( value ) {
if( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn( `(.style) alignItems value '${value}' is not valid. Aborted` );
return false;
}
return true;
}
export default class AlignItemsProperty extends SubStyleProperty {
constructor() {
super( 'alignItems', 'inherit', true );
this.isValidValue = _isValid;
}
}
@@ -0,0 +1,264 @@
import AlignItemsProperty from './AlignItemsProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import MeshUIBaseElement from './../../../../core/elements/MeshUIBaseElement';
/* eslint-enable no-unused-vars */
export default class AlignItemsPropertyBox extends AlignItemsProperty {
constructor( ) {
super();
// configure this property
this._allowsInherit = false;
this._needsUpdate = true;
// strategies
/**
*
* @type {(element:MeshUIBaseElement, (child:MeshUIBaseElement, parentOffset:number )=> number ) => void }
* @private
*/
this._process = this.emptyStrategyLogic;
/**
*
* @type {(child:MeshUIBaseElement, parentOffset:number )=> number}
* @private
*/
this._childAlign = this.emptyStrategyLogic;
}
/**
*
* @param {MeshUIBaseElement} element
*/
computeOutputValue( element ) {
// Stretch : Current or previous requires a bounds update of children
// if( this._value === 'stretch' || this._input === 'stretch' ) {
//
// for ( let i = 0; i < element._children._boxes.length; i++ ) {
// element._children._boxes[ i ]._bounds._needsProcess = true;
// }
//
// }
this._value = this._inheritedInput;
switch( element._flexDirection._value ) {
case 'row':
case 'row-reverse':
this._process = _processRow;
switch ( this._value ) {
case 'start':
this._childAlign = _alignChildRowStart;
break;
case 'end':
this._childAlign = _alignChildRowEnd;
break;
default:
this._childAlign = _alignChild;
}
break;
case 'column':
case 'column-reverse':
this._process = _processColumn;
switch ( this._value ) {
case 'start':
this._childAlign = _alignChildColumnStart;
break;
case 'end':
this._childAlign = _alignChildColumnEnd;
break;
default:
this._childAlign = _alignChild;
}
break;
}
this._needsProcess = true;
// @TODO: Store children here
element._autoSize._needsProcess = true;
element._flexDirection._needsProcess = true; //not mandatory
element._justifyContent._needsProcess = true;
this._needsProcess = true;
element._fontSize._needsProcess = true;
element._layouter._needsProcess = true;
}
/**
*
* @param element
*/
process( element ) {
// return;
// if( !element._children._boxes.length ) return;
this._process( element, this._childAlign );
// @TODO : Could be strategized
let snap = 'center';
let snapXon = 'center';
let snapYon = 'center';
const padding = element._padding._value;
const border = element._borderWidth._value;
if( element._flexDirection._value.indexOf('column') !== -1 ) {
if( this._value === 'start' ) {
snap = snapXon = 'left';
}else if( this._value === 'end' ){
snap = snapXon ='right';
}else {
snap = 'centerX';
}
} else {
/* eslint-disable no-lonely-if */
if( this._value === 'start' ) {
snap = snapYon = 'top';
}else if( this._value === 'end' ){
snap = snapYon ='bottom';
}else{
snap = 'centerY';
}
/* eslint-enable no-lonely-if */
}
// apply 4 directional padding and borders
let y = -(padding.x - padding.z) / 2 - (border.x - border.z) / 2;
let x = -(padding.y - padding.w) / 2 - ( border.y - border.w ) / 2;
if( snapXon === 'left' ) {
x = (padding.w - padding.y) / 2 + (border.w - border.y) / 2;
} else if( snapXon === 'right' ) {
x = - ( padding.y - padding.w ) / 2 - ( border.y - border.w ) / 2;
}
if( snapYon === 'top' ) {
y = - (padding.x - padding.z) / 2 - (border.x - border.z) / 2;
} else if( snapYon === 'bottom' ) {
y = (padding.z - padding.x) / 2 + (border.z - border.x) / 2;
}
element._children._boxes.forEach( ( child ) => {
let marginX = 0;
let marginY = 0;
// let marginY = ( -child._margin._value.x + child._margin._value.z ) /2;
// let marginY = ( -child._margin._value.x + child._margin._value.z ) /2;
if( snap === 'top' ) {
marginY = - child._margin._value.x;
} else if( snap === 'bottom' ) {
marginY = child._margin._value.z;
} else if( snap === 'left' ) {
marginX = child._margin._value.w;
} else if( snap === 'right' ) {
marginX = - child._margin._value.y;
} else if( snap === 'centerX' ) {
marginX = ( child._margin._value.w - child._margin._value.y ) /2;
} else if( snap === 'centerY' ) {
marginY = ( - child._margin._value.x + child._margin._value.z ) /2;
}
element._layouter._childrenPos[ child.id ].x += x + marginX;
element._layouter._childrenPos[ child.id ].y += y + marginY;
} );
}
}
/***********************************************************************************************************************
* STRATEGIES
**********************************************************************************************************************/
function _alignChild() {
return 0;
}
/**
*
* @param child
* @param parentOffset
* @return {number}
* @private
*/
function _alignChildRowEnd( child, parentOffset ) {
return - parentOffset + ( child._bounds._offsetHeight / 2 );
}
function _alignChildRowStart( child, parentOffset ) {
return parentOffset - ( child._bounds._offsetHeight / 2 );
}
function _alignChildColumnEnd( child, parentOffset ) {
return parentOffset - ( child._bounds._offsetWidth / 2 );
}
function _alignChildColumnStart( child, parentOffset ) {
return - parentOffset + ( child._bounds._offsetWidth / 2 );
}
function _processColumn( element, childAligner ) {
const AXIS_TARGET = element._bounds._innerWidth / 2;
element._children._boxes.forEach( ( child ) => {
element._layouter._childrenPos[ child.id ].x = childAligner( child, AXIS_TARGET );
} );
}
function _processRow( element, childAligner ) {
const AXIS_TARGET = element._bounds._innerHeight / 2;
element._children._boxes.forEach( ( child ) => {
element._layouter._childrenPos[ child.id ].y = childAligner( child, AXIS_TARGET );
} );
}
@@ -0,0 +1,42 @@
import SubStyleProperty from '../SubStyleProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import MeshUIBaseElement from '../../../elements/MeshUIBaseElement';
/* eslint-enable no-unused-vars */
export default class FlexDirectionProperty extends SubStyleProperty {
constructor() {
super( 'flexDirection', 'inherit', true );
// configure
this.isValid = _isValid;
}
}
const AVAILABLE_VALUES = ['row','row-reverse', 'column', 'column-reverse'];
/**
*
* @param {any} value
* @return {boolean}
* @private
*/
function _isValid( value ) {
if( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn( `(.style) flexDirection value '${value}' is not valid. Aborted` );
return false;
}
return true;
}
@@ -0,0 +1,259 @@
import FlexDirectionProperty from './FlexDirectionProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import MeshUIBaseElement from '../../../elements/MeshUIBaseElement';
/* eslint-enable no-unused-vars */
export default class FlexDirectionPropertyBox extends FlexDirectionProperty {
constructor( ) {
super();
// Configure
this._allowsInherit = false;
this._needsUpdate = true;
/**
*
* @type {number}
* @internal
*/
this._offset = 0;
/**
*
* @type {number}
* @internal
*/
this._reverse = 1;
/**
*
* @param { (element:MeshUIBaseElement) => void} element
* @private
*/
this._process = this.emptyStrategyLogic;
}
computeOutputValue( element ) {
this._value = this._inheritedInput;
switch ( this._value ) {
case "row":
this._process = _processRow;
// this._offset = - element._bounds._innerWidth / 2;
break;
case "row-reverse":
this._process = _processRowReverse;
// this._offset = element._bounds._innerWidth / 2;
break;
case "column":
this._process = _processColumn;
// this._offset = element._bounds._innerHeight / 2;
break;
case "column-reverse":
this._process = _processColumnReverse;
// this._offset = - element._bounds._innerHeight / 2;
break;
}
// also update dependencies
if( !element._justifyContent._needsUpdate ) element._justifyContent.computeOutputValue( element );
if( !element._alignItems._needsUpdate ) element._alignItems.computeOutputValue( element );
this._needsProcess = true;
}
process( element ) {
// this will be defined from strategy
//console.log( element.name, 'flexDirection process');
switch ( this._value ) {
case "row":
this._offset = - element._bounds._innerWidth / 2;
break;
case "row-reverse":
this._offset = element._bounds._innerWidth / 2;
break;
case "column":
this._offset = element._bounds._innerHeight / 2;
break;
case "column-reverse":
this._offset = - element._bounds._innerHeight / 2;
break;
}
this._reverse = -Math.sign( this._offset );
if( this._reverse === 0 ) {
this._reverse = 1;
}
this._process( element );
element._justifyContent._needsProcess = true;
element._layouter._needsProcess = true;
}
}
/***********************************************************************************************************************
* STRATEGIES
**********************************************************************************************************************/
function _processRow( element ) {
// end to end children
let accu = element._flexDirection._offset;
const REVERSE = element._flexDirection._reverse;
const boxes = element._children._boxes;
// Refactor reduce into fori in order to get rid of this keyword
for ( let i = 0; i < boxes.length; i++ ) {
/**
*
* @type {MeshUIBaseElement}
*/
const child = boxes[ i ];
const CHILD_ID = child.id;
// @TODO : use getter instead of compute function if possible
const CHILD_SIZE = child._bounds._offsetWidth;
// increase with the left margin before placing the child
accu += child._margin._value.w * REVERSE;
const position = element._layouter._childrenPos[ CHILD_ID ];
position.x = accu + ( CHILD_SIZE / 2 ) * REVERSE;
position.y = 0;
// increase the next child with this child right margin
accu += REVERSE * ( CHILD_SIZE + child._margin._value.y ) ;
}
}
function _processRowReverse( element ) {
// end to end children
let accu = element._flexDirection._offset;
const REVERSE = element._flexDirection._reverse;
const boxes = element._children._boxes;
// Refactor reduce into fori in order to get rid of this keyword
for ( let i = 0; i < boxes.length; i++ ) {
/**
*
* @type {MeshUIBaseElement}
*/
const child = boxes[ i ];
const CHILD_ID = child.id;
// @TODO : use getter instead of compute function if possible
const CHILD_SIZE = child._bounds._offsetWidth;
// decrease with the right margin before placing the child
accu += child._margin._value.y * REVERSE;
const position = element._layouter._childrenPos[ CHILD_ID ];
position.x = accu + ( CHILD_SIZE / 2 ) * REVERSE;
position.y = 0;
// decrease the next child with this child left margin
accu += (CHILD_SIZE + child._margin._value.w) * REVERSE ;
}
}
function _processColumn( element ) {
// end to end children
let accu = element._flexDirection._offset;
const REVERSE = element._flexDirection._reverse;
const boxes = element._children._boxes;
// Refactor reduce into fori in order to get rid of this keyword
for ( let i = 0; i < boxes.length; i++ ) {
const child = boxes[ i ];
const CHILD_ID = child.id;
// @TODO : use getter instead of compute function if possible
const CHILD_SIZE = child._bounds._offsetHeight;
// increase with the top margin before placing the child
accu += child._margin._value.x * REVERSE;
const position = element._layouter._childrenPos[ CHILD_ID ];
position.x = 0;
position.y = accu + ( CHILD_SIZE / 2 ) * REVERSE;
// increase the next child with this child bottom margin
accu += (CHILD_SIZE + child._margin._value.z) * REVERSE ;
}
}
function _processColumnReverse( element ) {
// end to end children
let accu = element._flexDirection._offset;
const REVERSE = element._flexDirection._reverse;
const boxes = element._children._boxes;
// Refactor reduce into fori in order to get rid of this keyword
for ( let i = 0; i < boxes.length; i++ ) {
const child = boxes[ i ];
const CHILD_ID = child.id;
// @TODO : use getter instead of compute function if possible
const CHILD_SIZE = child._bounds._offsetHeight;
// decrease with the bottom margin before placing the child
accu += child._margin._value.z * REVERSE;
const position = element._layouter._childrenPos[ CHILD_ID ];
position.x = 0;
position.y = accu + ( CHILD_SIZE / 2 ) * REVERSE;
// decrease the next child with this child top margin
accu += ( CHILD_SIZE + child._margin._value.x ) * REVERSE ;
}
}
@@ -0,0 +1,29 @@
import FlexDirectionProperty from './FlexDirectionProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import MeshUIBaseElement from '../../../elements/MeshUIBaseElement';
/* eslint-enable no-unused-vars */
export default class FlexDirectionPropertyText extends FlexDirectionProperty {
constructor( ) {
super();
this._value = this._input = 'column';
// Configure
this._allowsInherit = false;
this._needsUpdate = true;
}
/* eslint-disable no-unused-vars */computeOutputValue( element ) { /* eslint-enable no-unused-vars */
// @TODO : Evaluate the needs of this property. Could be empty
this._value = this._inheritedInput;
}
}
@@ -0,0 +1,43 @@
import SubStyleProperty from '../SubStyleProperty';
export default class JustifyContentProperty extends SubStyleProperty {
constructor() {
super( 'justifyContent', 'inherit', true );
/**
*
* @override
*/
this.isValidValue = _isValid;
}
}
/**
*
* @type {Array.<string>}
*/
const AVAILABLE_VALUES = [ 'start', 'center', 'end', 'space-between', 'space-around', 'space-evenly' ];
/**
*
* @param {any} value
* @return {boolean}
* @private
*/
function _isValid( value ) {
if ( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn( `(.style) justifyContent value '${value}' is not valid. Aborted` );
return false;
}
return true;
}
@@ -0,0 +1,284 @@
import JustifyContentProperty from './JustifyContentProperty';
export default class JustifyContentPropertyBox extends JustifyContentProperty {
constructor( defaultValue ) {
super( 'justifyContent', defaultValue, true );
// configure
this._allowsInherit = false;
this._needsUpdate = true;
// strategies
/**
*
* @type {(axisOffset:number) => number}
* @private
*/
this._computeOffset = this.emptyStrategyLogic;
/**
*
* @type {(element:MeshUIBaseElement, availableSpace:number, reverse:number) => Array.<number> }
* @private
*/
this._computeMargin = this.emptyStrategyLogic;
/**
*
* @type {(element:MeshUIBaseElement) => void}
* @private
*/
this._process = this.emptyStrategyLogic;
}
computeOutputValue( element ) {
this._value = this._inheritedInput;
//console.log( element._flexDirection._value );
switch ( element._flexDirection._value ) {
case 'column-reverse':
case 'column':
this._process = _column.bind( this );
break;
case 'row-reverse':
case 'row':
this._process = _row.bind( this );
break;
}
switch ( this._value ) {
case 'end':
this._computeOffset = _justificationOffsetEnd;
this._computeMargin = _justificationMargin;
break;
case 'center':
this._computeOffset = _justificationOffsetCenter;
this._computeMargin = _justificationMargin;
break;
case 'start':
this._computeOffset = _justificationOffset;
this._computeMargin = _justificationMargin;
break;
case 'space-between':
this._computeOffset = _justificationOffset;
this._computeMargin = _justificationMarginSpaceBetween;
break;
case 'space-around':
this._computeOffset = _justificationOffset;
this._computeMargin = _justificationMarginSpaceAround;
break;
case 'space-evenly':
this._computeOffset = _justificationOffset;
this._computeMargin = _justificationMarginSpaceEvenly;
break;
}
// @TODO : If flexDirection was keeping its children position,
// it won't be necessary to compute the same result again
// but it will increase the memory footprint
element._flexDirection._needsProcess = true;
}
process( element ) {
this._process( element );
element._alignItems._needsProcess = true; // not mandatory : Layout could sum each
}
}
function _row( element ) {
const startPos = element._flexDirection._offset;
const { usedDirectionSpace, remainingSpace } = _rowRemainingSpace( element );
// Items Offset
const axisOffset = ( startPos * 2 ) - ( usedDirectionSpace * Math.sign( startPos ) );
const justificationOffset = this._computeOffset( axisOffset );
// Items margin
// const justificationMargins = _getJustificationMargin( boxComponent.childrenBoxes, remainingSpace, JUSTIFICATION, REVERSE );
const justificationMargins = this._computeMargin( element, remainingSpace, element._flexDirection._reverse );
// Apply
element._children._boxes.forEach( ( child, childIndex ) => {
element._layouter._childrenPos[ child.id ].x -= justificationOffset - justificationMargins[ childIndex ];
} );
}
function _column( element ) {
const startPos = element._flexDirection._offset;
const { usedDirectionSpace, remainingSpace } = _columnRemainingSpace( element );
// Items Offset
const axisOffset = ( startPos * 2 ) - ( usedDirectionSpace * Math.sign( startPos ) );
const justificationOffset = this._computeOffset( axisOffset );
// Items margin
const justificationMargins = this._computeMargin( element, remainingSpace, element._flexDirection._reverse );
// Apply
element._children._boxes.forEach( ( child, childIndex ) => {
element._layouter._childrenPos[ child.id ].y -= justificationOffset - justificationMargins[ childIndex ];
} );
}
/***********************************************************************************************************************
* STRATEGIES
**********************************************************************************************************************/
/**
*
* @param {MeshUIBaseElement} element
* @return {{usedDirectionSpace: *, remainingSpace: number}}
* @private
*/
function _rowRemainingSpace( element ) {
const usedDirectionSpace = element._bounds._computeChildrenSideWidth( element );
return { usedDirectionSpace, remainingSpace: element._bounds._innerWidth - usedDirectionSpace };
}
function _columnRemainingSpace( element ) {
const usedDirectionSpace = element._bounds._computeChildrenSideHeight( element );
return { usedDirectionSpace, remainingSpace: element._bounds._innerHeight - usedDirectionSpace };
}
/* eslint-disable no-unused-vars */ function _justificationOffset( axisOffset ) { /* eslint-enable no-unused-vars */
return 0;
}
function _justificationOffsetEnd( axisOffset ) {
return axisOffset;
}
function _justificationOffsetCenter( axisOffset ) {
return axisOffset / 2;
}
/* eslint-disable no-unused-vars */
function _justificationMargin( element, availableSpace = 0, reverse = 1 ) { /* eslint-enable no-unused-vars */
return Array( element._children._boxes.length ).fill( 0 );
}
function _justificationMarginSpaceBetween( element, availableSpace = 0, reverse = 1 ) {
const boxes = element._children._boxes;
const length = boxes.length;
const justificationMargins = Array( length ).fill( 0 );
if ( availableSpace > 0 ) {
// only one children would act as start
if ( length > 1 ) {
const margin = availableSpace / ( length - 1 ) * reverse;
// set this margin for any children
// except for first child
justificationMargins[ 0 ] = 0;
for ( let i = 1; i < length; i++ ) {
justificationMargins[ i ] = margin * i;
}
}
}
return justificationMargins;
}
function _justificationMarginSpaceEvenly( element, availableSpace = 0, reverse = 1 ) {
const boxes = element._children._boxes;
const length = boxes.length;
const justificationMargins = Array( length ).fill( 0 );
if ( availableSpace > 0 ) {
const margin = availableSpace / ( length + 1 ) * reverse;
// set this margin for any children
for ( let i = 0; i < length; i++ ) {
justificationMargins[ i ] = margin * ( i + 1 );
}
}
return justificationMargins;
}
function _justificationMarginSpaceAround( element, availableSpace = 0, reverse = 1 ) {
const boxes = element._children._boxes;
const length = boxes.length;
const justificationMargins = Array( length ).fill( 0 );
if ( availableSpace > 0 ) {
const margin = availableSpace / ( length ) * reverse;
const start = margin / 2;
justificationMargins[ 0 ] = start;
// set this margin for any children
for ( let i = 1; i < length; i++ ) {
justificationMargins[ i ] = start + margin * i;
}
}
return justificationMargins;
}
@@ -0,0 +1,32 @@
import SubStyleProperty from '../SubStyleProperty';
export default class OrderProperty extends SubStyleProperty {
constructor( ) {
super( 'order', 0, true );
this._value = 0;
this._input = 0;
// configure
this._allowsInherit = false;
}
/* eslint-disable no-unused-vars */computeOutputValue( element ) { /* eslint-enable no-unused-vars */
this._value = this._inheritedInput;
// require parent children (order) update, which will require layout update
if( element._parent._value ) {
element._parent._value._children._needsProcess = true;
}
}
}
@@ -0,0 +1,37 @@
import StyleColorProperty from '../StyleColorProperty';
//JSDoc related imports
/* eslint-disable no-unused-vars */
import { Color } from 'three';
/* eslint-enable no-unused-vars */
export default class ColorProperty extends StyleColorProperty {
constructor( ) {
super( 'color', 'inherit', false );
this.output = this._outputValue;
}
/* eslint-disable no-unused-vars */
/**
*
* @param {MeshUIBaseElement} element
*/
computeOutputValue( element ) { /* eslint-enable no-unused-vars */
if( this._input === 'inherit' ) {
this._value.set( this.getInheritedInput( element ) );
} else {
this._value.set( this._input);
}
}
}
@@ -0,0 +1,75 @@
import SubStyleProperty from '../SubStyleProperty';
import FontLibrary from '../../../../font/FontLibrary';
import FontFamily from '../../../../font/FontFamily';
export default class FontFamilyProperty extends SubStyleProperty {
constructor( ) {
super( 'fontFamily', 'inherit' , true );
}
/* eslint-disable no-unused-vars */
/**
*
* @param element
*/
computeOutputValue( element ) { /* eslint-enable no-unused-vars */
if( this._input instanceof FontFamily ) {
this._value = this._input;
} else if ( this._input === 'inherit' ) {
// do nothing
} else if ( typeof this._input === 'string' ) {
// string - family
const fontFamily = FontLibrary.getFontFamily( this._input );
if( fontFamily ) {
this._value = fontFamily;
} else {
console.warn( `(.style) fontFamily, the font '${this._input}' is not registered. Aborted.`)
}
} else {
console.warn( `(.style) fontFamily requires a registered fontFamily instance, or the id of a registered fontFamily.`);
console.warn( `If you want to set a specific font, please use .font property instead.`);
}
}
/**
* @override
* @return {any|FontFamily|null}
*/
get value() { return this._value; }
getInheritedInput ( element ) {
if( this._input !== 'inherit' ) return this._input;
const parent = element._parent._value;
if( parent ) {
return parent[`_${this._id}`].getInheritedInput( parent )
}
return this.getDefaultValue();
}
}
@@ -0,0 +1,69 @@
import FontLibrary from '../../../../font/FontLibrary';
import { default as RegisteredFontFamily } from '../../../../font/FontFamily';
import FontFamilyProperty from './FontFamilyProperty';
export default class FontFamilyPropertyInline extends FontFamilyProperty {
constructor( ) {
super( 'fontFamily', 'inherit' , true );
this._input = 'inherit';
this._needsUpdate = true;
// configure
this._allowsInherit = false;
}
/* eslint-disable no-unused-vars */
/**
*
* @param element
*/
computeOutputValue( element ) { /* eslint-enable no-unused-vars */
let abstractedInput = this._inheritedInput;
if( abstractedInput === 'inherit' ) {
abstractedInput = this.getInheritedInput( element );
}
if( abstractedInput instanceof RegisteredFontFamily ) {
this._value = abstractedInput;
element._font._needsUpdate = true;
} else if ( typeof abstractedInput === 'string' ) {
// string - family
const fontFamily = FontLibrary.getFontFamily(abstractedInput);
if( fontFamily ) {
this._value = fontFamily;
element._font._needsUpdate = true;
} else {
console.warn( `(.style) fontFamily, the font '${abstractedInput}' is not registered. Aborted.`)
}
} else {
console.warn( `(.style) fontFamily requires a registered fontFamily instance, or the id of a registered fontFamily.`);
console.warn( `If you want to set a specific font, please use .font property instead.`);
}
}
/**
* @override
* @return {any|FontFamilyPropertyInline|null}
*/
get value() { return this._value; }
}
@@ -0,0 +1,30 @@
import SubStyleProperty from '../SubStyleProperty';
export default class FontKerningProperty extends SubStyleProperty {
constructor() {
super( 'fontKerning', 'inherit' );
this.isValidValue = _isValid;
}
}
const AVAILABLE_VALUES = ['normal', 'none', 'inherit'];
function _isValid( value ) {
if( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn( `(.style) fontKerning value '${value}' is not valid. Aborted` );
return false;
}
return true;
}
@@ -0,0 +1,45 @@
import FontKerningProperty from './FontKerningProperty';
export default class FontKerningPropertyInline extends FontKerningProperty {
constructor() {
super();
// Configure
this._allowsInherit = false;
this.computeOutputValue = this._computeFromInherited;
}
_computeFromInherited( element ) {
super._computeFromInherited(element);
// this._needsProcess = true;
element._parent._value._layouter._needsProcess = false;
}
process( element ) {
// apply kerning on inlines
if ( this._value !== 'none' ) {
// First character won't be kerned with its void lefthanded peer
const whiteSpacedContent = element._whiteSpace._whiteSpacedContent;
const inlines = element._inlines._value;
for ( let i = 1; i < inlines.length; i++ ) {
const glyphPair = whiteSpacedContent[ i - 1 ] + whiteSpacedContent[ i ];
// retrieve the kerning from the font
// as set it on the characterInline
inlines[ i ].kerning = element._font._fontVariant.getKerningAmount( glyphPair );
}
}
}
}
@@ -0,0 +1,25 @@
import FontKerningProperty from './FontKerningProperty';
export default class FontKerningPropertyText extends FontKerningProperty {
constructor() {
super();
this._value = this._input = this.getDefaultValue();
// Configure
this._allowsInherit = false;
this.computeOutputValue = this._computeFromInherited;
}
_computeFromInherited( element ) {
super._computeFromInherited(element);
}
}
@@ -0,0 +1,13 @@
import SubStyleProperty from '../SubStyleProperty';
export default class FontSizeProperty extends SubStyleProperty {
constructor() {
super( 'fontSize', 'inherit', true );
}
}
@@ -0,0 +1,58 @@
import SubStyleProperty from '../SubStyleProperty';
export default class FontSizePropertyInline extends SubStyleProperty {
constructor( ) {
super( 'fontSize', 'inherit', true );
// Configure
this._allowsInherit = false;
}
computeOutputValue( element ) {
this._value = this._inheritedInput;
if( element._font._fontVariant ) {
element._bounds._needsProcess = true;
element._layouter._needsProcess = true;
}
}
process( element ) {
if( !element._font._fontVariant || !element._font._fontVariant.isReady ) return;
const SCALE_MULT = this._value / element._font._fontVariant.typographic.size;
// First character won't be kerned with its void lefthanded peer
const inlines = element._inlines._value;
// update inlines properties before inline placements in lines
for ( let i = 0; i < inlines.length; i++ ) {
const inline = inlines[ i ];
inline.resetOffsets();
inline.fontSize = this._value;
inline.fontFactor = SCALE_MULT;
}
// element._layouter._needsProcess = true;
}
/**
*
* @return {number}
*/
get value() { return this._value; }
}
@@ -0,0 +1,29 @@
import SubStyleProperty from '../SubStyleProperty';
export default class FontStyleProperty extends SubStyleProperty {
constructor( defaultValue ) {
super( 'fontStyle', defaultValue, true );
this.isValidValue = _isValid;
}
}
const AVAILABLE_VALUES = ['normal', 'italic'];
function _isValid( value ) {
if( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn( `(.style) fontStyle value '${value}' is not valid. Aborted` );
return false;
}
return true;
}
@@ -0,0 +1,15 @@
import FontStyleProperty from './FontStyleProperty';
export default class FontStylePropertyInline extends FontStyleProperty {
constructor() {
super();
// configure
this._allowsInherit = false;
this.computeOutputValue = this._computeFromInherited;
}
}
@@ -0,0 +1,27 @@
import SubStyleProperty from '../SubStyleProperty';
export default class FontWeightProperty extends SubStyleProperty {
constructor( ) {
super( 'fontWeight', 'inherit', true );
this.isValid = _isValid;
}
}
const AVAILABLE_VALUES = ['100','200','300','400','500','600','700','800','900','light','normal','bold','bolder'];
function _isValid( value ) {
if( AVAILABLE_VALUES.indexOf( value.toString() ) === -1 ) {
console.warn( `(.style) fontWeight value '${value}' is not valid. Aborted` );
return false;
}
return true;
}
@@ -0,0 +1,18 @@
import FontWeightProperty from './FontWeightProperty';
import { uniformizeFontWeight } from '../../../../font/utils/FontUtils';
export default class FontWeightPropertyInline extends FontWeightProperty {
constructor() {
super();
}
computeOutputValue( element ) {
this._value = uniformizeFontWeight( this.getInheritedInput( element ) );
}
}
@@ -0,0 +1,13 @@
import SubStyleProperty from '../SubStyleProperty';
export default class LetterSpacingProperty extends SubStyleProperty {
constructor() {
super( 'letterSpacing', 'inherit', true );
}
}
@@ -0,0 +1,28 @@
import LetterSpacingProperty from './LetterSpacingProperty';
export default class LetterSpacingPropertyInline extends LetterSpacingProperty {
constructor() {
super();
// configure
this._input = 'inherit';
this._allowsInherit = false;
this.computeOutputValue = this._computeFromInherited;
}
_computeFromInherited( element ) {
super._computeFromInherited( element );
element._fontSize._needsProcess = true;
element._layouter._needsProcess = true;
}
}
@@ -0,0 +1,22 @@
import SubStyleProperty from '../SubStyleProperty';
export default class LineHeightProperty extends SubStyleProperty {
/**
*
*/
constructor() {
super( 'lineHeight', 'inherit', true );
}
update( element, out ) {
super.update( element, out );
element._layouter._needsProcess = true;
}
}
@@ -0,0 +1,21 @@
import LineHeightProperty from './LineHeightProperty';
export default class LineHeightPropertyInline extends LineHeightProperty {
/**
*
*/
constructor() {
super();
// configure
this._allowsInherit = false;
this.computeOutputValue = this._computeFromInherited;
}
}
@@ -0,0 +1,39 @@
import SubStyleProperty from '../SubStyleProperty';
export default class TextAlignProperty extends SubStyleProperty {
constructor() {
super( 'textAlign', 'inherit', true );
this.isValidValue = _isValid;
}
}
/**
*
* @type {Array.<string>}
*/
const AVAILABLE_VALUES = ['left', 'right', 'center', 'justify', 'justify-left', 'justify-right','justify-center'];
/**
*
* @param {any} value
* @return {boolean}
* @private
*/
const _isValid = function ( value ) {
if( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn( `(.style) textAlign value '${value}' is not valid. Aborted` );
return false;
}
return true;
}
@@ -0,0 +1,25 @@
import TextAlignProperty from './TextAlignProperty';
export default class TextAlignPropertyInline extends TextAlignProperty {
constructor() {
super();
// configure
this._allowsInherit = false;
this._needsUpdate = false;
}
/* eslint-disable no-unused-vars */ computeOutputValue( element ) { /* eslint-enable no-unused-vars */
this._value = this._inheritedInput;
element._layouter._needsProcess = true;
}
}
@@ -0,0 +1,168 @@
import TextAlignProperty from './TextAlignProperty';
export default class TextAlignPropertyText extends TextAlignProperty {
constructor() {
super();
// configure
this._allowsInherit = false;
this._needsUpdate = true;
//
// @TODO : strategies
}
/* eslint-disable no-unused-vars */computeOutputValue( element ) { /* eslint-enable no-unused-vars */
this._value = this._inheritedInput;
this._needsProcess = true;
}
process( element ) {
_process( element );
element._renderer._needsRender = true;
}
}
function _process( element ) {
const lines = element._layouter._value;
const ALIGNMENT = element._textAlign._value;
const INNER_WIDTH = element._bounds._innerWidth;
// Start the alignment by sticking to directions : left, right, center
for ( let i = 0; i < lines.length; i++ ) {
const line = lines[ i ];
// compute the alignment offset of the line
const offsetX = _computeLineOffset( element, line, i === lines.length - 1 );
const padding = element._padding._value;
const border = element._borderWidth._value;
// const paddingAmount = - ( padding.w + padding.y ) / 2 - ( border.w + border.y ) / 2;
// const paddingAmount = - ( padding.w + padding.y ) / 2;
const paddingAmount = ( - padding.w + padding.y ) / 2 + ( - border.w + border.y ) / 2;
line.x += offsetX;
// apply the offset to each characters of the line
for ( let j = 0; j < line.length; j++ ) {
line[ j ].offsetX += offsetX - paddingAmount;
// line[ j ].offsetX += offsetX;
}
// line.x = line[ 0 ].offsetX;
}
// last operations for justifications alignments
if ( ALIGNMENT.indexOf( 'justify' ) === 0 ) {
for ( let i = 0; i < lines.length; i++ ) {
const line = lines[ i ];
// do not process last line for justify-left or justify-right
if ( ALIGNMENT.indexOf( '-' ) !== -1 && i === lines.length - 1 ) return;
// can only justify is space is remaining
const REMAINING_SPACE = INNER_WIDTH - line.width;
if ( REMAINING_SPACE <= 0 ) return;
// count the valid spaces to extend
// Do not take the first nor the last space into account
let validSpaces = 0;
for ( let j = 1; j < line.length - 1; j++ ) {
validSpaces += line[ j ].char === ' ' ? 1 : 0;
}
const additionalSpace = REMAINING_SPACE / validSpaces;
// for right justification, process the loop in reverse
let inverter = 1;
if ( ALIGNMENT === 'justify-right' ) {
line.reverse();
inverter = -1;
}
let incrementalOffsetX = 0;
// start at ONE to avoid first space
for ( let j = 1; j <= line.length - 1; j++ ) {
// apply offset on each char
const inlineCharacter = line[ j ];
inlineCharacter.offsetX += incrementalOffsetX * inverter;
// and increase it when space
incrementalOffsetX += inlineCharacter.char === ' ' ? additionalSpace : 0;
}
// for right justification, the loop was processed in reverse
if ( ALIGNMENT === 'justify-right' ) {
line.reverse();
}
}
}
}
function _computeLineOffset ( element, line, lastLine ) {
switch ( element._textAlign._value ) {
case 'justify-left':
case 'justify':
case 'left':
return - element._bounds._innerWidth / 2;
case 'justify-right':
case 'right':
return -line.width + ( element._bounds._innerWidth / 2 );
case 'center':
return -line.width / 2;
case 'justify-center':
if ( lastLine ) {
// center alignement
return -line.width / 2;
}
// left alignment
return - element._bounds._innerWidth / 2;
default:
console.warn( `textAlign: '${element._textAlign._value}' is not valid` );
}
}
@@ -0,0 +1,39 @@
import SubStyleProperty from '../SubStyleProperty';
export default class WhiteSpaceProperty extends SubStyleProperty {
constructor() {
super( 'whiteSpace', 'inherit' );
this.isValidValue = _isValid;
}
}
/**
*
* @type {Array.<string>}
*/
const AVAILABLE_VALUES = ['normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap'];
/**
*
* @param {any} value
* @return {boolean}
* @private
*/
function _isValid( value ) {
if( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn( `(.style) whiteSpace value '${value}' is not valid. Aborted` );
return false;
}
return true;
}
@@ -0,0 +1,467 @@
import WhiteSpaceProperty from './WhiteSpaceProperty';
/**
* @typedef StringCollapserStrategy
* @type {(textContent:{string}) => string}
*/
/**
* @typedef InlineCollapserStrategy
* @type {(line:{Line}) => number }
*/
/**
* @typedef InlineWrapperStrategy
* @type {(inlines:{Array}, i:{number}, lastInlineOffset:{number}, options:Object<string,any>) => boolean}
*/
export default class WhiteSpacePropertyInline extends WhiteSpaceProperty {
constructor() {
super();
// configure
this._allowsInherit = false;
this.computeOutputValue = this._computeFromInherited;
this._whiteSpacedContent = '';
// strategies
/**
*
* @type {StringCollapserStrategy}
* @internal
*/
this._stringCollapser = this.emptyStrategyLogic;
/**
*
* @type {InlineCollapserStrategy}
* @internal
*/
this._inlineCollapser = this.emptyStrategyLogic;
/**
*
* @type {InlineWrapperStrategy}
* @internal
*/
this._inlineWrapper = this.emptyStrategyLogic;
}
/* eslint-disable no-unused-vars */
/**
*
* @param element
* @private
*/
_computeFromInherited( element ) { /* eslint-enable no-unused-vars */
super._computeFromInherited( element );
// set strategies
this._newLineBreakability = _newlineBreakability( this._value );
// REDO Whitespace Matrix
// https://developer.mozilla.org/en-US/docs/Web/CSS/white-space
switch ( this._value ) {
case 'nowrap':
case 'normal':
this._stringCollapser = _stringCollapseNewLine;
break;
case 'pre-line':
this._stringCollapser = _stringCollapseMultipleSpace;
break;
default:
this._stringCollapser = _stringCollapseNothing;
}
switch ( this._value ) {
case 'pre-line':
case 'nowrap':
case 'normal':
this._inlineCollapser = _inlineCollapseMultiple;
break;
case 'pre-wrap':
this._inlineCollapser = _inlineCollapseSingle;
break;
default:
this._inlineCollapser = _inlineCollapseNothing;
}
switch ( this._value ) {
case 'pre-line':
case 'pre-wrap':
case 'normal':
this._inlineWrapper = _lineBreakerWrapText;
break;
case 'pre':
this._inlineWrapper = _lineBreakerLineBreakOnly;
break;
default:
this._inlineWrapper = _lineBreakerNoWrap;
}
this._needsProcess = true;
}
process( element ) {
// @TODO: Make a property for Text -> inlineCollapser
if( element.isInline && !element.isInlineBlock ) {
this._whiteSpacedContent = this._stringCollapser( element._textContent._value );
element._glyphs._needsProcess = true;
}
}
}
/***********************************************************************************************************************
* STRATEGIES
**********************************************************************************************************************/
/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace#whitespace_helper_functions
*
* Throughout, whitespace is defined as one of the characters
* "\t" TAB \u0009
* "\n" LF \u000A
* "\r" CR \u000D
* " " SPC \u0020
*
* This does not use Javascript's "\s" because that includes non-breaking
* spaces (and also some other characters).
**/
const WHITE_CHARS = { '\t': '\u0009', '\n': '\u000A', '\r': '\u000D', ' ': '\u0020' };
/**
* Get the breakability of a newline character according to white-space property
*
* @param whiteSpace
* @returns {string|null}
*/
const _newlineBreakability = function ( whiteSpace ) {
switch ( whiteSpace ) {
case 'pre':
case 'pre-wrap':
case 'pre-line':
return 'mandatory';
}
// case NOWRAP:
// case NORMAL:
// default:
return null;
};
// STRING COLLAPSER -----------------------------------------------------
/**
* Treat newlines as spaces
* @param textContentValue
* @return {*}
* @private
*
*/
function _stringCollapseNewLine( textContentValue ) {
return _stringCollapseMultipleSpace( textContentValue.replace( /\n/g, ' ' ) );
}
/**
* Treat sequences of spaces as only one space
* @param textContentValue
* @return {*}
* @private
*/
function _stringCollapseMultipleSpace( textContentValue ) {
return textContentValue.replace( /[ ]{2,}/g, ' ' );
}
/**
*
* @param textContentValue
* @return {*}
* @private
*/
function _stringCollapseNothing ( textContentValue ) {
return textContentValue;
}
// LineBreakers -----------------------------------------------------
/**
*
* @param inlines
* @param i
* @param lastInlineOffset
* @param options
* @return {boolean}
* @private
*/
function _lineBreakerWrapText( inlines, i, lastInlineOffset, options ) {
const inline = inlines[ i ];
// prevent additional computation if line break is mandatory
if ( inline.lineBreak === 'mandatory' ) return true;
// ?? Missing letterSpacing ?
// prevent additional computation if this character already exceed the available size
if ( lastInlineOffset + inline.xadvance + inline.xoffset + inline.kerning > options.INNER_WIDTH ) return true;
const nextBreak = _distanceToNextBreak( inlines, i, options );
return _shouldFriendlyBreak( inlines[ i - 1 ], lastInlineOffset, nextBreak, options );
}
/* eslint-disable no-unused-vars */
/**
*
* @param inlines
* @param i
* @param lastInlineOffset
* @param options
* @return {boolean}
* @private
*/
function _lineBreakerLineBreakOnly( inlines, i, lastInlineOffset, options ) { /* eslint-enable no-unused-vars */
return inlines[ i ].lineBreak === 'mandatory';
}
/**
*
* @return {boolean}
* @private
*/
function _lineBreakerNoWrap() {
return false;
}
// Inlines collapser -----------------------------------------------------
/**
*
* @param line
* @return {number}
* @private
*/
function _inlineCollapseSingle( line ) {
if ( !line[ 0 ] ) return 0;
const firstInline = line[ 0 ];
const lastInline = line[ line.length - 1 ];
// only process whiteChars glyphs inlines
// if( firstInline.glyph && whiteChars[firstInline.glyph] && line.length > 1 ){
if ( firstInline.char && firstInline.char === '\n' && line.length > 1 ) {
// if ( firstInline.char && WHITE_CHARS[ firstInline.char ] && line.length > 1 ) {
_collapseLeftInlines( [ firstInline ], line[ 1 ] );
}
// if( lastInline.glyph && whiteChars[lastInline.glyph] && line.length > 1 ){
if ( lastInline.char && lastInline.char === '\n' && line.length > 1 ) {
// if ( lastInline.char && WHITE_CHARS[ firstInline.char ] && line.length > 1 ) {
_collapseRightInlines( [ lastInline ], line[ line.length - 2 ] );
}
return firstInline.offsetX;
}
function _inlineCollapseMultiple( line ) {
if ( !line[ 0 ] ) return 0;
let inlinesToCollapse = [];
let collapsingTarget;
// collect starting whitespaces to collapse
for ( let i = 0; i < line.length; i++ ) {
const inline = line[ i ];
if ( inline.char && WHITE_CHARS[ inline.char ] && line.length > i ) {
inlinesToCollapse.push( inline );
collapsingTarget = line[ i + 1 ];
continue;
}
break;
}
_collapseLeftInlines( inlinesToCollapse, collapsingTarget );
inlinesToCollapse = [];
collapsingTarget = null;
// collect ending whitespace to collapse
for ( let i = line.length - 1; i > 0; i-- ) {
const inline = line[ i ];
if ( inline.char && WHITE_CHARS[ inline.char ] && i > 0 ) {
inlinesToCollapse.push( inline );
collapsingTarget = line[ i - 1 ];
continue;
}
break;
}
_collapseRightInlines( inlinesToCollapse, collapsingTarget );
return line[ 0 ].offsetX;
}
/**
*
* @param line
* @return {number|*}
* @private
*/
function _inlineCollapseNothing( line ) {
if ( !line[ 0 ] ) return 0;
return line[ 0 ].offsetX;
}
/***********************************************************************************************************************
* Internal logics
**********************************************************************************************************************/
/**
* Visually collapse inlines from right to left ( endtrim )
* @param {Array} inlines
* @param targetInline
* @private
*/
function _collapseRightInlines( inlines, targetInline ) {
if ( !targetInline ) return;
for ( let i = 0; i < inlines.length; i++ ) {
const inline = inlines[ i ];
inline.fontFactor = 0;
inline.offsetX = targetInline.offsetX + targetInline.cumulativeWidth;
inline.cumulativeWidth = 0;
}
}
/**
* Visually collapse inlines from left to right (starttrim)
* @param {Array} inlines
* @param targetInline
* @private
*/
function _collapseLeftInlines( inlines, targetInline ) {
if ( !targetInline ) return;
for ( let i = 0; i < inlines.length; i++ ) {
const inline = inlines[ i ];
inline.fontFactor = 0;
// inline.offsetX += inline.cumulativeWidth;
inline.offsetX = targetInline.offsetX;
inline.cumulativeWidth = 0;
}
}
/**
* get the distance in world coord to the next glyph defined
* as break-line-safe ( like whitespace for instance )
* @private
*/
function _distanceToNextBreak( inlines, currentIdx, options, accu ) {
accu = accu || 0;
// end of the text
if ( !inlines[ currentIdx ] ) return accu;
const inline = inlines[ currentIdx ];
// const kerning = inline.kerning ? inline.kerning : 0;
// const xoffset = inline.xoffset ? inline.xoffset : 0;
// const xadvance = inline.xadvance ? inline.xadvance : inline.width;
// if inline.lineBreak is set, it is 'mandatory' or 'possible'
if ( inline.lineBreak ) return accu + inline.xadvance;
// no line break is possible on this character
return _distanceToNextBreak(
inlines,
currentIdx + 1,
options,
accu + inline.xadvance + inline.xoffset + inline.kerning + options.LETTERSPACING
);
}
/**
* Test if we should line break here even if the current glyph is not out of boundary.
* It might be necessary if the last glyph was break-line-friendly (whitespace, hyphen..)
* and the distance to the next friendly glyph is out of boundary.
*/
function _shouldFriendlyBreak( prevChar, lastInlineOffset, nextBreak, options ) {
// We can't check if last glyph is break-line-friendly it does not exist
if ( !prevChar || !prevChar.char ) return false;
// Next break-line-friendly glyph is inside boundary
if ( lastInlineOffset + nextBreak < options.INNER_WIDTH ) return false;
// Previous glyph was break-line-friendly
return options.BREAKON.indexOf( prevChar.char ) > -1;
}
@@ -0,0 +1,46 @@
import SubStyleProperty from '../SubStyleProperty';
export default class Display extends SubStyleProperty {
constructor( defaultValue ) {
super( 'display', defaultValue );
// configure
this._value = 'flex';
this._allowsInherit = false;
this._needsUpdate = false;
this.isValidValue = _isValid;
}
computeOutputValue( element ) {
element._visible._value = this._output !== 'none';
}
}
const AVAILABLE_VALUES = ['none','flex'];
/**
*
* @param {any} value
* @return {boolean}
* @private
*/
function _isValid( value ) {
if( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn( `(.style) display value '${value}' is not valid. Aborted` );
return false;
}
return true;
}
@@ -0,0 +1,206 @@
import SubStyleProperty from '../SubStyleProperty';
import { Plane, Vector3 } from 'three';
export default class Overflow extends SubStyleProperty {
constructor( defaultValue ) {
super( 'overflow', defaultValue, true );
this.isValidValue = _isValid;
/**
*
* @type {Array.<Plane>|null}
* @internal
*/
this._clippingPlanes = null;
this._renderStrategy = this._emptyRender;
}
/**
* Update of overflow is a bit different, as parent may trigger changes on it
* @override
*/
update( element, out ) {
// in any case, it will compute value. It doesn't have updateRequire evaluation
// let updateRequired = true;
// Inline has priority if set
if ( this._inline !== undefined && this._inline !== 'unset' ) {
this._input = this._inline;
}
// or fallback on computed
else if ( this._computed !== undefined ) {
this._input = this._computed;
}
if ( !this._allowsInherit ) {
this._inheritedInput = this.getInheritedInput( element );
}
this.computeOutputValue( element );
// rebuild same properties on children 'inheritance'
for ( const childUIElement of element._children._uis ) {
childUIElement[ `_overflow` ]._needsUpdate = true;
}
this.output( out );
}
output( out ) {
out['clippingPlanes'] = this._clippingPlanes;
}
computeOutputValue( element ) {
// update self --------------------
super.computeOutputValue( element );
if( this._value === 'hidden' ) {
this._renderStrategy = this._propagateRender;
}else{
this._renderStrategy = this._emptyRender;
this._clippingPlanes = null;
}
const parent = element._parent._value;
if( parent !== null ) {
// Check that parent is hiddenOverflow or has clippingPlanes
const overflowParent = parent._overflow;
if ( ( overflowParent._value === 'hidden' || overflowParent._clippingPlanes !== null ) && !this._clippingPlanes ) {
// add planes and render
this._clippingPlanes = [
// top
new Plane( new Vector3( 0, -1, 0 ), 1 ),
// right
new Plane( new Vector3( -1, 0, 0 ), 1 ),
// bottom
new Plane( new Vector3( 0, 1, 0 ), 1 ),
// left
new Plane( new Vector3( 1, 0, 0 ), 1 ),
];
// bind the parent to the clipping plane in a custom property
for ( let i = 0; i < this._clippingPlanes.length; i++ ) {
this._clippingPlanes[ i ].parent = parent;
}
// Also add parent clipping planes if isset
if( overflowParent._clippingPlanes !== null ) {
this._clippingPlanes.push( ...overflowParent._clippingPlanes );
}
this._renderStrategy = this._hiddenRender;
this._needsRender = true;
} else if ( ( overflowParent._value === 'visible' || overflowParent._clippingPlanes === null ) && this._clippingPlanes !== null ){
// remove planes and render
this._clippingPlanes = null;
this._renderStrategy = this._emptyRender;
this._needsRender = true;
}
}
}
render( element) {
this._renderStrategy( element );
}
/* eslint-disable no-unused-vars */ _emptyRender( element ) { /* eslint-enable no-unused-vars */ }
_hiddenRender( element ) {
const parentUI = element._parent._value;
const yLimit = parentUI._bounds._offsetHeight;
const xLimit = parentUI._bounds._offsetWidth;
const padding = parentUI._padding._value;
const border = parentUI._borderWidth._value;
for ( let i = 0; i < 4 && i < this._clippingPlanes.length ; i++ ) {
const clippingPlane = this._clippingPlanes[ i ];
switch ( i % 4 ) {
// top
case 0:
clippingPlane.constant = yLimit / 2 - ( padding.x + border.x );
break;
// right
case 1:
clippingPlane.constant = xLimit / 2 - ( padding.y + border.y );
break;
// bottom
case 2:
clippingPlane.constant = yLimit / 2 - ( padding.z + border.z );
break;
// left
case 3:
clippingPlane.constant = xLimit / 2 - ( padding.w + border.w );
break;
}
clippingPlane.applyMatrix4( parentUI.matrixWorld )
}
for ( let i = 0; i < element._children._uis.length; i++ ) {
const ui = element._children._uis[ i ];
ui._overflow._needsRender = true;
}
}
_propagateRender( element ) {
for ( let i = 0; i < element._children._uis.length; i++ ) {
const ui = element._children._uis[ i ];
ui._overflow._needsRender = true;
}
}
}
const AVAILABLE_VALUES = ['visible', 'hidden'];
function _isValid( value ) {
if( AVAILABLE_VALUES.indexOf( value ) === -1 ) {
console.warn( `(.style) overflow value '${value}' is not valid. Aborted` );
return false;
}
return true;
}

Some files were not shown because too many files have changed in this diff Show More