diff --git a/build/three-mesh-ui.js b/build/three-mesh-ui.js index f6b1adbd..1be5b83c 100644 --- a/build/three-mesh-ui.js +++ b/build/three-mesh-ui.js @@ -47,7 +47,7 @@ /************************************************************************/ var __webpack_exports__ = {}; -// UNUSED EXPORTS: AlignItems, Block, ContentDirection, FontLibrary, InlineBlock, JustifyContent, Keyboard, Text, TextAlign, Whitespace, default, update +// UNUSED EXPORTS: AlignItems, Block, ContentDirection, FontLibrary, FontStyle, FontWeight, InlineBlock, JustifyContent, Keyboard, MSDFFontMaterialUtils, ShaderChunkUI, Text, TextAlign, Whitespace, default, update // NAMESPACE OBJECT: ./src/utils/block-layout/ContentDirection.js var ContentDirection_namespaceObject = {}; @@ -116,6 +116,35 @@ __webpack_require__.d(TextAlign_namespaceObject, { "textAlign": () => (textAlign) }); +// NAMESPACE OBJECT: ./src/utils/font/FontWeight.js +var FontWeight_namespaceObject = {}; +__webpack_require__.r(FontWeight_namespaceObject); +__webpack_require__.d(FontWeight_namespaceObject, { + "BOLD": () => (BOLD), + "BOLDER": () => (BOLDER), + "LIGHTER": () => (LIGHTER), + "NORMAL": () => (FontWeight_NORMAL), + "_100": () => (_100), + "_200": () => (_200), + "_300": () => (_300), + "_400": () => (_400), + "_500": () => (_500), + "_600": () => (_600), + "_700": () => (_700), + "_800": () => (_800), + "_900": () => (_900) +}); + +// NAMESPACE OBJECT: ./src/utils/font/FontStyle.js +var FontStyle_namespaceObject = {}; +__webpack_require__.r(FontStyle_namespaceObject); +__webpack_require__.d(FontStyle_namespaceObject, { + "ITALIC": () => (ITALIC), + "NORMAL": () => (FontStyle_NORMAL), + "OBLIQUE": () => (OBLIQUE), + "obliqueCustomAngle": () => (obliqueCustomAngle) +}); + ;// CONCATENATED MODULE: external "THREE" const external_THREE_namespaceObject = THREE; ;// CONCATENATED MODULE: ./src/utils/block-layout/ContentDirection.js @@ -124,21 +153,43 @@ const ROW_REVERSE = "row-reverse"; const COLUMN = "column"; const COLUMN_REVERSE = "column-reverse"; + +/** + * @tests '/test/specs/utils/box-layout/content-direction.js' + * @param {BoxComponent} container + * @param {string} DIRECTION + * @param {number} startPos + * @param {number} REVERSE + */ function contentDirection( container, DIRECTION, startPos, REVERSE ){ // end to end children let accu = startPos; - let childGetSize = "getWidth"; + + let childGetSize = "getOffsetWidth"; let axisPrimary = "x"; let axisSecondary = "y"; + // left right + let margins = ['w','y']; + if( DIRECTION.indexOf( COLUMN ) === 0 ){ - childGetSize = "getHeight"; + childGetSize = "getOffsetHeight"; + axisPrimary = "y"; axisSecondary = "x"; + // top bttom + margins = ['x', 'z']; + + } + + if ( DIRECTION.indexOf('-reverse') !== -1 ) { + + margins.reverse(); + } // Refactor reduce into fori in order to get rid of this keyword @@ -148,9 +199,8 @@ function contentDirection( container, DIRECTION, startPos, REVERSE ){ const CHILD_ID = child.id; const CHILD_SIZE = child[childGetSize](); - const CHILD_MARGIN = child.margin || 0; - accu += CHILD_MARGIN * REVERSE; + accu += child._margin[margins[0]] * REVERSE; container.childrenPos[ CHILD_ID ] = { [axisPrimary]: accu + ( ( CHILD_SIZE / 2 ) * REVERSE ), @@ -158,12 +208,14 @@ function contentDirection( container, DIRECTION, startPos, REVERSE ){ }; // update accu for next children - accu += ( REVERSE * ( CHILD_SIZE + CHILD_MARGIN ) ); + accu += ( REVERSE * ( CHILD_SIZE + child._margin[margins[1]] ) ); } } + + ;// CONCATENATED MODULE: ./src/utils/block-layout/AlignItems.js @@ -173,6 +225,11 @@ const CENTER = "center"; const END = "end"; const STRETCH = "stretch"; // Still bit experimental +/** + * @tests '/test/specs/utils/box-layout/align-items.js' + * @param {BoxComponent} boxComponent + * @param {string} DIRECTION + */ function alignItems( boxComponent, DIRECTION){ const ALIGNMENT = boxComponent.getAlignItems(); @@ -182,19 +239,19 @@ function alignItems( boxComponent, DIRECTION){ } - let getSizeMethod = "getWidth"; + let getSizeMethod = "getInnerWidth"; let axis = "x"; if( DIRECTION.indexOf( ROW ) === 0 ){ - getSizeMethod = "getHeight"; + getSizeMethod = "getInnerHeight"; axis = "y"; } - const AXIS_TARGET = ( boxComponent[getSizeMethod]() / 2 ) - ( boxComponent.padding || 0 ); + const AXIS_TARGET = ( boxComponent[getSizeMethod]() / 2 ); boxComponent.childrenBoxes.forEach( ( child ) => { - let offset; + let offset = 0; switch ( ALIGNMENT ){ @@ -203,11 +260,11 @@ function alignItems( boxComponent, DIRECTION){ case 'bottom': // @TODO : Deprecated and will be remove upon 7.x.x if( DIRECTION.indexOf( ROW ) === 0 ){ - offset = - AXIS_TARGET + ( child[getSizeMethod]() / 2 ) + ( child.margin || 0 ); + offset = - AXIS_TARGET + ( child[getSizeMethod]() / 2 ); }else{ - offset = AXIS_TARGET - ( child[getSizeMethod]() / 2 ) - ( child.margin || 0 ); + offset = AXIS_TARGET - ( child[getSizeMethod]() / 2 ); } @@ -218,18 +275,18 @@ function alignItems( boxComponent, DIRECTION){ case 'top': // @TODO : Deprecated and will be remove upon 7.x.x if( DIRECTION.indexOf( ROW ) === 0 ){ - offset = AXIS_TARGET - ( child[getSizeMethod]() / 2 ) - ( child.margin || 0 ); + offset = AXIS_TARGET - ( child[getSizeMethod]() / 2 ); }else{ - offset = - AXIS_TARGET + ( child[getSizeMethod]() / 2 ) + ( child.margin || 0 ); + offset = - AXIS_TARGET + ( child[getSizeMethod]() / 2 ); } break; } - boxComponent.childrenPos[ child.id ][axis] = offset || 0; + boxComponent.childrenPos[ child.id ][axis] = offset; } ); @@ -278,6 +335,13 @@ const SPACE_AROUND = 'space-around'; const SPACE_BETWEEN = 'space-between'; const SPACE_EVENLY = 'space-evenly'; +/** + * @tests '/test/specs/utils/box-layout/justify-content.js' + * @param {BoxComponent} boxComponent + * @param {string} direction + * @param {number} startPos + * @param {number} REVERSE + */ function justifyContent( boxComponent, direction, startPos, REVERSE){ const JUSTIFICATION = boxComponent.getJustifyContent(); @@ -290,7 +354,7 @@ function justifyContent( boxComponent, direction, startPos, REVERSE){ const side = direction.indexOf('row') === 0 ? 'width' : 'height' const usedDirectionSpace = boxComponent.getChildrenSideSum( side ); - const INNER_SIZE = side === 'width' ? boxComponent.getInnerWidth() : boxComponent.getInnerHeight(); + const INNER_SIZE = side === 'width' ? boxComponent.innerWidth : boxComponent.innerHeight; const remainingSpace = INNER_SIZE - usedDirectionSpace; // Items Offset @@ -422,219 +486,6 @@ function _getJustificationMargin( items, spaceToDistribute, justification, rever } -;// CONCATENATED MODULE: ./src/components/core/BoxComponent.js -/** - -Job: Handle everything related to a BoxComponent element dimensioning and positioning - -Knows: Parents and children dimensions and positions - -It's worth noting that in three-mesh-ui, it's the parent Block that computes -its children position. A Block can only have either only box components (Block) -as children, or only inline components (Text, InlineBlock). - - */ - - - - - -function BoxComponent( Base ) { - - return class BoxComponent extends Base { - - constructor( options ) { - - super( options ); - - this.isBoxComponent = true; - this.childrenPos = {}; - - } - - - /** Get width of this component minus its padding */ - getInnerWidth() { - - const DIRECTION = this.getContentDirection(); - - switch ( DIRECTION ) { - - case 'row' : - case 'row-reverse' : - return this.width - ( this.padding * 2 || 0 ) || this.getChildrenSideSum( 'width' ); - - case 'column' : - case 'column-reverse' : - return this.getHighestChildSizeOn( 'width' ); - - default : - console.error( `Invalid contentDirection : ${DIRECTION}` ); - break; - - } - - } - - /** Get height of this component minus its padding */ - getInnerHeight() { - - const DIRECTION = this.getContentDirection(); - - switch ( DIRECTION ) { - - case 'row' : - case 'row-reverse' : - return this.getHighestChildSizeOn( 'height' ); - - case 'column' : - case 'column-reverse' : - return this.height - ( this.padding * 2 || 0 ) || this.getChildrenSideSum( 'height' ); - - default : - console.error( `Invalid contentDirection : ${DIRECTION}` ); - break; - - } - - } - - /** Return the sum of all this component's children sides + their margin */ - getChildrenSideSum( dimension ) { - - return this.childrenBoxes.reduce( ( accu, child ) => { - - const margin = ( child.margin * 2 ) || 0; - - const CHILD_SIZE = ( dimension === 'width' ) ? - ( child.getWidth() + margin ) : - ( child.getHeight() + margin ); - - return accu + CHILD_SIZE; - - }, 0 ); - - } - - /** Look in parent record what is the instructed position for this component, then set its position */ - setPosFromParentRecords() { - - if ( this.parentUI && this.parentUI.childrenPos[ this.id ] ) { - - this.position.x = ( this.parentUI.childrenPos[ this.id ].x ); - this.position.y = ( this.parentUI.childrenPos[ this.id ].y ); - - } - - } - - /** Position inner elements according to dimensions and layout parameters. */ - computeChildrenPosition() { - - if ( this.children.length > 0 ) { - - const DIRECTION = this.getContentDirection(); - let directionalOffset; - - switch ( DIRECTION ) { - - case ROW : - directionalOffset = - this.getInnerWidth() / 2; - break; - - case ROW_REVERSE : - directionalOffset = this.getInnerWidth() / 2; - break; - - case COLUMN : - directionalOffset = this.getInnerHeight() / 2; - break; - - case COLUMN_REVERSE : - directionalOffset = - this.getInnerHeight() / 2; - break; - - } - - const REVERSE = - Math.sign( directionalOffset ); - - contentDirection(this, DIRECTION, directionalOffset, REVERSE ); - justifyContent(this, DIRECTION, directionalOffset, REVERSE ); - alignItems( this, DIRECTION ); - } - - } - - /** - * Returns the highest linear dimension among all the children of the passed component - * MARGIN INCLUDED - */ - getHighestChildSizeOn( direction ) { - - return this.childrenBoxes.reduce( ( accu, child ) => { - - const margin = child.margin || 0; - const maxSize = direction === 'width' ? - child.getWidth() + ( margin * 2 ) : - child.getHeight() + ( margin * 2 ); - - return Math.max( accu, maxSize ); - - }, 0 ); - - } - - /** - * Get width of this element - * With padding, without margin - */ - getWidth() { - - - // This is for stretch alignment - // @TODO : Conceive a better performant way - if( this.parentUI && this.parentUI.getAlignItems() === 'stretch' ){ - - if( this.parentUI.getContentDirection().indexOf('column') !== -1 ){ - - return this.parentUI.getWidth() - ( this.parentUI.padding * 2 || 0 ); - - } - - } - - - return this.width || this.getInnerWidth() + ( this.padding * 2 || 0 ); - - } - - /** - * Get height of this element - * With padding, without margin - */ - getHeight() { - - // This is for stretch alignment - // @TODO : Conceive a better performant way - if( this.parentUI && this.parentUI.getAlignItems() === 'stretch' ){ - - if( this.parentUI.getContentDirection().indexOf('row') !== -1 ){ - - return this.parentUI.getHeight() - ( this.parentUI.padding * 2 || 0 ); - - } - - } - - return this.height || this.getInnerHeight() + ( this.padding * 2 || 0 ); - - } - - }; - -} - - ;// CONCATENATED MODULE: ./src/utils/inline-layout/Whitespace.js /** * @see https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace#whitespace_helper_functions @@ -784,6 +635,8 @@ const Whitespace_shouldBreak = function( inlines, i, lastInlineOffset, options){ */ const collapseWhitespaceOnInlines = function ( line, whiteSpace ) { + if( !line[0] ) return 0; + const firstInline = line[ 0 ]; const lastInline = line[ line.length - 1 ]; @@ -896,7 +749,9 @@ function _collapseRightInlines( inlines, targetInline ) { // inline.width = 0; // inline.height = 0; inline.fontFactor = 0; - inline.offsetX = targetInline.offsetX + targetInline.width; + // inline.offsetX = targetInline.offsetX + targetInline.width; + inline.offsetX = targetInline.offsetX + targetInline.cumulativeWidth; + // inline.offsetX -= inline.cumulativeWidth; } @@ -919,7 +774,8 @@ function _collapseLeftInlines( inlines, targetInline ) { // inline.width = 0; // inline.height = 0; inline.fontFactor = 0; - inline.offsetX = targetInline.offsetX; + // inline.offsetX = targetInline.offsetX; + inline.offsetX += inline.cumulativeWidth; } @@ -975,6 +831,11 @@ function _shouldFriendlyBreak( prevChar, lastInlineOffset, nextBreak, options ) } ;// CONCATENATED MODULE: ./src/utils/inline-layout/TextAlign.js +//JSDoc related import +/* eslint-disable no-unused-vars */ + +/* eslint-enable no-unused-vars */ + const LEFT = 'left'; const RIGHT = 'right'; const TextAlign_CENTER = 'center'; @@ -985,11 +846,12 @@ const JUSTIFY_CENTER = 'justify-center'; /** * - * @param {Array.>} lines + * @param boxComponent + * @param {Array.>} lines * @param ALIGNMENT * @param INNER_WIDTH */ -function textAlign( lines, ALIGNMENT, INNER_WIDTH ) { +function textAlign( boxComponent, lines, ALIGNMENT, INNER_WIDTH ) { // Start the alignment by sticking to directions : left, right, center for ( let i = 0; i < lines.length; i++ ) { @@ -999,10 +861,18 @@ function textAlign( lines, ALIGNMENT, INNER_WIDTH ) { // compute the alignment offset of the line const offsetX = _computeLineOffset( line, ALIGNMENT, INNER_WIDTH, i === lines.length - 1 ); + const padding = boxComponent._padding; + const border = boxComponent._borderWidth; + + // 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; + // apply the offset to each characters of the line for ( let j = 0; j < line.length; j++ ) { - line[ j ].offsetX += offsetX; + line[ j ].offsetX += offsetX - paddingAmount; + // line[ j ].offsetX += offsetX; } @@ -1105,441 +975,152 @@ const _computeLineOffset = ( line, ALIGNMENT, INNER_WIDTH, lastLine ) => { }; -;// CONCATENATED MODULE: ./src/components/core/InlineManager.js -/** - -Job: Positioning inline elements according to their dimensions inside this component +;// CONCATENATED MODULE: ./src/utils/inline-layout/InlineJustification.js -Knows: This component dimensions, and its children dimensions +function justifyInlines( boxComponent, lines, JUSTIFICATION, INNER_HEIGHT ){ -This module is used for Block composition (Object.assign). A Block is responsible -for the positioning of its inline elements. In order for it to know what is the -size of these inline components, parseParams must be called on its children first. + const textHeight = Math.abs( lines.height ); -It's worth noting that a Text is not positioned as a whole, but letter per letter, -in order to create a line break when necessary. It's Text that merge the various letters -in its own updateLayout function. - */ + // Line vertical positioning + let justificationOffset = ( () => { + switch ( JUSTIFICATION ) { + case 'start': + // return ( INNER_HEIGHT / 2 ) - lines[ 0 ].lineHeight - boxComponent._padding.x ; + // return boxComponent._padding.x - lines[0].lineHeight ; + // return (INNER_HEIGHT * .5) + boxComponent._padding.x - (lines[0].lineHeight * .5); + return (INNER_HEIGHT * .5) - lines[0].lineHeight; -function InlineManager( Base ) { + case 'end': + return textHeight - lines[ 0 ].lineHeight - ( INNER_HEIGHT / 2 ); - return class InlineManager extends Base { + case 'space-around': + case 'space-between': + case 'space-evenly': + case 'center': + return ( textHeight / 2 ) - lines[ 0 ].lineHeight ; - /** Compute children .inlines objects position, according to their pre-computed dimensions */ - computeInlinesPosition() { + default: + console.warn( `justifyContent: '${JUSTIFICATION}' is not valid` ); - // computed by BoxComponent - const INNER_WIDTH = this.getWidth() - ( this.padding * 2 || 0 ); - const INNER_HEIGHT = this.getHeight() - ( this.padding * 2 || 0 ); + } + } )(); - // got by MeshUIComponent - const JUSTIFICATION = this.getJustifyContent(); - const ALIGNMENT = this.getTextAlign(); + // Apply padding + const padding = boxComponent._padding; + const border = boxComponent._borderWidth; - const INTERLINE = this.getInterLine(); + justificationOffset += ( - padding.x + padding.z ) / 2 + ( - border.x + border.z ) / 2; - // Compute lines - const lines = this.computeLines(); + // - ///////////////////////////////////////////////////////////////// - // Position lines according to justifyContent and contentAlign - ///////////////////////////////////////////////////////////////// + lines.forEach( ( line ) => { - // individual vertical offset + line.y += justificationOffset; + line.forEach( ( inline ) => { - let textHeight = lines.reduce( ( offsetY, line, i, arr ) => { + inline.offsetY += justificationOffset; - const charAlignement = line.lineHeight - line.lineBase; + } ); - line.forEach( ( inline ) => { + } ); - inline.offsetY = offsetY - line.lineHeight + charAlignement + arr[ 0 ].lineHeight; +} - } ); +;// CONCATENATED MODULE: ./src/components/core/Line.js +//JSDoc related imports +/* eslint-disable no-unused-vars */ - return offsetY - line.lineHeight - INTERLINE; +/* eslint-enable no-unused-vars */ - }, 0 ) + INTERLINE; - // +/** + * Line represents an horizontal combination of positioned inlines with additional properties + */ +class Line extends Array { - textHeight = Math.abs( textHeight ); + /** + * + * @param {Inline[]} items + */ + constructor(...items) { + super(...items); - // Line vertical positioning + /** + * The width of this line + * @type {number} + */ + this.width = 0; - const justificationOffset = ( () => { - switch ( JUSTIFICATION ) { + /** + * The maximum lineBase of this line of inlines + * @type {number} + */ + this.lineBase = 0; - case 'start': - return ( INNER_HEIGHT / 2 ) - lines[ 0 ].lineHeight; - case 'end': - return textHeight - lines[ 0 ].lineHeight - ( INNER_HEIGHT / 2 ) + ( lines[ lines.length - 1 ].lineHeight - lines[ lines.length - 1 ].lineHeight ); - case 'center': - return ( textHeight / 2 ) - lines[ 0 ].lineHeight; - default: - console.warn( `justifyContent: '${JUSTIFICATION}' is not valid` ); + /** + * The maximum lineHeight of this line of inlines + * @type {number} + */ + this.lineHeight = 0; - } - } )(); + /** + * The vertical position of this line + * @type {number} + */ + this.y = 0; - // const justificationOffset = 0; + } - // +} - lines.forEach( ( line ) => { +;// CONCATENATED MODULE: ./src/components/core/Lines.js +//JSDoc related imports +/* eslint-disable no-unused-vars */ - line.forEach( ( inline ) => { +/* eslint-enable no-unused-vars */ - inline.offsetY += justificationOffset; +/** + * Lines represents a vertical succession of Line + */ +class Lines extends Array { - } ); + /** + * + * @param {Line} items + */ + constructor(...items) { + super(...items); - } ); + /** + * The maximum width of Line items + * @type {number} + */ + this.width = 0; - // Horizontal positioning - textAlign( lines, ALIGNMENT, INNER_WIDTH ); + /** + * The addition of height of any Line + * @type {number} + */ + this.height = 0; + } - // Make lines accessible to provide helpful informations - this.lines = lines; +} - } +;// CONCATENATED MODULE: ./src/font/FontVariant.js - calculateBestFit( bestFit ) { - if ( this.childrenInlines.length === 0 ) return; +// JSDoc related imports +/* eslint-disable no-unused-vars */ - switch ( bestFit ) { - case 'grow': - this.calculateGrowFit(); - break; - case 'shrink': - this.calculateShrinkFit(); - break; - case 'auto': - this.calculateAutoFit(); - break; - } - } - - calculateGrowFit() { - - const INNER_HEIGHT = this.getHeight() - ( this.padding * 2 || 0 ); - - //Iterative method to find a fontSize of text children that text will fit into container - let iterations = 1; - const heightTolerance = 0.075; - const firstText = this.childrenInlines.find( inlineComponent => inlineComponent.isText ); - - let minFontMultiplier = 1; - let maxFontMultiplier = 2; - let fontMultiplier = firstText._fitFontSize ? firstText._fitFontSize / firstText.getFontSize() : 1; - let textHeight; - - do { - - textHeight = this.calculateHeight( fontMultiplier ); - - if ( textHeight > INNER_HEIGHT ) { - - if ( fontMultiplier <= minFontMultiplier ) { // can't shrink text - - this.childrenInlines.forEach( inlineComponent => { - - if ( inlineComponent.isInlineBlock ) return; - - // ensure fontSize does not shrink - inlineComponent._fitFontSize = inlineComponent.getFontSize(); - - } ); - - break; - - } - - maxFontMultiplier = fontMultiplier; - fontMultiplier -= ( maxFontMultiplier - minFontMultiplier ) / 2; - - } else { - - if ( Math.abs( INNER_HEIGHT - textHeight ) < heightTolerance ) break; - - if ( Math.abs( fontMultiplier - maxFontMultiplier ) < 5e-10 ) maxFontMultiplier *= 2; - - minFontMultiplier = fontMultiplier; - fontMultiplier += ( maxFontMultiplier - minFontMultiplier ) / 2; - - } - - } while ( ++iterations <= 10 ); - - } - - calculateShrinkFit() { - - const INNER_HEIGHT = this.getHeight() - ( this.padding * 2 || 0 ); - - // Iterative method to find a fontSize of text children that text will fit into container - let iterations = 1; - const heightTolerance = 0.075; - const firstText = this.childrenInlines.find( inlineComponent => inlineComponent.isText ); - - let minFontMultiplier = 0; - let maxFontMultiplier = 1; - let fontMultiplier = firstText._fitFontSize ? firstText._fitFontSize / firstText.getFontSize() : 1; - let textHeight; - - do { - - textHeight = this.calculateHeight( fontMultiplier ); - - if ( textHeight > INNER_HEIGHT ) { - - maxFontMultiplier = fontMultiplier; - fontMultiplier -= ( maxFontMultiplier - minFontMultiplier ) / 2; - - } else { - - if ( fontMultiplier >= maxFontMultiplier ) { // can't grow text - - this.childrenInlines.forEach( inlineComponent => { - - if ( inlineComponent.isInlineBlock ) return; - - // ensure fontSize does not grow - inlineComponent._fitFontSize = inlineComponent.getFontSize(); - - } ); - - break; - - } - - if ( Math.abs( INNER_HEIGHT - textHeight ) < heightTolerance ) break; - - minFontMultiplier = fontMultiplier; - fontMultiplier += ( maxFontMultiplier - minFontMultiplier ) / 2; - - } - - } while ( ++iterations <= 10 ); - } - - calculateAutoFit() { - - const INNER_HEIGHT = this.getHeight() - ( this.padding * 2 || 0 ); - - //Iterative method to find a fontSize of text children that text will fit into container - let iterations = 1; - const heightTolerance = 0.075; - const firstText = this.childrenInlines.find( inlineComponent => inlineComponent.isText ); - - let minFontMultiplier = 0; - let maxFontMultiplier = 2; - let fontMultiplier = firstText._fitFontSize ? firstText._fitFontSize / firstText.getFontSize() : 1; - let textHeight; - - do { - - textHeight = this.calculateHeight( fontMultiplier ); - - if ( textHeight > INNER_HEIGHT ) { - - maxFontMultiplier = fontMultiplier; - fontMultiplier -= ( maxFontMultiplier - minFontMultiplier ) / 2; - - } else { - - if ( Math.abs( INNER_HEIGHT - textHeight ) < heightTolerance ) break; - - if ( Math.abs( fontMultiplier - maxFontMultiplier ) < 5e-10 ) maxFontMultiplier *= 2; - - minFontMultiplier = fontMultiplier; - fontMultiplier += ( maxFontMultiplier - minFontMultiplier ) / 2; - - } - - } while ( ++iterations <= 10 ); - } - - /** - * computes lines based on children's inlines array. - * @private - */ - computeLines() { - - // computed by BoxComponent - const INNER_WIDTH = this.getWidth() - ( this.padding * 2 || 0 ); - - // Will stock the characters of each line, so that we can - // correct lines position before to merge - const lines = [ [] ]; - - this.childrenInlines.reduce( ( lastInlineOffset, inlineComponent ) => { - - // Abort condition - - if ( !inlineComponent.inlines ) return; - - ////////////////////////////////////////////////////////////// - // Compute offset of each children according to its dimensions - ////////////////////////////////////////////////////////////// - - const FONTSIZE = inlineComponent._fitFontSize || inlineComponent.getFontSize(); - const LETTERSPACING = inlineComponent.isText ? inlineComponent.getLetterSpacing() * FONTSIZE : 0; - const WHITESPACE = inlineComponent.getWhiteSpace(); - const BREAKON = inlineComponent.getBreakOn(); - - const whiteSpaceOptions = { - WHITESPACE, - LETTERSPACING, - BREAKON, - INNER_WIDTH - } - - const currentInlineInfo = inlineComponent.inlines.reduce( ( lastInlineOffset, inline, i, inlines ) => { - - // Line break - const shouldBreak = Whitespace_shouldBreak(inlines,i,lastInlineOffset, whiteSpaceOptions ); - - if ( shouldBreak ) { - - lines.push( [ inline ] ); - - inline.offsetX = inline.xoffset; - - // restart the lastInlineOffset as zero. - if ( inline.width === 0 ) return 0; - - // compute lastInlineOffset normally - // except for kerning which won't apply - // as there is visually no lefthanded glyph to kern with - return inline.xadvance + LETTERSPACING; - - } - - lines[ lines.length - 1 ].push( inline ); - - inline.offsetX = lastInlineOffset + inline.xoffset + inline.kerning; - - return lastInlineOffset + inline.xadvance + inline.kerning + LETTERSPACING; - - }, lastInlineOffset ); - - // - - return currentInlineInfo; - - }, 0 ); - - // Compute lines dimensions - - lines.forEach( ( line ) => { - - // - - line.lineHeight = line.reduce( ( height, inline ) => { - - const charHeight = inline.lineHeight !== undefined ? inline.lineHeight : inline.height; - // const charHeight = inline.height; - - return Math.max( height, charHeight ); - - }, 0 ); - - // - - line.lineBase = line.reduce( ( lineBase, inline ) => { - - const newLineBase = inline.lineBase !== undefined ? inline.lineBase : inline.height; - // const newLineBase = inline.height; - - return Math.max( lineBase, newLineBase ); - - }, 0 ); - - // - - line.width = 0; - const lineHasInlines = line[ 0 ]; - - if ( lineHasInlines ) { - - // starts by processing whitespace, it will return a collapsed left offset - const WHITESPACE = this.getWhiteSpace(); - const whiteSpaceOffset = collapseWhitespaceOnInlines( line, WHITESPACE ); - - // apply the collapsed left offset to ensure the starting offset is 0 - line.forEach( ( inline ) => { - - inline.offsetX -= whiteSpaceOffset; - - } ); - - // compute its width: length from firstInline:LEFT to lastInline:RIGHT - line.width = this.computeLineWidth( line ); - - } - - } ); - - return lines; - } - - calculateHeight( fontMultiplier ) { - - this.childrenInlines.forEach( inlineComponent => { - - if ( inlineComponent.isInlineBlock ) return; - - // Set font size and recalculate dimensions - inlineComponent._fitFontSize = inlineComponent.getFontSize() * fontMultiplier; - inlineComponent.calculateInlines( inlineComponent._fitFontSize ); - - } ); - - const lines = this.computeLines(); - - const INTERLINE = this.getInterLine(); - - const textHeight = lines.reduce( ( offsetY, line ) => { - - return offsetY - line.lineHeight - INTERLINE; - - }, 0 ) + INTERLINE; - - return Math.abs( textHeight ); - } - - /** - * Compute the width of a line - * @param line - * @returns {number} - */ - computeLineWidth( line ) { - - // only by the length of its extremities - const firstInline = line[ 0 ]; - - const lastInline = line[ line.length - 1 ]; - - // Right + Left ( left is negative ) - return (lastInline.offsetX + lastInline.width) + firstInline.offsetX; - - } - - }; - -} - -;// CONCATENATED MODULE: ./src/font/FontVariant.js +/* eslint-enable no-unused-vars */ /** @@ -1547,23 +1128,28 @@ function InlineManager( Base ) { */ class FontVariant extends external_THREE_namespaceObject.EventDispatcher { + /** + * + * @param {string} weight + * @param {string} style + */ constructor( weight, style ) { super(); - this._isReady = false; + /** @private */ this._isReady = false; - this._weight = weight; - this._style = style; + /** @protected */ this._weight = weight; + /** @protected */ this._style = style; - this._size = 42; - this._lineHeight = 42; - this._lineBase = 42; + /** @protected */ this._size = 42; + /** @protected */ this._lineHeight = 42; + /** @protected */ this._lineBase = 42; /** * - * @type {TypographyFont} - * @private + * @type {TypographicFont} + * @protected */ this._font = null; @@ -1571,34 +1157,70 @@ class FontVariant extends external_THREE_namespaceObject.EventDispatcher { /** * - * @returns {TypographyFont} + * @returns {TypographicFont} */ get typographic() { return this._font; } + /** + * + * @returns {boolean} + */ get isReady() { return this._isReady; } + /** + * + * @returns {string} + */ get weight() { return this._weight; } + /** + * + * @returns {string} + */ get style() { return this._style; } + /** + * + * @returns {Texture} + */ get texture() { return this._texture; } + /** + * @param {Function.} v + * @abstract + */ + set fontMaterial( v ) { + throw Error( `FontVariant('${this.id}')::fontMaterial - is abstract.` ); + } + + /** + * @return {Function.} + * @abstract + */ + get fontMaterial() { + throw Error( `FontVariant('${this.id}')::fontMaterial - is abstract.` ); + } + + /** + * + * @returns {string} + */ get id(){ return `${this._name}(w:${this.weight},s:${this.style})`; } @@ -1606,24 +1228,24 @@ class FontVariant extends external_THREE_namespaceObject.EventDispatcher { /** * * @param {string} character - * @returns {MSDFTypographyCharacter} + * @returns {MSDFTypographicGlyph} */ - getTypographyCharacter( character ) { + getTypographicGlyph( character ) { - let typographyCharacter = this._chars[ character ]; - if ( typographyCharacter ) return typographyCharacter; + let typographicGlyph = this._chars[ character ]; + if ( typographicGlyph ) return typographicGlyph; if ( character.match( /\s/ ) ) return this._chars[ " " ]; const fallbackCharacter = font_FontLibrary.missingCharacter( this, character ); if( fallbackCharacter ) { - typographyCharacter = this._chars[ fallbackCharacter ]; - if ( typographyCharacter ) return typographyCharacter; + typographicGlyph = this._chars[ fallbackCharacter ]; + if ( typographicGlyph ) return typographicGlyph; } - throw Error( `FontVariant('${this.id}')::getTypographyCharacter() - character('${character}') and/or fallback character were not found in provided msdf charset.` ); + throw Error( `FontVariant('${this.id}')::getTypographicGlyph() - character('${character}') and/or fallback character were not found in provided msdf charset.` ); } /* eslint-disable no-unused-vars */ @@ -1633,10 +1255,10 @@ class FontVariant extends external_THREE_namespaceObject.EventDispatcher { * Convert an InlineCharacter to a geometry * * @abstract - * @param {InlineCharacter} inline - * @returns {THREE.BufferGeometry|Array.} + * @param {InlineGlyph} inline + * @returns {BufferGeometry|Array.} */ - getGeometryCharacter( inline ) { + getGeometricGlyph( inline, segments = 1 ) { throw new Error(`FontVariant(${typeof this})::getGeometryCharacter() is abstract and should therefore be overridden.`); @@ -1660,17 +1282,17 @@ class FontVariant extends external_THREE_namespaceObject.EventDispatcher { /** * Perform some changes on the character description of this font - * @param {Object} adjustmentObject + * @param {Object.>} adjustmentObject */ - adjustTypographyCharacters( adjustmentObject ){ + adjustTypographicGlyphs( adjustmentObject ){ for ( const char in adjustmentObject ) { - const desc = this.getTypographyCharacter( char ); - const characterAdjustment = adjustmentObject[ char ]; - for ( const propertyToAdjust in characterAdjustment ) { + const typographicGlyph = this.getTypographicGlyph( char ); + const glyphAdjustment = adjustmentObject[ char ]; + for ( const propertyToAdjust in glyphAdjustment ) { - desc["_"+propertyToAdjust] = adjustmentObject[char][propertyToAdjust]; + typographicGlyph["_"+propertyToAdjust] = adjustmentObject[char][propertyToAdjust]; } @@ -1701,7 +1323,7 @@ class FontVariant extends external_THREE_namespaceObject.EventDispatcher { _readyCondition () { // ie: MSDFFontVariant - // Must have chards and a texture + // Must have chars and a texture // return this._chars && this._texture throw new Error(`FontVariant(${typeof this})::_readyCondition() is abstract and should therefore be overridden.`); @@ -1718,7 +1340,7 @@ const _readyEvent = { type: 'ready' }; /** * Set the ready status of a fontVariant - * @param fontVariant + * @param {FontVariant} fontVariant * @private */ function _setReady( fontVariant ) { @@ -1728,128 +1350,60 @@ function _setReady( fontVariant ) { } -/** - * @typedef {Object} MSDFJson - * @see https://www.angelcode.com/products/bmfont/doc/file_format.html - * - * @property {MSDFJsonInfo} info - * @property {MSDFJsonCommon} common - * @property {Array.} pages - * @property {Array.} chars - * @property {Array.} kernings - */ -/** - * - * @typedef {Object} MSDFJsonInfo - * @see https://www.angelcode.com/products/bmfont/doc/file_format.html - * - * @property {string} face This is the name of the true type font. - * @property {number} size The size of the true type font. - * @property {boolean} bold The font is bold. - * @property {boolean} italic The font is italic. - * @property {string[]} charset The name of the OEM charset used (when not unicode). - * @property {boolean} unicode Set to 1 if it is the unicode charset. - * @property {number} stretchH The font height stretch in percentage. 100% means no stretch. - * @property {number} smooth Set to 1 if smoothing was turned on. - * @property {number} aa The supersampling level used. 1 means no supersampling was used. - * @property {Array.} padding TThe padding for each character (up, right, down, left). - * @property {Array.} spacing The spacing for each character (horizontal, vertical). - * @property {number} outline (not found) The outline thickness for the characters. - */ - -/** - * - * @typedef {Object} MSDFJsonCommon - * @see https://www.angelcode.com/products/bmfont/doc/file_format.html - * - * @property {number} lineHeight This is the distance in pixels between each line of text. - * @property {number} base The number of pixels from the absolute top of the line to the base of the characters. - * @property {number} scaleW The width of the texture, normally used to scale the x pos of the character image. - * @property {number} scaleH The height of the texture, normally used to scale the y pos of the character image. - * @property {number} pages The number of texture pages included in the font. - * @property {boolean} packed - * @property {number} alphaChnl - * @property {number} redChnl - * @property {number} greenChnl - * @property {number[]} blueChnl - */ - -/** - * - * @typedef {Object} MSDFJsonPage - * @see https://www.angelcode.com/products/bmfont/doc/file_format.html - * - * @property {string} id The page id. - * @property {string} file The texture file name. - */ - -/** - * - * @typedef {Object} MSDFJsonChar - * @see https://www.angelcode.com/products/bmfont/doc/file_format.html - * - * @property {number} id The character id. - * @property {number} index The character index. - * @property {string} char The character. - * @property {number} x The left position of the character image in the texture. - * @property {number} y The top position of the character image in the texture. - * @property {number} width The width of the character image in the texture. - * @property {number} height The height of the character image in the texture. - * @property {number} xoffset How much the current position should be offset when copying the image from the texture to the screen. - * @property {number} yoffset How much the current position should be offset when copying the image from the texture to the screen. - * @property {number} xadvance How much the current position should be advanced after drawing the character. - * @property {string} page The texture page where the character image is found. - * @property {number} chnl The texture channel where the character image is found (1 = blue, 2 = green, 4 = red, 8 = alpha, 15 = all channels). - */ - - - -/** - * - * @typedef {Object} MSDFJsonKerning - * @see https://www.angelcode.com/products/bmfont/doc/file_format.html - * - * @property {number} first The first character id. - * @property {number} second The second character id. - * @property {number} amount How much the x position should be adjusted when drawing the second character immediately following the first. - * - */ - -;// CONCATENATED MODULE: ./src/font/TypographyFont.js -class TypographyFont { +;// CONCATENATED MODULE: ./src/font/TypographicFont.js +class TypographicFont { constructor() { - this._size = 42; - this._lineHeight = 42; - this._lineBase = 38; + /** @protected */ this._size = 42; + /** @protected */ this._lineHeight = 42; + /** @protected */ this._lineBase = 38; + /** @protected */ this._name = "-"; + /** @protected */ this._charset = ""; - this._name = "-"; - - this._charset = ""; } + /** + * + * @returns {number} + */ get size() { return this._size; } + /** + * + * @returns {number} + */ get lineHeight() { return this._lineHeight; } + /** + * + * @returns {number} + */ get lineBase() { return this._lineBase; } + /** + * + * @returns {string} + */ get name() { return this._name; } + /** + * + * @returns {string} + */ get charset() { return this._charset; } } -;// CONCATENATED MODULE: ./src/font/msdf/MSDFTypographyFont.js +;// CONCATENATED MODULE: ./src/font/msdf/MSDFTypographicFont.js -class MSDFTypographyFont extends TypographyFont{ +class MSDFTypographicFont extends TypographicFont{ /** * - * @param {MSDFJson} json + * @param {import('./MSDFFontVariant').MSDFJson} json */ constructor( json ) { @@ -1870,38 +1424,58 @@ class MSDFTypographyFont extends TypographyFont{ } + /** + * + * @returns {number} + */ get textureWidth() { return this._textureWidth; } + /** + * + * @returns {number} + */ get textureHeight() { return this._textureHeight; } } -;// CONCATENATED MODULE: ./src/font/TypographyCharacter.js +;// CONCATENATED MODULE: ./src/font/TypographicGlyph.js +//JSDoc related imports +/* eslint-disable no-unused-vars */ + + +/* eslint-enable no-unused-vars */ + /** + * @class * @abstract */ -class TypographyCharacter { +class TypographicGlyph { /** * - * @param {TypographyFont} typographicFont + * @param {TypographicFont} typographicFont */ constructor( typographicFont ) { - this._char = ""; - this._width = this._heigth = this._xadvance = 1; - this._xoffset = this._yoffset = 0; + /** @protected */ this._char = ""; + /** @protected */ this._width = 1; + /** @protected */ this._heigth = 1; + /** @protected */ this._xadvance = 1; + /** @protected */ this._xoffset = 0; + /** @protected */ this._yoffset = 0; /** * - * @private + * @type {TypographicFont} + * @protected */ this._font = typographicFont; + } /** * - * @returns {TypographyFont} + * @returns {TypographicFont} */ get font() { @@ -1919,36 +1493,60 @@ class TypographyCharacter { } + /** + * + * @returns {number} + */ get width() { return this._width; } + /** + * + * @returns {number} + */ get height() { return this._heigth; } + /** + * + * @returns {number} + */ get xadvance() { return this._xadvance; } + /** + * + * @returns {number} + */ get xoffset() { return this._xoffset; } + /** + * + * @returns {number} + */ get yoffset() { return this._yoffset; } + /** + * + * @param value + */ set yoffset( value ) { this._yoffset = value; @@ -1958,9 +1556,9 @@ class TypographyCharacter { /** * * @abstract - * @returns {InlineCharacter} + * @returns {InlineGlyph} */ - asInlineCharacter() { + asInlineGlyph() { throw new Error("Abstract... Need to be implemented") @@ -1968,167 +1566,425 @@ class TypographyCharacter { } -;// CONCATENATED MODULE: ./src/font/InlineCharacter.js -class InlineCharacter { +;// CONCATENATED MODULE: ./src/components/core/Inline.js +/** + * This is the abstract/base class / interface of any inline + * Inline can be positioned according to text rules + */ +class Inline { - /** - * - * @param {TypographyCharacter} characterDesc - */ - constructor( characterDesc ) { + constructor() { - this._typographic = characterDesc; + /** @protected */ this._offsetX = 0; + /** @protected */ this._offsetY = 0; - this._fontFactor = 1; - this._lineBreak = null; + /** @protected */ this._lineBreak = null; - this._fontSize = 0; - this._kerning = 0; + /** @protected */ this._kerning = 0; - this._offsetX = 0; - this._offsetY = 0; + /** @protected */ this._fontFactor = 1; + /** @protected */ this._fontSize = 0; - } + /** @protected */ this._cumulativeWidth = 0; - get typographic(){ + /** @protected */ this._paddingLeft = 0; + /** @protected */ this._paddingRight = 0; - return this._typographic; + /** @protected */ this._marginLeft = 0; + /** @protected */ this._marginRight = 0; } + /** + * @returns {void} + */ resetOffsets() { this._offsetX = this._offsetY = 0; + this._cumulativeWidth = 0; } - /********************************************************************************************************************* - * GETTERS FROM CHARACTER DESCRIPTION - ********************************************************************************************************************/ - - get xadvance() { return this._typographic.xadvance * this._fontFactor; } - - get xoffset() { return this._typographic.xoffset * this._fontFactor; } + /** + * The horizontal distance this inline fills + * @returns {number} + */ + get xadvance() { return 0 } - get yoffset() { return this._typographic.yoffset * this._fontFactor; } + /** + * The offset x of this inline in a line + * @returns {number} + */ + get xoffset() { return 0 } - get width() { return this._typographic.width * this._fontFactor ; } + /** + * The offset y of this inline in a line + * @returns {number} + */ + get yoffset() { return 0 } - get height() { return this._typographic.height * this._fontFactor; } + /** + * + * @returns {number} + */ + get width() { return 0 } /** * - * @return {string} + * @returns {number} */ - get char() { return this._typographic.char; } + get height() { return 0 } + /** + * + * @param {string|null} value + */ set lineBreak( value ){ this._lineBreak = value; } + /** + * + * @returns {string|null} + */ get lineBreak() { return this._lineBreak; } - get anchor() { - - const lineHeight = this._typographic.font.lineHeight; - const lineBase = this._typographic.font.lineBase; - - return ( ( this._typographic.yoffset + this._typographic.height - lineBase ) * this._fontSize ) / lineHeight; - - } + /** + * + * @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 } - - get lineHeight() { return this._typographic.font.lineHeight * this._fontFactor; } - + /** + * + * @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; } - get lineBase() { return this._typographic.font.lineBase * this._fontFactor; } - - set fontFactor( value ){ + /** + * + * @param {number} value + */ + set cumulativeWidth( value ) { - this._fontFactor = value; + this._cumulativeWidth = value; } -} + /** + * + * @return {number} + */ + get marginLeft() { return this._marginLeft; } -;// CONCATENATED MODULE: ./src/font/msdf/MSDFInlineCharacter.js + /** + * + * @param {number} value + */ + set marginLeft( value ) { + this._marginLeft = value; -class MSDFInlineCharacter extends InlineCharacter{ + } /** * - * @param {MSDFTypographyCharacter} characterDesc + * @return {number} */ - constructor( characterDesc ) { + get marginRight() { return this._marginRight; } - super( characterDesc ); + /** + * + * @param {number} value + */ + set marginRight( value ) { - } + this._marginRight = value; - get uv() { return this.typographic.uv; } + } -} + /** + * + * @return {number} + */ + get paddingLeft() { return this._paddingLeft; } -;// CONCATENATED MODULE: ./src/font/msdf/MSDFTypographyCharacter.js + /** + * + * @param {number} value + */ + set paddingLeft( value ) { + this._paddingLeft = value; + } -class MSDFTypographyCharacter extends TypographyCharacter { + /** + * + * @return {number} + */ + get paddingRight() { return this._paddingRight; } /** - * @param {MSDFTypographyFont} fontDescription - * @param {MSDFJsonChar} characterData + * + * @param {number} value */ - constructor( fontDescription, characterData ) { + set paddingRight( value ) { - super(fontDescription); + this._paddingRight = value; - this._char = characterData.char; - this._width = characterData.width; - this._heigth = characterData.height; + } - this._xadvance = characterData.xadvance ? characterData.xadvance : this._width; - this._xoffset = characterData.xoffset ? characterData.xoffset : 0; - this._yoffset = characterData.yoffset ? characterData.yoffset : 0; + /** + * + * @returns {number} + */ + get lineBase() { return 0 } + + /** + * + * @param {number} value + */ + set fontFactor( value ){ + + this._fontFactor = value; + + } +} + +;// CONCATENATED MODULE: ./src/font/InlineGlyph.js + + +//JSDoc related imports +/* eslint-disable no-unused-vars */ + +/* eslint-enable no-unused-vars */ + +class InlineGlyph extends Inline { + + /** + * + * @param {TypographicGlyph} characterDesc + */ + constructor( characterDesc ) { + + super(); + + /** @protected */ this._typographic = characterDesc; + + } + + /** + * + * @returns {TypographicGlyph} + */ + get typographic(){ + + return this._typographic; + + } + + /********************************************************************************************************************* + * GETTERS FROM CHARACTER DESCRIPTION + ********************************************************************************************************************/ + + /** + * @override + * @returns {number} + */ + get xadvance() { return this._typographic.xadvance * this._fontFactor; } + + /** + * @override + * @returns {number} + */ + get xoffset() { return this._typographic.xoffset * this._fontFactor; } + + /** + * @override + * @returns {number} + */ + get yoffset() { return this._typographic.yoffset * this._fontFactor; } + + /** + * @override + * @returns {number} + */ + get width() { return this._typographic.width * this._fontFactor ; } + + /** + * @override + * @returns {number} + */ + get height() { return this._typographic.height * this._fontFactor; } + + /** + * + * @return {string} + */ + get char() { return this._typographic.char; } + + /** + * @override + * @returns {number} + */ + get anchor() { + + const lineHeight = this._typographic.font.lineHeight; + const lineBase = this._typographic.font.lineBase; + + return ( ( this._typographic.yoffset + this._typographic.height - lineBase ) * this._fontSize ) / lineHeight; + + } + + /** + * @override + * @returns {number} + */ + get lineHeight() { return this._typographic.font.lineHeight * this._fontFactor; } + + /** + * @override + * @returns {number} + */ + get lineBase() { return this._typographic.font.lineBase * this._fontFactor; } + + +} + +;// CONCATENATED MODULE: ./src/font/msdf/MSDFInlineGlyph.js + + +//JSDoc related imports +/* eslint-disable no-unused-vars */ + +/* eslint-enable no-unused-vars */ + +/** + * @extends InlineGlyph + */ +class MSDFInlineGlyph extends InlineGlyph{ + + /** + * + * @param {MSDFTypographicGlyph} characterDesc + */ + constructor( characterDesc ) { + + super( characterDesc ); + + } + + /** + * + * @returns {{left:number, right:number, top:number, bottom:number}|null} + */ + get uv() { return this.typographic.uv; } + +} + +;// CONCATENATED MODULE: ./src/font/msdf/MSDFTypographicGlyph.js + + + +//JSDoc related imports +/* eslint-disable no-unused-vars */ + +/* eslint-enable no-unused-vars */ - // Msdf requires uvs - this._uv = null; + +class MSDFTypographicGlyph extends TypographicGlyph { + + /** + * @param {MSDFTypographicFont} fontDescription + * @param {import('./MSDFFontVariant').MSDFJsonChar} characterData + */ + constructor( fontDescription, characterData ) { + + super(fontDescription); + + this._char = characterData.char; + this._width = characterData.width; + this._heigth = characterData.height; + + this._xadvance = characterData.xadvance ? characterData.xadvance : this._width; + this._xoffset = characterData.xoffset ? characterData.xoffset : 0; + this._yoffset = characterData.yoffset ? characterData.yoffset : 0; + + // Msdf requires uvs + this._uv = null; if( !isNaN( characterData.x ) ) { // transform absolute pixel values into uv values [0,1] @@ -2153,42 +2009,56 @@ class MSDFTypographyCharacter extends TypographyCharacter { } /** - * Abstraction - * - * @returns {MSDFInlineCharacter} + * @override + * @returns {MSDFInlineGlyph} */ - asInlineCharacter() { + asInlineGlyph() { - return new MSDFInlineCharacter( this ); + return new MSDFInlineGlyph( this ); } } -;// CONCATENATED MODULE: ./src/font/msdf/MSDFGeometryCharacter.js +;// CONCATENATED MODULE: ./src/font/msdf/MSDFGeometricGlyph.js + +//JSDoc related imports +/* eslint-disable no-unused-vars */ + +/* eslint-enable no-unused-vars */ -class MSDFGeometryCharacter extends external_THREE_namespaceObject.PlaneBufferGeometry { +class MSDFGeometricGlyph extends external_THREE_namespaceObject.PlaneBufferGeometry { /** * - * @param {MSDFInlineCharacter} inline + * @param {MSDFInlineGlyph} inline */ - constructor( inline ) { + constructor( inline, segments = 1 ) { + - super( inline.width, inline.height ); + // default w & h segments + let wS = 1, hS=1; + + // If charOBJ, try to distribute segments proportionally + const typographicFontSize = inline.typographic.font.size; + + wS = Math.ceil((inline.typographic.width / typographicFontSize) * segments); + hS = Math.ceil((inline.typographic.height / typographicFontSize) * segments); + + super( inline.width, inline.height, wS, hS ); // If inline has UVs if ( inline.uv ) { - this.mapUVs( inline ); + this._mapUVs( inline ); - this.transformGeometry( inline ); + this._transformGeometry( inline ); // White spaces (we don't want our plane geometry to have a visual width nor a height) } else { - this.nullifyUVs(); + this._nullifyUVs(); this.scale( 0, 0, 1 ); @@ -2201,54 +2071,49 @@ class MSDFGeometryCharacter extends external_THREE_namespaceObject.PlaneBufferGe /** * Compute the right UVs that will map the MSDF texture so that the passed character * will appear centered in full size + * @param {MSDFInlineGlyph} inline * @private */ - mapUVs( inline ) { - - const uvAttribute = this.attributes.uv; - - for ( let i = 0; i < uvAttribute.count; i++ ) { - - let u = uvAttribute.getX( i ); - let v = uvAttribute.getY( i ); - - [ u, v ] = ( () => { - - switch ( i ) { + _mapUVs( inline ) { - case 0 : - // return [ xMin, yMax ]; - return [ inline.uv.left, inline.uv.bottom ]; - case 1 : - // return [ xMax, yMax ]; - return [ inline.uv.right, inline.uv.bottom ]; - case 2 : - // return [ xMin, yMin ]; - return [ inline.uv.left, inline.uv.top ]; - case 3 : - // return [ xMax, yMin ]; - return [ inline.uv.right, inline.uv.top ]; - } + const width = inline.uv.right - inline.uv.left; + const height = inline.uv.bottom - inline.uv.top; - } )(); + const originalUvArray = this.getAttribute('uv').array.slice() - uvAttribute.setXY( i, u, v ); + const uvGlyph = []; + for (let i = 0; i < originalUvArray.length; i += 2) { + const u = originalUvArray[i]; + const v = originalUvArray[i + 1]; + uvGlyph.push(inline.uv.left + width * u); + uvGlyph.push(inline.uv.top + height * v); } + this.setAttribute('uvG', new external_THREE_namespaceObject.BufferAttribute(new Float32Array(uvGlyph), 2)); } - /** Set all UVs to 0, so that none of the glyphs on the texture will appear */ - nullifyUVs() { - - const uvAttribute = this.attributes.uv; - - for ( let i = 0; i < uvAttribute.count; i++ ) { + /** + * Set all UVs to 0, so that none of the glyphs on the texture will appear + * @private + * */ + _nullifyUVs() { - uvAttribute.setXY( i, 0, 0 ); + // const uvAttribute = this.attributes.uv; + // + // for ( let i = 0; i < uvAttribute.count; i++ ) { + // + // uvAttribute.setXY( i, 0, 0 ); + // + // } + const uvGlyph = []; + const length = this.getAttribute('uv').array.length; + for ( let i = 0; i < length; i++ ) { + uvGlyph.push(0); } + this.setAttribute('uvG', new external_THREE_namespaceObject.BufferAttribute(new Float32Array(uvGlyph), 2)); } @@ -2256,9 +2121,10 @@ class MSDFGeometryCharacter extends external_THREE_namespaceObject.PlaneBufferGe * * @TODO: Apply pivot properties when splitText isset * Gives the previously computed scale and offset to the geometry - * @param {MSDFInlineCharacter} inline + * @param {MSDFInlineGlyph} inline + * @private */ - transformGeometry( inline ) { + _transformGeometry( inline ) { // @@ -2273,2105 +2139,4278 @@ class MSDFGeometryCharacter extends external_THREE_namespaceObject.PlaneBufferGe } -;// CONCATENATED MODULE: ./src/font/msdf/MSDFFontVariant.js +;// CONCATENATED MODULE: ./src/font/msdf/renderers/ShaderChunks/msdf-alphaglyph.pars.vertex.glsl.js +/* harmony default export */ const msdf_alphaglyph_pars_vertex_glsl = (/* glsl */` +attribute vec2 uvG; +varying vec2 vUvG; +`); + +;// CONCATENATED MODULE: ./src/font/msdf/renderers/ShaderChunks/msdf-alphaglyph.vertex.glsl.js +/* harmony default export */ const msdf_alphaglyph_vertex_glsl = (/* glsl */` +vUvG = uvG; +`); + +;// CONCATENATED MODULE: ./src/font/msdf/renderers/ShaderChunks/msdf-offsetglyph.vertex.glsl.js +/* harmony default export */ const msdf_offsetglyph_vertex_glsl = (/* glsl */` +gl_Position.z -= 0.00001; +`); + +;// CONCATENATED MODULE: ./src/font/msdf/renderers/ShaderChunks/msdf-alphaglyph.pars.fragment.glsl.js +/* harmony default export */ const msdf_alphaglyph_pars_fragment_glsl = (/* glsl */` +varying vec2 vUvG; +uniform sampler2D glyphMap; +uniform vec2 unitRange; +// functions from the original msdf repo: +// https://github.com/Chlumsky/msdfgen#using-a-multi-channel-distance-field +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} +float screenPxRange() { + // precomputed unitRange as recommended by Chlumsky + // vec2 unitRange = vec2(pxRange)/vec2(textureSize(glyphMap, 0)); + vec2 screenTexSize = vec2(1.0)/fwidth(vUvG); + return max(0.5*dot(unitRange, screenTexSize), 1.0); +} +float tap(vec2 offsetUV) { + vec3 msd = texture( glyphMap, offsetUV ).rgb; + float sd = median(msd.r, msd.g, msd.b); + float screenPxDistance = screenPxRange() * (sd - 0.5); + float alpha = clamp(screenPxDistance + 0.5, 0.0, 1.0); + return alpha; +} +`); +;// CONCATENATED MODULE: ./src/font/msdf/renderers/ShaderChunks/msdf-alphaglyph.fragment.glsl.js +/* harmony default export */ const msdf_alphaglyph_fragment_glsl = (/* glsl */` + float alpha; +#ifdef NO_RGSS + alpha = tap( vUvG ); +#else + // shader-based supersampling based on https://bgolus.medium.com/sharper-mipmapping-using-shader-based-supersampling-ed7aadb47bec + // per pixel partial derivatives + vec2 dx = dFdx(vUvG); + vec2 dy = dFdy(vUvG); + // rotated grid uv offsets + vec2 uvOffsets = vec2(0.125, 0.375); + vec2 offsetUV = vec2(0.0, 0.0); + // supersampled using 2x2 rotated grid + alpha = 0.0; + offsetUV.xy = vUvG + uvOffsets.x * dx + uvOffsets.y * dy; + alpha += tap(offsetUV); + offsetUV.xy = vUvG - uvOffsets.x * dx - uvOffsets.y * dy; + alpha += tap(offsetUV); + offsetUV.xy = vUvG + uvOffsets.y * dx - uvOffsets.x * dy; + alpha += tap(offsetUV); + offsetUV.xy = vUvG - uvOffsets.y * dx + uvOffsets.x * dy; + alpha += tap(offsetUV); + alpha *= 0.25; +#endif -class MSDFFontVariant extends FontVariant { + alpha = clamp( alpha, 0.0, 1.0 ); - constructor( weight, style, json, texture ) { +#ifdef INVERT_ALPHA - super(weight, style); + alpha = 1.0 - alpha; - if ( json.pages ) { +#endif - this._buildData( json ); + diffuseColor.a *= alpha; +`); - } else { +;// CONCATENATED MODULE: ./src/utils/mediator/transformers/MaterialTransformers.js - _loadJson( this, json ); +/** + * Transfer the alphaTest value from MeshUIComponent to material + * @type {import('../Mediator').MediationTransformer} + */ +const alphaTestTransformer = function ( target, targetProperty, value) { - } + // set the value in the material + target.alphaTest = value; - if ( texture instanceof external_THREE_namespaceObject.Texture ) { + toPreprocessorTriggerTransformer(target, 'USE_ALPHATEST', value === 0 ? '' : null ); - this._buildTexture( texture ); +} - } else { +/** + * Transform a value as a preprocessor trigger + * @type {import('../Mediator').MediationTransformer} + */ +const toPreprocessorTriggerTransformer = function ( target, targetProperty, value) { - _loadTexture( this, texture ); + if ( value ) { + + if( target.defines[targetProperty] === undefined ) { + + target.defines[targetProperty] = ''; + target.needsUpdate = true; } - this._checkReadiness(); + } else if( target.defines[targetProperty] !== undefined ) { + + delete target.defines[targetProperty]; + target.needsUpdate = true; } +} - get texture() { +/** + * Transform a value as a preprocessor value + * @type {import('../Mediator').MediationTransformer} + */ +const asPreprocessorValueTransformer = function ( target, targetProperty, value) { - return this._texture; + // abort if nothing to update, same value + if( target.defines[targetProperty] && target.defines[targetProperty] === value ) return; - } + // or change the preprocessor and update + target.defines[targetProperty] = value; + target.needsUpdate = true; - /** - * - * @param {MSDFJson} json - * @private - */ - _buildData( json ) { +} - this._font = new MSDFTypographyFont( json ); +/** + * Transform a value as a uniform or userData value + * @type {import('../Mediator').MediationTransformer} + */ +const uniformOrUserDataTransformer = function( material, property, value ) { - this._kernings = this._buildKerningPairs( json ); - this._chars = this._buildCharacters( json ); - this._chars[ " " ] = this._buildCharacterWhite( json ); + if( material.userData[property] ) { - this._size = json.info.size; - this._lineHeight = json.common.lineHeight; - this._lineBase = json.common.base; + material.userData[property].value = value; + + }else{ + + material.uniforms[property].value = value; } - /** - * - * @param texture - * @private - */ - _buildTexture( texture ) { +} - this._texture = texture; +const toUserDataTransformer = function( material, property, value ) { - texture.generateMipmaps = false; - texture.minFilter = external_THREE_namespaceObject.LinearFilter; - texture.magFilter = external_THREE_namespaceObject.LinearFilter; + material.userData[property].value = value; - texture.needsUpdate = true; +} - } +;// CONCATENATED MODULE: ./src/font/msdf/utils/MSDFFontMaterialUtils.js - /** - * - * @param {MSDFInlineCharacter} inline - * @returns {MSDFGeometryCharacter} - */ - getGeometryCharacter( inline ) { - return new MSDFGeometryCharacter( inline ); - } - /** - * Abstraction implementation - * - * @returns {boolean} - * @private - */ - _readyCondition() { - return this._chars && this._texture; - } - /** - * Ensure that each font variant has its kerning dictionary - * @see src/font/msdf/FontVariantMSDF.js for an implementation - * - * @param {MSDFJson} json - * - * @returns {Object.} - * @private - */ - _buildKerningPairs( json ) { +/* eslint-disable no-unused-vars */ - const friendlyKernings = {}; - // Loop through each kernings pairs defined in msdf json - for ( let i = 0; i < json.kernings.length; i++ ) { +/* eslint-enable no-unused-vars */ - const kerning = json.kernings[ i ]; +/** + * MSDFFontMaterialUtils provides utilities + * for customizing other threejs or custom materials + * into a three-mesh-ui MSDFFontMaterial + */ +class MSDFFontMaterialUtils { - // ignore zero kerned glyph pair - if ( kerning.amount === 0 ) continue; + /** + * Alter a material options with required fontMaterial options and or default values + * @param {Object.} materialOptions + */ + static ensureMaterialOptions( materialOptions ) { + materialOptions.transparent = true; + materialOptions.alphaTest = materialOptions.alphaTest || 0.02; + } - // Build and store the glyph paired characters "ij","WA", ... as keys, referecing their kerning amount - const glyphPair = String.fromCharCode( kerning.first, kerning.second ); + /** + * As three-mesh-ui FontMaterial relies on webgl preprocessors, + * lets force the material to have a proper defines object + * @param {Material|ShaderMaterial} threeMaterial + */ + static ensureDefines( threeMaterial ) { + if ( !threeMaterial.defines ) { + threeMaterial.defines = {}; + } + } - // This would then be available for fast access - friendlyKernings[ glyphPair ] = kerning.amount; + /** + * + * @param {Material|ShaderMaterial} threeMaterial + * @param {Object.} materialOptions + */ + static ensureUserData( threeMaterial, materialOptions ) { + threeMaterial.userData.glyphMap = { value: materialOptions.glyphMap }; + threeMaterial.userData.unitRange = { value: new external_THREE_namespaceObject.Vector2() }; + } - } + /** + * + * @param {any} shader + * @param {Material|ShaderMaterial} threeMaterial + */ + static bindUniformsWithUserData( shader, threeMaterial ) { - // update the font to keep it - return friendlyKernings; + shader.uniforms.glyphMap = threeMaterial.userData.glyphMap; + shader.uniforms.unitRange = threeMaterial.userData.unitRange; + } + /** + * + * @param shader + */ + static injectShaderChunks( shader ) { + MSDFFontMaterialUtils.injectVertexShaderChunks( shader ); + MSDFFontMaterialUtils.injectFragmentShaderChunks( shader ); } + /** + * + * @param shader + */ + static injectVertexShaderChunks( shader ) { + shader.vertexShader = shader.vertexShader.replace( + '#include ', + '#include \n' + msdf_alphaglyph_pars_vertex_glsl + ); + + // vertex chunks + shader.vertexShader = shader.vertexShader.replace( + '#include ', + '#include \n' + msdf_alphaglyph_vertex_glsl + ) + + shader.vertexShader = shader.vertexShader.replace( + '#include ', + '#include \n' + msdf_offsetglyph_vertex_glsl + ) + } /** * - * @param {MSDFJson} json - * @private + * @param shader */ - _buildCharacters( json ) { + static injectFragmentShaderChunks( shader ) { + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + '#include \n' + msdf_alphaglyph_pars_fragment_glsl + ) - const friendlyChars = {}; + // fragment chunks + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + '#include \n' + msdf_alphaglyph_fragment_glsl + ) + } - for ( let i = 0; i < json.chars.length; i++ ) { - const charOBJ = json.chars[ i ]; - friendlyChars[ charOBJ.char ] = new MSDFTypographyCharacter( this._font, charOBJ ); - } + /** + * Mix a threejs Material into a three-mesh-ui FontMaterial + * @param {typeof Material|ShaderMaterial} materialClass + * @returns {typeof Material|ShaderMaterial} + */ + static from( materialClass ) { - return friendlyChars; + return class extends materialClass { + + /** + * + * @abstract + * @returns {Object.<{m:string, t?:(fontMaterial:Material|ShaderMaterial, materialProperty:string, value:any) => void}>} + */ + static get fontMaterialProperties() { + return MSDFFontMaterialUtils.mediation; + } + + constructor( options = {} ) { + + // same as FontMaterial extension + MSDFFontMaterialUtils.ensureMaterialOptions( options ); + super( options ); + MSDFFontMaterialUtils.ensureDefines( this ); + MSDFFontMaterialUtils.ensureUserData( this, options ); + + // defines two internal properties in order to kept + // user allowed to use onBeforeCompile for its own stuff + // 1- store an callback for user + /* eslint-disable no-unused-vars */ + this._userDefinedOnBeforeCompile = (shader) => {}; + /* eslint-enable no-unused-vars */ + // 2- store the cumulative callback + this._onBeforeCompile = this._cumulativeOnBeforeCompile; + } + + //////////////////////////// + // OnBeforeCompile Override + /////////////////////////// + + /** + * Override the setter of onBeforeCompile in order to never overwrite + * the three-mesh-ui fontMaterial onBeforeCompile + * @param { (shader:any) => void }fct + */ + set onBeforeCompile( fct ) { + // only store it as userDefinedCallback + this._userDefinedOnBeforeCompile = fct; + } + + /** + * Override the getter of onBeforeCompile in order to + * always deliver the cumulativeCallbacks to threejs + * @returns { (shader:any) => void } + */ + get onBeforeCompile() { + return this._onBeforeCompile; + } + + /** + * + * On before compile that first run three-mesh-ui fontMaterial + * then user defined onBeforeCompile + * @param shader + * @private + */ + _cumulativeOnBeforeCompile = ( shader ) => { + // bind uniforms + MSDFFontMaterialUtils.bindUniformsWithUserData( shader, this ); + // inject both vertex and fragment shaders + MSDFFontMaterialUtils.injectShaderChunks( shader ); + + // user defined additional onBeforeCompile + this._userDefinedOnBeforeCompile( shader ); + } + } } /** * - * @param {MSDFJson} json - * @private + * @returns {Object<{m: string, t?: (function((Material|ShaderMaterial), string, *): void)}>} */ - _buildCharacterWhite( json ) { - return new MSDFTypographyCharacter( this._font, - { - char: ' ', - width: json.info.size / 3, - height: json.info.size * 0.7, - }); - } + static get mediation() { -} + return mediationDefinitions; -/*********************************************************************************************************************** - * INTERNAL STUFF - **********************************************************************************************************************/ + } +} /** - * Load a msdf json then build fontVariant data - * - * @param {FontVariant} fontVariant - * @param {string} jsonUrl + * Convert a fontVariant to a material glyphMap texture + * @type {(fontMaterial:Material|ShaderMaterial, materialProperty:string, value:any) => void } * @private */ -function _loadJson( fontVariant, jsonUrl ) { +const _fontToGlyphMapTransformer = function( fontMaterial, materialProperty, value) { - new external_THREE_namespaceObject.FileLoader().setResponseType( 'json' ) .load( jsonUrl, ( response ) => { + const texture = value ? value.texture : null; + const unitRange = value ? value.unitRange : new external_THREE_namespaceObject.Vector2(); - fontVariant._buildData( response ); - fontVariant._checkReadiness(); + if( fontMaterial[materialProperty] !== undefined ) { - } ); + fontMaterial.glyphMap = texture; + fontMaterial.unitRange = unitRange; + return; + } + + if( fontMaterial.userData && fontMaterial.userData.glyphMap ) { + + fontMaterial.userData.glyphMap.value = texture; + fontMaterial.userData.unitRange.value = unitRange; + + } } /** - * Load a msdf texture then build texture * - * @param {FontVariant} fontVariant - * @param {string} textureUrl + * @type {(fontMaterial:Material|ShaderMaterial, materialProperty:string, value:any) => void } * @private */ -function _loadTexture( fontVariant, textureUrl ) { +const _RGSSTransformer = function( fontMaterial, materialProperty, value){ - new external_THREE_namespaceObject.TextureLoader().load( textureUrl, ( texture ) => { + if ( !value ) { - fontVariant._buildTexture( texture ); - fontVariant._checkReadiness(); + fontMaterial.defines['NO_RGSS'] = ''; - } ); + } else { + + delete fontMaterial.defines['NO_RGSS']; + + } + + fontMaterial.needsUpdate = true; } -/** - * @typedef {Object} MSDFJson - * @see https://www.angelcode.com/products/bmfont/doc/file_format.html - * - * @property {MSDFJsonInfo} info - * @property {MSDFJsonCommon} common - * @property {Array.} pages - * @property {Array.} chars - * @property {Array.} kernings - */ /** * - * @typedef {Object} MSDFJsonInfo - * @see https://www.angelcode.com/products/bmfont/doc/file_format.html - * - * @property {string} face This is the name of the true type font. - * @property {number} size The size of the true type font. - * @property {boolean} bold The font is bold. - * @property {boolean} italic The font is italic. - * @property {string[]} charset The name of the OEM charset used (when not unicode). - * @property {boolean} unicode Set to 1 if it is the unicode charset. - * @property {number} stretchH The font height stretch in percentage. 100% means no stretch. - * @property {number} smooth Set to 1 if smoothing was turned on. - * @property {number} aa The supersampling level used. 1 means no supersampling was used. - * @property {Array.} padding TThe padding for each character (up, right, down, left). - * @property {Array.} spacing The spacing for each character (horizontal, vertical). - * @property {number} outline (not found) The outline thickness for the characters. + * @type {Object.<{m:string, t?:(fontMaterial:Material|ShaderMaterial, materialProperty:string, value:any) => void}>} */ +const mediationDefinitions = { + alphaTest: { m: 'alphaTest', t: alphaTestTransformer }, + _side: { m: 'side' }, + // side: { m: 'side' }, + _font: { m: "glyphMap", t: _fontToGlyphMapTransformer }, + fontColor: { m: 'color' }, + fontOpacity: { m: 'opacity' }, + fontSupersampling: { m: 'NO_RGSS', t: _RGSSTransformer }, + invertAlpha: { m: 'INVERT_ALPHA', t: toPreprocessorTriggerTransformer }, +} + +;// CONCATENATED MODULE: ./src/font/msdf/renderers/ShaderLib/msdf-fontmaterial.glsl.js -/** - * - * @typedef {Object} MSDFJsonCommon - * @see https://www.angelcode.com/products/bmfont/doc/file_format.html - * - * @property {number} lineHeight This is the distance in pixels between each line of text. - * @property {number} base The number of pixels from the absolute top of the line to the base of the characters. - * @property {number} scaleW The width of the texture, normally used to scale the x pos of the character image. - * @property {number} scaleH The height of the texture, normally used to scale the y pos of the character image. - * @property {number} pages The number of texture pages included in the font. - * @property {boolean} packed - * @property {number} alphaChnl - * @property {number} redChnl - * @property {number} greenChnl - * @property {number[]} blueChnl - */ -/** - * - * @typedef {Object} MSDFJsonPage - * @see https://www.angelcode.com/products/bmfont/doc/file_format.html - * - * @property {string} id The page id. - * @property {string} file The texture file name. - */ -/** - * - * @typedef {Object} MSDFJsonChar - * @see https://www.angelcode.com/products/bmfont/doc/file_format.html - * - * @property {number} id The character id. - * @property {number} index The character index. - * @property {string} char The character. - * @property {number} x The left position of the character image in the texture. - * @property {number} y The top position of the character image in the texture. - * @property {number} width The width of the character image in the texture. - * @property {number} height The height of the character image in the texture. - * @property {number} xoffset How much the current position should be offset when copying the image from the texture to the screen. - * @property {number} yoffset How much the current position should be offset when copying the image from the texture to the screen. - * @property {number} xadvance How much the current position should be advanced after drawing the character. - * @property {string} page The texture page where the character image is found. - * @property {number} chnl The texture channel where the character image is found (1 = blue, 2 = green, 4 = red, 8 = alpha, 15 = all channels). - */ /** * - * @typedef {Object} MSDFJsonKerning - * @see https://www.angelcode.com/products/bmfont/doc/file_format.html - * - * @property {number} first The first character id. - * @property {number} second The second character id. - * @property {number} amount How much the x position should be adjusted when drawing the second character immediately following the first. - * + * @type {string} */ +const vertexShader = /* glsl */` +${msdf_alphaglyph_pars_vertex_glsl} +#include +void main() { + ${msdf_alphaglyph_vertex_glsl} + #include + #include + ${msdf_offsetglyph_vertex_glsl} + #include +} +` -;// CONCATENATED MODULE: ./src/font/FontFamily.js +/** + * + * @type {string} + */ +const fragmentShader = /* glsl */` +uniform vec3 diffuse; +uniform float opacity; +${msdf_alphaglyph_pars_fragment_glsl} +#include +#include +void main() { + // instead of : vec4 diffuseColor + vec4 diffuseColor = vec4( diffuse, opacity ); + ${msdf_alphaglyph_fragment_glsl} + #include + // instead of + gl_FragColor = diffuseColor; + #include +} +` +;// CONCATENATED MODULE: ./src/font/msdf/materials/MSDFFontMaterial.js -class FontFamily extends external_THREE_namespaceObject.EventDispatcher { - /** - * - * @param name - */ - constructor( name ) { - super(); +// JSDoc related import +/* eslint-disable no-unused-vars */ - this._name = name; - this._variants = []; +/* eslint-enable no-unused-vars */ - this._isReady = false; +const ALPHA_TEST = 0.02; - } - get isReady() { return this._isReady; } +/** + * This material implements the msdf rendering shader + */ +class MSDFFontMaterial extends external_THREE_namespaceObject.ShaderMaterial { /** - * - * @param weight - * @param style - * @param json - * @param texture - * @param override + * This static method is mandatory for extending ThreeMeshUI.MSDFFontMaterial + * It will provide a transfer description for properties from ThreeMeshUI.Text to THREE.Material + * @see {MSDFFontMaterialUtils.mediation} + * @returns {Object.<{m:string, t?:(fontMaterial:Material|ShaderMaterial, materialProperty:string, value:any) => void}>} */ - addVariant( weight, style, json, texture, override = false){ + static get mediation() { - if( override || !this.getVariant( weight, style) ){ + return MSDFFontMaterialUtils.mediation; - this._isReady = false; + } - const newVariant = new MSDFFontVariant( weight, style, json, texture); + constructor( materialOptions = {} ) { - this._variants.push( newVariant ); + super( { - if( !newVariant.isReady ){ + uniforms: { + 'glyphMap': { value: null }, // texture + 'diffuse': { value: null }, // vec3 + 'opacity': { value: 1 }, + 'unitRange': { value: new external_THREE_namespaceObject.Vector2(0,0) }, // vec2 + 'alphaTest': { value: ALPHA_TEST }, + }, + transparent: true, + clipping: true, + vertexShader: vertexShader, + fragmentShader: fragmentShader, + extensions: { + derivatives: true + }, + } ); - newVariant.addEventListener( "ready", this._checkReadiness ) + // webgl preprocessor AlphaTest set by default + this.defines[ 'USE_ALPHATEST' ] = ''; + this.needsUpdate = true; - } else { + // initiate additional properties + this.noRGSS = materialOptions.noRGSS || false; - this._checkReadiness(); + } - } + /** + * The color will be the diffuse uniform + * @returns {Color} + */ + get color() { - } else { + return this.uniforms.diffuse.value; - console.warn(`FontFamily('${this._name}')::addVariant() - Variant(${weight}, ${style}) already exists.`); + } - } + /** + * + * @param {Color} v + */ + set color( v ) { - return this; + this.uniforms.diffuse.value = v; } /** * - * @param {FontVariant} variantImplementation - * @param {boolean} [override=false] + * @param {number} v */ - addCustomImplementationVariant( variantImplementation, override = false){ + set opacity( v ) { - if( override || !this.getVariant( variantImplementation.weight, variantImplementation.style) ){ + if( this.uniforms ) + this.uniforms.opacity.value = v; - this._isReady = false; + } - this._variants.push( variantImplementation ); + /** + * The color will be the diffuse uniform + * @returns {number} + */ + get opacity() { - if( !variantImplementation.isReady ){ + return this.uniforms.opacity.value; - variantImplementation.addEventListener( "ready", this._checkReadiness ) + } - } else { - this._checkReadiness(); - } - } else { + /** + * The color will be the diffuse uniform + * @returns {Vector2} + */ + get unitRange() { - console.warn(`FontFamily('${this._name}')::addCustomImplementationVariant() - Variant(${variantImplementation.weight}, ${variantImplementation.style}) already exists.`); + return this.uniforms.unitRange.value; - } + } - return this; + /** + * + * @param {Vector2} v + */ + set unitRange( v ) { + + this.uniforms.unitRange.value.copy( v ); } /** * - * @param weight - * @param style - * @returns {FontVariant} + * @returns {Texture} */ - getVariant( weight, style ){ + get glyphMap() { - return this._variants.find( fontVariant => fontVariant.weight === weight && fontVariant.style === style ); + return this.uniforms.glyphMap.value; } - get name(){ return this._name; } + /** + * + * @param {Texture} v + */ + set glyphMap( v ) { - _checkReadiness = () => { + this.uniforms.glyphMap.value = v; - if( this._variants.every( v => v.isReady ) ) { + } - FontFamily_setReady( this ); + /** + * Is this a default fontMaterial instance + * @returns {boolean} + */ + get isDefault() { - } + return this.constructor === MSDFFontMaterial; + + } + + /** + * + * @returns {number} + */ + get alphaTest() { + return this.uniforms.alphaTest.value; + } + /** + * + * @param {number} v + */ + set alphaTest( v ) { + this.uniforms.alphaTest.value = v; } } -const FontFamily_readyEvent = { type: 'ready' }; +;// CONCATENATED MODULE: ./src/font/msdf/MSDFFontVariant.js -/** - * Set the ready status of a fontVariant - * @param {FontFamily} fontFamily - * @private - */ -function FontFamily_setReady( fontFamily ) { - fontFamily._isReady = true; - fontFamily.dispatchEvent( FontFamily_readyEvent ); -} -;// CONCATENATED MODULE: ./src/font/FontLibrary.js -const _fontFamilies = {}; +//JSDoc related imports /* eslint-disable no-unused-vars */ +/* eslint-enable no-unused-vars */ + /** - * - * @param {FontFamily} fontFamily - * @returns {Promise} + * @extends {FontVariant} */ -function prepare( fontFamily ) { +class MSDFFontVariant extends FontVariant { - /** - * - * @type {FontFamily[]} - */ - const families = [ ...arguments ]; + constructor( weight, style, json, texture ) { - // Check all family are right instance - families.forEach( f => { + super(weight, style); - if( !(f instanceof FontFamily) ) { + // provide default values + this._unitRange = new external_THREE_namespaceObject.Vector2( 1, 1 ); - throw new Error(`FontLibrary::prepare() - One of the provided parameter is not a FontFamily. Instead ${typeof f} given.`); + if ( json.pages ) { - } + this._buildData( json ); - }) + } else { - /** - * Check that all provided families are loaded - * @returns {boolean} - */ - const areAllLoaded = function() { + _loadJson( this, json ); - return families.every( f => f.isReady ); + } - } + if ( texture instanceof external_THREE_namespaceObject.Texture ) { - // @TODO: Should handle possible rejection - return new Promise((resolve,reject)=>{ + this._buildTexture( texture ); - // Direct resolve if all loaded - if ( areAllLoaded() ){ + } else { - resolve(); + _loadTexture( this, texture ); - } else { + } - // Add listener on each family not ready - for ( let i = 0; i < families.length; i++ ) { + this._defaultMaterialClass = MSDFFontMaterial; - const family = families[ i ]; - if( !family.isReady ){ - family.addEventListener( "ready" , ()=> { + this._checkReadiness(); - // Resolve if all other families are loaded - if( areAllLoaded() ) { + } - resolve(); - } + get texture() { - }); + return this._texture; - } + } - } + get unitRange() { - } + return this._unitRange; - }); + } -} + /** + * @param {Function.} v + * @override + */ + set fontMaterial( v ) { -/* eslint-enable no-unused-vars */ + this._defaultMaterialClass = v; + } -/** - * - * @param {string} name - * @returns {FontFamily} - */ -function addFontFamily( name ) { + /** + * + * @override + * @returns {Function.} + */ + get fontMaterial() { + + return this._defaultMaterialClass; - if ( _fontFamilies[ name ] ) { - console.error( `FontLibrary::addFontFamily - Font('${name}') is already registered` ); } - _fontFamilies[ name ] = new FontFamily( name ); + /** + * + * @param {MSDFJson} json + * @private + */ + _buildData( json ) { - return _fontFamilies[ name ]; + this._font = new MSDFTypographicFont( json ); -} + this._kernings = this._buildKerningPairs( json ); + this._chars = this._buildCharacters( json ); + this._chars[ " " ] = this._buildCharacterWhite( json ); -/** - * - * @param {string} name - * @returns {FontFamily} - */ -function getFontFamily( name ) { + this._size = json.info.size; + this._lineHeight = json.common.lineHeight; + this._lineBase = json.common.base; - return _fontFamilies[ name ]; + this._distanceRange = json.distanceField.distanceRange; -} + // precompute the unit range as recommended by chlumsky + // @see https://github.com/Chlumsky/msdfgen + // "I would suggest precomputing unitRange as a uniform variable instead of pxRange for better performance." + this._unitRange = new external_THREE_namespaceObject.Vector2(this._distanceRange, this._distanceRange) + .divide( new external_THREE_namespaceObject.Vector2( json.common.scaleW, json.common.scaleH ) ); + } + /** + * + * @param texture + * @private + */ + _buildTexture( texture ) { -/** - * - * @param { (fontVariant:FontVariant, character:string ) => string|null } handler - */ -function setMissingCharacterHandler( handler ) { + texture.generateMipmaps = false; + texture.minFilter = external_THREE_namespaceObject.LinearFilter; + texture.magFilter = external_THREE_namespaceObject.LinearFilter; - _missingCharacterHandler = handler; + texture.needsUpdate = true; -} + } -// + /** + * + * @param {MSDFInlineGlyph} inline + * @returns {MSDFGeometricGlyph} + */ + getGeometricGlyph( inline, segments = 1 ) { -const FontLibrary = { - addFontFamily, - getFontFamily, - prepare, - setMissingCharacterHandler, - missingCharacter -}; + return new MSDFGeometricGlyph( inline, segments ); -/* harmony default export */ const font_FontLibrary = (FontLibrary); + } -/** - * - * @type { (fontVariant:FontVariant, character:string ) => string|null } - * @private - */ -let _missingCharacterHandler = function ( fontVariant, character ) { + /** + * Abstraction implementation + * + * @returns {boolean} + * @private + */ + _readyCondition() { - console.error( `The character '${character}' is not included in the font characters set.` ); + return this._chars && this._texture; - // return a glyph has fallback - return " "; + } -}; + /** + * Ensure that each font variant has its kerning dictionary + * @see src/font/msdf/FontVariantMSDF.js for an implementation + * + * @param {MSDFJson} json + * + * @returns {Object.} + * @private + */ + _buildKerningPairs( json ) { -/** - * - * @param {FontVariant} fontVariant - * @param {string} character - * - * @returns {string} - */ -function missingCharacter( fontVariant, character ) { + const friendlyKernings = {}; - // Execute the user defined handled - return _missingCharacterHandler( fontVariant, character ); + // Loop through each kernings pairs defined in msdf json + for ( let i = 0; i < json.kernings.length; i++ ) { -} + const kerning = json.kernings[ i ]; + // ignore zero kerned glyph pair + if ( kerning.amount === 0 ) continue; + // Build and store the glyph paired characters "ij","WA", ... as keys, referecing their kerning amount + const glyphPair = String.fromCharCode( kerning.first, kerning.second ); -;// CONCATENATED MODULE: ./src/components/core/UpdateManager.js -/** - * 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(). + // This would then be available for fast access + friendlyKernings[ glyphPair ] = kerning.amount; + + } + + // update the font to keep it + return friendlyKernings; + + } + + + /** + * + * @param {MSDFJson} json + * @private + */ + _buildCharacters( json ) { + + const friendlyChars = {}; + + for ( let i = 0; i < json.chars.length; i++ ) { + const charOBJ = json.chars[ i ]; + + friendlyChars[ charOBJ.char ] = new MSDFTypographicGlyph( this._font, charOBJ ); + + } + + return friendlyChars; + + } + + /** + * + * @param {MSDFJson} json + * @private + */ + _buildCharacterWhite( json ) { + return new MSDFTypographicGlyph( this._font, + { + char: ' ', + width: json.info.size / 3, + height: json.info.size * 0.7, + }); + } + +} + +/*********************************************************************************************************************** + * INTERNAL STUFF + **********************************************************************************************************************/ + + +/** + * Load a msdf json then build fontVariant data * - * 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). + * @param {FontVariant} fontVariant + * @param {string} jsonUrl + * @private + */ +function _loadJson( fontVariant, jsonUrl ) { + + new external_THREE_namespaceObject.FileLoader().setResponseType( 'json' ) .load( jsonUrl, ( response ) => { + + fontVariant._buildData( response ); + fontVariant._checkReadiness(); + + } ); + +} + +/** + * Load a msdf texture then build texture * - * 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. + * @param {FontVariant} fontVariant + * @param {string} textureUrl + * @private + */ +function _loadTexture( fontVariant, textureUrl ) { + + fontVariant._texture = new external_THREE_namespaceObject.TextureLoader().load( textureUrl, ( texture ) => { + + fontVariant._buildTexture( texture ); + fontVariant._checkReadiness(); + + } ); + +} + +/*********************************************************************************************************************** + * MSDF FILE FORMAT DESCRIPTION + * @see https://www.angelcode.com/products/bmfont/doc/file_format.html + **********************************************************************************************************************/ + + +/** + * @typedef {Object} MSDFJson * - * Instead, redundant update request are moot, the component will update once when the use calls - * update() in their render loop. + * @property {MSDFJsonInfo} info + * @property {MSDFJsonCommon} common + * @property {Array.} pages + * @property {Array.} chars + * @property {{fieldType:string, distanceRange:number}} distanceField + * @property {Array.} kernings */ -class UpdateManager { - /* - * get called by MeshUIComponent when component.set has been used. - * It registers this component and all its descendants for the different types of updates that were required. - */ - static requestUpdate( component, updateParsing, updateLayout, updateInner ) { +/** + * + * @typedef {Object} MSDFJsonInfo + * + * @property {string} face This is the name of the true type font. + * @property {number} size The size of the true type font. + * @property {boolean} bold The font is bold. + * @property {boolean} italic The font is italic. + * @property {string[]} charset The name of the OEM charset used (when not unicode). + * @property {boolean} unicode Set to 1 if it is the unicode charset. + * @property {number} stretchH The font height stretch in percentage. 100% means no stretch. + * @property {number} smooth Set to 1 if smoothing was turned on. + * @property {number} aa The supersampling level used. 1 means no supersampling was used. + * @property {Array.} padding TThe padding for each character (up, right, down, left). + * @property {Array.} spacing The spacing for each character (horizontal, vertical). + * @property {number} outline (not found) The outline thickness for the characters. + */ - component.traverse( ( child ) => { +/** + * + * @typedef {Object} MSDFJsonCommon + * + * @property {number} lineHeight This is the distance in pixels between each line of text. + * @property {number} base The number of pixels from the absolute top of the line to the base of the characters. + * @property {number} scaleW The width of the texture, normally used to scale the x pos of the character image. + * @property {number} scaleH The height of the texture, normally used to scale the y pos of the character image. + * @property {number} pages The number of texture pages included in the font. + * @property {boolean} packed + * @property {number} alphaChnl + * @property {number} redChnl + * @property {number} greenChnl + * @property {number[]} blueChnl + */ - if ( !child.isUI ) return; +/** + * @typedef {Object} MSDFJsonPage + * + * @property {string} id The page id. + * @property {string} file The texture file name. + */ - // request updates for all descendants of the passed components - if ( !this.requestedUpdates[ child.id ] ) { +/** + * + * @typedef {Object} MSDFJsonChar + * + * @property {number} id The character id. + * @property {number} index The character index. + * @property {string} char The character. + * @property {number} x The left position of the character image in the texture. + * @property {number} y The top position of the character image in the texture. + * @property {number} width The width of the character image in the texture. + * @property {number} height The height of the character image in the texture. + * @property {number} xoffset How much the current position should be offset when copying the image from the texture to the screen. + * @property {number} yoffset How much the current position should be offset when copying the image from the texture to the screen. + * @property {number} xadvance How much the current position should be advanced after drawing the character. + * @property {string} page The texture page where the character image is found. + * @property {number} chnl The texture channel where the character image is found (1 = blue, 2 = green, 4 = red, 8 = alpha, 15 = all channels). + */ - this.requestedUpdates[ child.id ] = { - updateParsing, - updateLayout, - updateInner, - needCallback: ( updateParsing || updateLayout || updateInner ) - }; - } else { - if ( updateParsing ) this.requestedUpdates[ child.id ].updateParsing = true; - if ( updateLayout ) this.requestedUpdates[ child.id ].updateLayout = true; - if ( updateInner ) this.requestedUpdates[ child.id ].updateInner = true; +/** + * + * @typedef {Object} MSDFJsonKerning + * + * @property {number} first The first character id. + * @property {number} second The second character id. + * @property {number} amount How much the x position should be adjusted when drawing the second character immediately following the first. + * + */ - } +;// CONCATENATED MODULE: ./src/font/FontFamily.js - } ); - } +//JSDoc related imports - /** Register a passed component for later updates */ - static register( component ) { +/* eslint-disable no-unused-vars */ - if ( !this.components.includes( component ) ) { - this.components.push( component ); +/* eslint-enable no-unused-vars */ - } +class FontFamily extends external_THREE_namespaceObject.EventDispatcher { + + /** + * + * @param {string} name + */ + constructor( name ) { + + super(); + + /** + * + * @type {string} + * @private + */ + this._name = name; + + /** + * + * @type {Array.} + * @private + */ + this._variants = []; + + /** + * + * @type {boolean} + * @private + */ + this._isReady = false; } - /** Unregister a component (when it's deleted for instance) */ - static disposeOf( component ) { + get isReady() { return this._isReady; } - const idx = this.components.indexOf( component ); + /** + * + * @param {string} weight + * @param {string} style + * @param {string|Object} json + * @param {string|Texture} texture + * @param {boolean} [override=false] + */ + addVariant( weight, style, json, texture, override = false){ - if ( idx > -1 ) { + if( override || !this.getVariant( weight, style) ){ - this.components.splice( idx, 1 ); + this._isReady = false; - } + const newVariant = new MSDFFontVariant( weight, style, json, texture); - } + this._variants.push( newVariant ); - /** Trigger all requested updates of registered components */ - static update() { + if( !newVariant.isReady ){ - if ( Object.keys( this.requestedUpdates ).length > 0 ) { + newVariant.addEventListener( "ready", this._checkReadiness ) + + } else { + + this._checkReadiness(); + + } + + } else { + + console.warn(`FontFamily('${this._name}')::addVariant() - Variant(${weight}, ${style}) already exists.`); + + } + + return this; + + } + + /** + * + * @param {FontVariant} variantImplementation + * @param {boolean} [override=false] + */ + addCustomImplementationVariant( variantImplementation, override = false){ + + if( override || !this.getVariant( variantImplementation.weight, variantImplementation.style) ){ + + this._isReady = false; + + this._variants.push( variantImplementation ); + + if( !variantImplementation.isReady ){ + + variantImplementation.addEventListener( "ready", this._checkReadiness ) + + } else { + + this._checkReadiness(); + + } + + } else { + + console.warn(`FontFamily('${this._name}')::addCustomImplementationVariant() - Variant(${variantImplementation.weight}, ${variantImplementation.style}) already exists.`); + + } + + return this; + + } + + /** + * + * @param {string} weight + * @param {string} style + * @returns {FontVariant|null} + */ + getVariant( weight, style ){ + + return this._variants.find( fontVariant => fontVariant.weight === weight && fontVariant.style === style ); + + } + + /** + * + * @return {string} + */ + get name(){ return this._name; } + + _checkReadiness = () => { + + if( this._variants.every( v => v.isReady ) ) { + + FontFamily_setReady( this ); + + } + + } + +} + +const FontFamily_readyEvent = { type: 'ready' }; + +/** + * Set the ready status of a fontVariant + * @param {FontFamily} fontFamily + * @private + */ +function FontFamily_setReady( fontFamily ) { + + fontFamily._isReady = true; + fontFamily.dispatchEvent( FontFamily_readyEvent ); + +} + +;// CONCATENATED MODULE: ./src/font/FontLibrary.js + + +// JSDoc related imports +/* eslint-disable no-unused-vars */ + +/* eslint-enable no-unused-vars */ + + +const _fontFamilies = {}; + +/* eslint-disable no-unused-vars */ + +/** + * + * @param {FontFamily} fontFamily + * @returns {Promise} + */ +function prepare( fontFamily ) { + + /** + * + * @type {FontFamily[]} + */ + const families = [ ...arguments ]; + + // Check all family are right instance + families.forEach( f => { + + if( !(f instanceof FontFamily) ) { + + throw new Error(`FontLibrary::prepare() - One of the provided parameter is not a FontFamily. Instead ${typeof f} given.`); + + } + + }) + + /** + * Check that all provided families are loaded + * @returns {boolean} + */ + const areAllLoaded = function() { + + return families.every( f => f.isReady ); + + } + + // @TODO: Should handle possible rejection + return new Promise((resolve,reject)=>{ + + // Direct resolve if all loaded + if ( areAllLoaded() ){ + + resolve(); + + } else { + + // Add listener on each family not ready + for ( let i = 0; i < families.length; i++ ) { + + const family = families[ i ]; + if( !family.isReady ){ + + family.addEventListener( "ready" , ()=> { + + // Resolve if all other families are loaded + if( areAllLoaded() ) { + + resolve(); + + } + + }); + + } + + } + + } + + }); + +} + +/* eslint-enable no-unused-vars */ + + +/** + * + * @param {string} name + * @returns {FontFamily} + */ +function addFontFamily( name ) { + + if ( _fontFamilies[ name ] ) { + console.error( `FontLibrary::addFontFamily - Font('${name}') is already registered` ); + } + + _fontFamilies[ name ] = new FontFamily( name ); + + return _fontFamilies[ name ]; + +} + +/** + * + * @param {string} name + * @returns {FontFamily} + */ +function getFontFamily( name ) { + + return _fontFamilies[ name ]; + +} + + +/** + * + * @param { (fontVariant:FontVariant, character:string ) => string|null } handler + */ +function setMissingCharacterHandler( handler ) { + + _missingCharacterHandler = handler; + +} + +// + +const FontLibrary = { + addFontFamily, + getFontFamily, + prepare, + setMissingCharacterHandler, + missingCharacter +}; + +/* harmony default export */ const font_FontLibrary = (FontLibrary); + +/** + * + * @type { (fontVariant:FontVariant, character:string ) => string|null } + * @private + */ +let _missingCharacterHandler = function ( fontVariant, character ) { + + console.error( `The character '${character}' is not included in the font characters set.` ); + + // return a glyph has fallback + return " "; + +}; + +/** + * + * @param {FontVariant} fontVariant + * @param {string} character + * + * @returns {string} + */ +function missingCharacter( fontVariant, character ) { + + // Execute the user defined handled + return _missingCharacterHandler( fontVariant, character ); + +} + + + +;// CONCATENATED MODULE: ./src/components/core/UpdateManager.js +/** + * 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. + */ +class UpdateManager { + + /* + * get called by MeshUIComponent when component.set has been used. + * It registers this component and all its descendants for the different types of updates that were required. + */ + static requestUpdate( component, updateParsing, updateLayout, updateInner ) { + + component.traverse( ( child ) => { + + if ( !child.isUI ) return; + + // request updates for all descendants of the passed components + if ( !this.requestedUpdates[ child.id ] ) { + + this.requestedUpdates[ child.id ] = { + updateParsing, + updateLayout, + updateInner, + needCallback: ( updateParsing || updateLayout || updateInner ) + }; + + } else { + + if ( updateParsing ) this.requestedUpdates[ child.id ].updateParsing = true; + if ( updateLayout ) this.requestedUpdates[ child.id ].updateLayout = true; + if ( updateInner ) this.requestedUpdates[ child.id ].updateInner = true; + + } + + } ); + + } + + /** Register a passed component for later updates */ + static register( component ) { + + if ( !this.components.includes( component ) ) { + + this.components.push( component ); + + } + + } + + /** Unregister a component (when it's deleted for instance) */ + static disposeOf( component ) { + + const idx = this.components.indexOf( component ); + + if ( idx > -1 ) { + + this.components.splice( idx, 1 ); + + } + + } + + /** Trigger all requested updates of registered components */ + static update() { + + if ( Object.keys( this.requestedUpdates ).length > 0 ) { + + const roots = this.components.filter( ( component ) => { + + return !component.parentUI; + + } ); + + roots.forEach( root => this.traverseParsing( root ) ); + roots.forEach( root => this.traverseUpdates( root ) ); + + } + + } + + /** + * Calls parseParams update of all components from parent to children + * @private + */ + static traverseParsing( component ) { + + const request = this.requestedUpdates[ component.id ]; + + if ( request && request.updateParsing ) { + + component.parseParams(); + + request.updateParsing = false; + + } + + component.childrenUIs.forEach( child => this.traverseParsing( child ) ); + + } + + /** + * Calls updateLayout and updateInner functions of components that need an update + * @private + */ + static traverseUpdates( component ) { + + const request = this.requestedUpdates[ component.id ]; + // instant remove the requested update, + // allowing code below ( especially onAfterUpdate ) to add it without being directly remove + delete this.requestedUpdates[ component.id ]; + + // + + if ( request && request.updateLayout ) { + + request.updateLayout = false; + + component.updateLayout(); + + } + + // + + if ( request && request.updateInner ) { + + request.updateInner = false; + + component.updateInner(); + + } + + + // Update any child + component.childrenUIs.forEach( ( childUI ) => { + + this.traverseUpdates( childUI ); + + } ); + + // before sending onAfterUpdate + if ( request && request.needCallback ) { + + component.performAfterUpdate(); + + } + + } + +} + +// TODO move these into the class (Webpack unfortunately doesn't understand +// `static` property syntax, despite browsers already supporting this) +UpdateManager.components = []; +UpdateManager.requestedUpdates = {}; + +;// CONCATENATED MODULE: ./src/utils/font/FontWeight.js +const LIGHTER = '100'; +const _100 = '100'; +const _200 = '200'; +const _300 = '300'; +const FontWeight_NORMAL = '400'; +const _400 = '400'; +const _500 = '500'; +const _600 = '600'; +const BOLD = '700'; +const _700 = '700'; +const _800 = '800'; +const BOLDER = '900'; +const _900 = '900'; + + + +;// CONCATENATED MODULE: ./src/utils/font/FontStyle.js +const FontStyle_NORMAL = "normal"; +const ITALIC = "italic"; +const OBLIQUE = "oblique"; + +const MAX_OBLIQUE_ANGLE = 90; + +/** + * Get the oblique style with custom angle + * @param angleInDegree + */ +function obliqueCustomAngle( angleInDegree ){ + + // Clamp the angle + angleInDegree = angleInDegree < - MAX_OBLIQUE_ANGLE ? - MAX_OBLIQUE_ANGLE : angleInDegree > MAX_OBLIQUE_ANGLE ? MAX_OBLIQUE_ANGLE : angleInDegree; + + return `${OBLIQUE} ${angleInDegree}deg`; + +} + + +;// CONCATENATED MODULE: ./src/utils/Defaults.js + + + + + + + + + + +/** List the default values of the lib components */ +/* harmony default export */ const Defaults = ({ + container: null, + fontFamily: null, + fontSize: 0.05, + fontKerning: 'normal', // FontKerning would act like css : "none"|"normal"|"auto"("auto" not yet implemented) + fontStyle: FontStyle_NORMAL, + fontWeight: FontWeight_NORMAL, + bestFit: 'none', + offset: 0.01, + interLine: 0.01, + breakOn: '- ,.:?!\n',// added '\n' to also acts as friendly breaks when white-space:normal + whiteSpace: PRE_LINE, + contentDirection: COLUMN, + alignItems: CENTER, + justifyContent: JustifyContent_START, + fontTexture: null, + textAlign: TextAlign_CENTER, + fontColor: new external_THREE_namespaceObject.Color( 0xffffff ), + fontOpacity: 1, + fontPXRange: 4, + fontSupersampling: true, + borderRadius: new external_THREE_namespaceObject.Vector4( 0.01,0.01,0.01, 0.01 ), + borderWidth: new external_THREE_namespaceObject.Vector4( 0, 0, 0, 0 ), + borderColor: new external_THREE_namespaceObject.Color( 'black' ), + borderOpacity: 1, + backgroundSize: "cover", + backgroundColor: new external_THREE_namespaceObject.Color( 0x222222 ), + backgroundWhiteColor: new external_THREE_namespaceObject.Color( 0xffffff ), + backgroundOpacity: 0.8, + backgroundOpaqueOpacity: 1.0, + // this default value is a function to avoid initialization issues (see issue #126) + // backgroundTexture: makeBackgroundTexture, + backgroundTexture: null, + hiddenOverflow: false, + letterSpacing: 0 +}); + +;// CONCATENATED MODULE: ./src/utils/mediator/transformers/CommonTransformers.js +/** + * Transfer the alphaTest value from MeshUIComponent to material + * @type {import('../Mediator').MediationTransformer} + */ +const directTransfer = function ( target, targetProperty, value ) { + + target[targetProperty] = value; + +} + +;// CONCATENATED MODULE: ./src/utils/mediator/Mediator.js + + +/** + * An option function to transform value from subject to target + * @typedef {(target:any, targetProperty:string, value:any) => void} MediationTransformer + * + */ + +/** + * @typedef {Object.<{subjectProperty:string, trans?:MediationTransformer}>} MediationDefinition + * + */ + +class Mediator{ + + /** + * @constructor + * @param {MediationDefinition} definition + */ + constructor( definition ) { + + /** + * + * @type {MediationDefinition} + * @private + */ + this._definition = definition; + + } + + /** + * + * @param {MediationDefinition} value + */ + set definition( value ) { + + this._definition = value; + + } + + + /** + * + * @param {MeshUIComponent} subject + * @param {any} target + * @param {Object.<(string|number), any>} options + * @param {any} [secondTarget=null] + */ + mediate( subject, target, options, secondTarget = null ) { + + // Mediate each subject properties to material + for ( const subjectProperty in this._definition ) { + const mediationDefinition = this._definition[subjectProperty]; + + if ( options[subjectProperty] !== undefined ) { + + // retrieve the mediation transformer to use for this property + const mediationTransformer = mediationDefinition.t ? mediationDefinition.t : directTransfer; + mediationTransformer( target, mediationDefinition.m, options[subjectProperty] ); + + // Also transfert to second target is isset + if( secondTarget ) { + + mediationTransformer( secondTarget, mediationDefinition.m, options[subjectProperty] ); + + } + + } + + } + + } + + + /*********************************************************************************************************************** + * STATIC + **********************************************************************************************************************/ + + /** + * + * @param {MeshUIComponent} subject + * @param {any} target + * @param {Object.<(string|number), any>} options + * @param {Object.<{subjectProperty:string, trans?:(target:any, targetProperty:string, value:any) => void}>} mediationDefinitions + * @param {any} [secondTarget=null] + */ + static mediate( subject, target, options, mediationDefinitions, secondTarget = null ) { + + // Cannot mediate if target not defined + if( !target ) return; + + // if no options found, retrieve all need options + if( !options ){ + + options = {}; + for ( const materialProperty in mediationDefinitions ) { + + let value = subject[materialProperty]; + if( value === undefined ){ + + const upperCaseProperty = materialProperty[0].toUpperCase() + materialProperty.substring(1) + if( subject["get"+upperCaseProperty] ) { + + value = subject["get"+upperCaseProperty](); + + } + + } + + if( value !== undefined ) { + + options[materialProperty] = value; + + } + + } + + } + + + // Mediate each subject properties to material + for ( const subjectProperty in mediationDefinitions ) { + const definition = mediationDefinitions[subjectProperty]; + + if ( options[subjectProperty] !== undefined ) { + + // retrieve the mediation transformer to use for this property + const mediationTransformer = definition.t ? definition.t : directTransfer; + mediationTransformer( target, definition.m, options[subjectProperty] ); + + // Also transfert to second target is isset + if( secondTarget ) { + + mediationTransformer( secondTarget, definition.m, options[subjectProperty] ); + + } + + } + + } + + } + + +} + +;// CONCATENATED MODULE: ./src/components/core/MeshUIComponent.js + + + + + + + + + + + + + +//JSDoc related imports +/* eslint-disable no-unused-vars */ + + + +/* eslint-enable no-unused-vars */ + +/** + + +Job: +- Set this component attributes and call updates accordingly +- Getting this component attribute, from itself or from its parents +- Managing this component's states + +This is the core module of three-mesh-ui. Every component is composed with it. +It owns the principal public methods of a component : set, setupState and setState. + + */ + +class MeshUIComponent extends external_THREE_namespaceObject.Object3D { + + /** + * + * @param {Object.<(string), any>} options + */ + constructor( options ) { + + super( options ); + + this.states = {}; + this.currentState = undefined; + this.isUI = true; + this.autoLayout = true; + + // children + + /** + * + * @type {MeshUIComponent[]} + */ + this.childrenUIs = []; + + /** + * + * @type {MeshUIComponent[]} + */ + this.childrenBoxes = []; + + /** + * + * @type {MeshUIComponent[]} + */ + this.childrenTexts = []; + + /** + * + * @type {MeshUIComponent[]} + */ + this.childrenInlines = []; + + + /** + * parents + * @type {MeshUIComponent|null} + */ + this.parentUI = null; + + // update parentUI when this component will be added or removed + this.addEventListener( 'added', this._rebuildParentUI ); + this.addEventListener( 'removed', this._rebuildParentUI ); + + /** + * + * @type {Mesh|null} + * @protected + */ + this._main = null; + + // hooks + this._hooks = {}; + this._onAfterUpdates = []; + + this.position.z = this.getOffset(); + + /** + * + * @type {Object.<{m:string, t?:(value:any) => any}>} + * @protected + */ + this._materialMediation = {}; + + this._meshMediation = { + _castShadow:{m:'castShadow'}, + _receiveShadow:{m:'receiveShadow'}, + // _renderOrder:{m:'renderOrder'} + } + + + /** + * + * @type {Vector4} + * @private + */ + this._borderRadius = new external_THREE_namespaceObject.Vector4().copy( Defaults.borderRadius ); + + /** + * + * @type {Vector4} + * @private + */ + this._borderWidth = new external_THREE_namespaceObject.Vector4().copy( Defaults.borderWidth ); + + /** + * + * @type {Vector4} + * @private + */ + this._padding = new external_THREE_namespaceObject.Vector4( 0, 0, 0, 0 ); + + /** + * + * @type {Vector4} + * @private + */ + this._margin = new external_THREE_namespaceObject.Vector4( 0, 0, 0, 0 ); + + /** + * @Todo: Probably only for boxComponents + * @type {Lines} + */ + this.lines = new Lines(); + + /** + * + * @type {FontVariant} + * @protected + */ + this._font = null; + + } + + ///////////// + /// GETTERS + ///////////// + + getClippingPlanes() { + + const planes = []; + + if ( this.parentUI ) { + + if ( this.isBlock && this.parentUI.getHiddenOverflow() ) { + + // const yLimit = ( this.getInsetHeight() / 2 ); + const yLimit = this.parentUI.getOffsetHeight(); + const xLimit = this.parentUI.getOffsetWidth(); + const padding = this.parentUI._padding; + const border = this.parentUI._borderWidth; + + const newPlanes = [ + // top + new external_THREE_namespaceObject.Plane( new external_THREE_namespaceObject.Vector3( 0, -1, 0 ), yLimit / 2 - ( padding.x + border.x ) ), + // right + new external_THREE_namespaceObject.Plane( new external_THREE_namespaceObject.Vector3( -1, 0, 0 ), xLimit / 2 - ( padding.y + border.y ) ), + // bottom + new external_THREE_namespaceObject.Plane( new external_THREE_namespaceObject.Vector3( 0, 1, 0 ), yLimit / 2 - ( padding.z + border.z ) ), + // left + new external_THREE_namespaceObject.Plane( new external_THREE_namespaceObject.Vector3( 1, 0, 0 ), xLimit / 2 - ( padding.w + border.w ) ), + ]; + + newPlanes.forEach( plane => { + + plane.applyMatrix4( this.parentUI.matrixWorld ); + + } ); + + planes.push( ...newPlanes ); + + } + + if ( this.parentUI.parentUI ) { + + planes.push( ...this.parentUI.getClippingPlanes() ); + + } + + } + + return planes; + + } + + /** + * @TODO : This is already present in MaterialManager + * Update a component's materials clipping planes. + * Called every frame. + */ + updateClippingPlanes( value ) { + + const newClippingPlanes = value !== undefined ? value : this.getClippingPlanes(); + + if ( JSON.stringify( newClippingPlanes ) !== JSON.stringify( this.clippingPlanes ) ) { + + this.clippingPlanes = newClippingPlanes; + + if ( this.material ) this.material.clippingPlanes = this.clippingPlanes; + + } + + } + + /** Get the highest parent of this component (the parent that has no parent on top of it) */ + getHighestParent() { + + if ( !this.parentUI ) { + + return this; + + } + + return this.parent.getHighestParent(); + + + } + + /** + * look for a property in this object, and if does not find it, find in parents or return default value + * @private + */ + _getProperty( propName ) { + + if ( this[ propName ] === undefined && this.parentUI ) { + + return this.parent._getProperty( propName ); + + } else if ( this[ propName ] !== undefined ) { + + return this[ propName ]; + + } + + return Defaults[ propName ]; + + } + + // + + getFontSize() { + + return this._getProperty( 'fontSize' ); + + } + + getSegments() { + + return this.segments || 1; + + } + + getAlphaTest() { + + return this.alphaTest || 0.02; + + } + + getFontKerning() { + + return this._getProperty( 'fontKerning' ); + + } + + getFontStyle() { + + return this._getProperty( 'fontStyle' ); + + } + + getFontWeight() { + + return this._getProperty( 'fontWeight' ); + + } + + getLetterSpacing() { + + return this._getProperty( 'letterSpacing' ); + + } + + getFontTexture() { + + if( this._font && this._font.isReady ){ + + return this._font.texture; + + } + + return this._getProperty( 'fontTexture' ); + + } + + getFontFamily() { + + return this._getProperty( 'fontFamily' ); + + } + + getBreakOn() { + + return this._getProperty( 'breakOn' ); + + } + + getWhiteSpace() { + + return this._getProperty( 'whiteSpace' ); + + } + + getTextAlign() { + + return this._getProperty( 'textAlign' ); + + } + + getFontColor() { + + return this._getProperty( 'fontColor' ); + + } + + + getFontSupersampling() { + + return this._getProperty( 'fontSupersampling' ); + + } + + getFontOpacity() { + + return this._getProperty( 'fontOpacity' ); + + } + + getFontPXRange() { + + return this._getProperty( 'fontPXRange' ); + + } + + getBorderRadius() { + + return this._getProperty( 'borderRadius' ); + + } + + getBorderWidth() { + + return this._getProperty( 'borderWidth' ); + + } + + getBorderColor() { + + return this._getProperty( 'borderColor' ); + + } + + getBorderOpacity() { + + return this._getProperty( 'borderOpacity' ); + + } + + /// SPECIALS + + /** return the first parent with a 'threeOBJ' property */ + getContainer() { + + if ( !this.threeOBJ && this.parent ) { + + return this.parent.getContainer(); + + } else if ( this.threeOBJ ) { + + return this; + + } + + return Defaults.container; + + + } + + /** Get the number of UI parents above this elements (0 if no parent) */ + getParentsNumber( i ) { + + i = i || 0; + + if ( this.parentUI ) { + + return this.parentUI.getParentsNumber( i + 1 ); + + } + + return i; + + } + + //////////////////////////////////// + /// GETTERS WITH NO PARENTS LOOKUP + //////////////////////////////////// + + getBackgroundOpacity() { + + return ( !this.backgroundOpacity && this.backgroundOpacity !== 0 ) ? + Defaults.backgroundOpacity : this.backgroundOpacity; + + } + + getBackgroundColor() { + + return this.backgroundColor || Defaults.backgroundColor; + + } + + getBackgroundTexture() { + + // return this.backgroundTexture || DEFAULTS.backgroundTexture(); + return this.backgroundTexture || Defaults.backgroundTexture; + + } + + /** + * @deprecated + * @returns {string} + */ + getAlignContent() { + + return this.alignContent || Defaults.alignContent; + + } + + getAlignItems() { + + return this.alignItems || Defaults.alignItems; + + } + + getContentDirection() { + + return this.contentDirection || Defaults.contentDirection; + + } + + getJustifyContent() { + + return this.justifyContent || Defaults.justifyContent; + + } + + getInterLine() { + + return ( this.interLine === undefined ) ? Defaults.interLine : this.interLine; + + } + + getOffset() { + + return ( this.offset === undefined ) ? Defaults.offset : this.offset; + + } + + getBackgroundSize() { + + return ( this.backgroundSize === undefined ) ? Defaults.backgroundSize : this.backgroundSize; + + } + + getHiddenOverflow() { + + return ( this.hiddenOverflow === undefined ) ? Defaults.hiddenOverflow : this.hiddenOverflow; + + } + + getBestFit() { + + return ( this.bestFit === undefined ) ? Defaults.bestFit : this.bestFit; + + } + + /////////////// + /// UPDATE + /////////////// + + /** + * Filters children in order to compute only one times children lists + * @private + */ + _rebuildChildrenLists() { + + // Stores all children that are ui + this.childrenUIs = this.children.filter( child => child.isUI && child.visible ); + + // Stores all children that are box + this.childrenBoxes = this.childrenUIs.filter( child => child.isBoxComponent ); + + // Stores all children that are inline + this.childrenInlines = this.childrenUIs.filter( child => child.isInline ); + + // Stores all children that are text + this.childrenTexts = this.childrenUIs.filter( child => child.isText ); + } + + /** + * Try to retrieve parentUI after each structural change + * @private + */ + _rebuildParentUI = () => { + + if ( this.parent && this.parent.isUI ) { + + this.parentUI = this.parent; + this.position.z = this.getOffset(); + + } else { + + this.parentUI = null; + + } + + }; + + /** + * When the user calls component.add, it registers for updates, + * then call THREE.Object3D.add. + */ + + /* eslint-disable no-unused-vars */ + /** + * + * @override + * @param {...Object3D} object + * @return {this} + */ + add( object) { + + for ( const id of Object.keys( arguments ) ) { + + // An inline component relies on its parent for positioning + if ( arguments[ id ].isUI ) this.update( null, true ); + // if ( arguments[ id ].isInline ) this.update( null, true ); + + } + + super.add( ...arguments ); + + this._rebuildChildrenLists(); + + return this; + + } + + + /** + * When the user calls component.remove, it registers for updates, + * then call THREE.Object3D.remove. + * @override + * @param {...Object3D} object + * @return {this} + */ + remove(object) { + + for ( const id of Object.keys( arguments ) ) { + + // An inline component relies on its parent for positioning + if ( arguments[ id ].isInline ) this.update( null, true ); + + } + + super.remove( ...arguments ); + + this._rebuildChildrenLists(); + + return this; + + } + + /* eslint-enable no-unused-vars */ + + // + + update( updateParsing, updateLayout, updateInner ) { + + UpdateManager.requestUpdate( this, updateParsing, updateLayout, updateInner ); + + } + + /** + * + * @param {Function} func + */ + set onAfterUpdate( func ) { + + console.warn( '`onAfterUpdate` property has been deprecated, please rely on `addAfterUpdate` instead.' ); + this.addAfterUpdate( func ); + + } + + /** + * + * @param {Function} func + */ + addAfterUpdate( func ) { + + this._onAfterUpdates.push( func ); + + } + + /** + * + * @param {Function} func + */ + removeAfterUpdate( func ) { + + const index = this._onAfterUpdates.indexOf( func ); + if( index !== -1 ) { + + this._onAfterUpdates.splice( index, 1 ); + + } + + } + + performAfterUpdate() { + + for ( let i = 0; i < this._onAfterUpdates.length; i++ ) { + + this._onAfterUpdates[ i ](); + + } + + } + + /** + * Set this component's passed parameters. + * If necessary, take special actions. + * Update this component unless otherwise specified. + */ + set( options ) { + + let parsingNeedsUpdate, layoutNeedsUpdate, innerNeedsUpdate; + + // Register to the update manager, so that it knows when to update + + UpdateManager.register( this ); + + // Abort if no option passed + + if ( !options || JSON.stringify( options ) === JSON.stringify( {} ) ) return; + + // Set this component parameters according to options, and trigger updates accordingly + // The benefit of having two types of updates, is to put everthing that takes time + // in one batch, and the rest in the other. This way, efficient animation is possible with + // attribute from the light batch. + + for ( const prop of Object.keys( options ) ) { + + if ( this[ prop ] != options[ prop ] ) { + + const value = options[ prop ]; + + switch ( prop ) { + + case 'content' : + case 'fontWeight' : + case 'fontStyle' : + case 'whiteSpace': // @TODO : Whitespace could also just be layouting + if ( this.isText ) parsingNeedsUpdate = true; + layoutNeedsUpdate = true; + this[ prop ] = value; + break; + + // Only layout now - Not anymore parsing + case 'fontSize' : + case 'fontKerning' : + case 'breakOn': + case 'segments': + layoutNeedsUpdate = true; + this[ prop ] = value; + break; + + case 'bestFit' : + if ( this.isBlock ) { + parsingNeedsUpdate = true; + layoutNeedsUpdate = true; + } + this[ prop ] = value; + break; + + case 'width' : + case 'height' : + // case 'padding' : + // @TODO: I don't think this is true anymore + if ( this.isInlineBlock || ( this.isBlock ) ) parsingNeedsUpdate = true; + layoutNeedsUpdate = true; + this[ prop ] = value; + break; + + case 'padding': + this._fourDimensionsValueSetter(this._padding, value ); + layoutNeedsUpdate = true; + break; + case 'paddingTop': + this._padding.x = value; + layoutNeedsUpdate = true; + break; + case 'paddingRight': + this._padding.y = value; + layoutNeedsUpdate = true; + break; + case 'paddingBottom': + this._padding.z = value; + layoutNeedsUpdate = true; + break; + case 'paddingLeft': + this._padding.w = value; + layoutNeedsUpdate = true; + break; + + case 'letterSpacing' : + case 'interLine' : + // @TODO: I don't think this is true anymore + if ( this.isBlock ) parsingNeedsUpdate = true; + layoutNeedsUpdate = true; + this[ prop ] = value; + break; + + case 'margin' : + this._fourDimensionsValueSetter(this._margin, value ); + layoutNeedsUpdate = true; + break; + case 'marginTop': + this._margin.x = value; + layoutNeedsUpdate = true; + break; + case 'marginRight': + this._margin.y = value; + layoutNeedsUpdate = true; + break; + case 'marginBottom': + this._margin.z = value; + layoutNeedsUpdate = true; + break; + case 'marginLeft': + this._margin.w = value; + layoutNeedsUpdate = true; + break; + // case 'margin': + case 'contentDirection' : + case 'justifyContent' : + case 'alignContent' : + case 'alignItems' : + case 'textAlign' : + case 'textType' : + layoutNeedsUpdate = true; + this[ prop ] = value; + break; + + case 'fontColor' : + case 'fontOpacity' : + case 'fontSupersampling' : + case 'backgroundColor' : + case 'backgroundOpacity' : + case 'backgroundTexture' : + case 'backgroundSize' : + case 'borderColor' : + case 'borderOpacity' : + // innerNeedsUpdate = true; + this[ prop ] = value; + break; + + case 'hiddenOverflow' : + this[ prop ] = value; + break; + + case 'offset': + // if( !this.isBlock || this.parentUI ){ + + this[ prop ] = value; + this.position.z = value; + + // } + break; + + // abstracted properties, those properties don't need to be store as this[prop] = value + case 'borderRadius' : + this._fourDimensionsValueSetter( this._borderRadius, value); + break; + case 'borderTopLeftRadius': + this._borderRadius.x = value; + break; + case 'borderTopRightRadius': + this._borderRadius.y = value; + break; + case 'borderBottomRightRadius': + this._borderRadius.z = value; + break; + case 'borderBottomLeftRadius': + this._borderRadius.w = value; + break; + case 'borderTopRadius': + this._borderRadius.x = value; + this._borderRadius.y = value; + break; + case 'borderRightRadius': + this._borderRadius.y = value; + this._borderRadius.z = value; + break; + case 'borderLeftRadius': + this._borderRadius.x = value; + this._borderRadius.w = value; + break + case 'borderBottomRadius': + this._borderRadius.z = value; + this._borderRadius.w = value; + break; + + + case 'borderWidth' : + this._fourDimensionsValueSetter( this._borderWidth, value); + layoutNeedsUpdate = true; + break; + case 'borderTopWidth' : + this._borderWidth.x = value; + layoutNeedsUpdate = true; + break; + case 'borderRightWidth': + this._borderWidth.y = value; + layoutNeedsUpdate = true; + break; + case 'borderBottomWidth': + this._borderWidth.z = value; + layoutNeedsUpdate = true; + break; + case 'borderLeftWidth': + this._borderWidth.w = value; + layoutNeedsUpdate = true; + break; + + default: + this[ prop ] = value; + } + + } + + } + + // special cases, this.update() must be called only when some files finished loading + + // Selection of fontFamily and font property + // 1. Preferred way, give a {FontFamily} property + if ( options.fontFamily instanceof FontFamily ) { + + this.fontFamily = options.fontFamily; + this.font = options.fontFamily.getVariant( this.getFontWeight(), this.getFontStyle() ); + + } + + // 1.1 Preferred way, a bit annoying to check options.fontTexture ( retro-compatibility ) + else if( typeof options.fontFamily === 'string' && !options.fontTexture ) { + + const fontFamily = font_FontLibrary.getFontFamily( options.fontFamily ); + + if( fontFamily ){ + + this.fontFamily = fontFamily; + this.font = fontFamily.getVariant( this.getFontWeight(), this.getFontStyle() ); + + } + + } + // 2. < v7.x.x way + else if ( options.fontFamily && options.fontTexture ) { + + // Set from old way, check if that family is already registered + const fontName = options.fontFamily.pages ? options.fontFamily.info.face : options.fontFamily; + + let fontFamily = font_FontLibrary.getFontFamily( fontName ); + + if ( !fontFamily ) { + + fontFamily = font_FontLibrary.addFontFamily( fontName ) + .addVariant( FontWeight_NORMAL, FontStyle_NORMAL, options.fontFamily, options.fontTexture ); + + } + + this.fontFamily = fontFamily; + + // @TODO: Add more variant selection + this.font = fontFamily.getVariant( FontWeight_NORMAL, FontStyle_NORMAL ); + + } + + // if font kerning changes for a child of a block with Best Fit enabled, we need to trigger parsing for the parent as well. + if ( this.parentUI && this.parentUI.getBestFit() != 'none' ) this.parentUI.update( true, true, false ); + + // Call component update + + this.update( parsingNeedsUpdate, layoutNeedsUpdate, innerNeedsUpdate ); + + + if ( layoutNeedsUpdate ) this.getHighestParent().update( false, true, false ); + + + // + this._transferToMaterial( options ); + + // + // this._transferToMesh( options ); + + + + } + + ///////////////////// + // STATES MANAGEMENT + ///////////////////// + + /** Store a new state in this component, with linked attributes */ + setupState( options ) { + + this.states[ options.state ] = { + attributes: options.attributes, + onSet: options.onSet + }; + + } + + /** Set the attributes of a stored state of this component */ + setState( state ) { + + const savedState = this.states[ state ]; + + if ( !savedState ) { + console.warn( `state "${state}" does not exist within this component` ); + return; + } - const roots = this.components.filter( ( component ) => { + if ( state === this.currentState ) return; - return !component.parentUI; + this.currentState = state; - } ); + if ( savedState.onSet ) savedState.onSet(); - roots.forEach( root => this.traverseParsing( root ) ); - roots.forEach( root => this.traverseUpdates( root ) ); + if ( savedState.attributes ) this.set( savedState.attributes ); } - } + /** + * Get completely rid of this component and its children, also unregister it for updates + * @override + * @return {this} + */ + clear() { - /** - * Calls parseParams update of all components from parent to children - * @private - */ - static traverseParsing( component ) { + this.traverse( ( obj ) => { - const request = this.requestedUpdates[ component.id ]; + UpdateManager.disposeOf( obj ); - if ( request && request.updateParsing ) { + if ( obj.material ) obj.material.dispose(); - component.parseParams(); + if ( obj.geometry ) obj.geometry.dispose(); - request.updateParsing = false; + } ); + return this; } - component.childrenUIs.forEach( child => this.traverseParsing( child ) ); - - } + /*********************************************************************************************************************** + * TO MATERIAL HOLDER + **********************************************************************************************************************/ - /** - * Calls updateLayout and updateInner functions of components that need an update - * @private - */ - static traverseUpdates( component ) { + get material() { + return this._material; + } - const request = this.requestedUpdates[ component.id ]; - // instant remove the requested update, - // allowing code below ( especially onAfterUpdate ) to add it without being directly remove - delete this.requestedUpdates[ component.id ]; + /** + * + * @param {Material|ShaderMaterial} material + */ + set material( material ) { - // + this._material = material; - if ( request && request.updateLayout ) { + // Update the fontMaterialProperties that need to be transferred to + this._materialMediation = {...material.constructor.mediation } - request.updateLayout = false; + // transfer all the properties to material + this._transferToMaterial(); - component.updateLayout(); + if( this._main ) { - } + this._main.material = this._material; - // + } - if ( request && request.updateInner ) { + } - request.updateInner = false; + /** + * + * @param {Material|null} material + */ + set customDepthMaterial( material ) { - component.updateInner(); + this._customDepthMaterial = material; - } + this._transferToMaterial(); + if ( this._main ) { + // transfer to the main if isset + this._main.customDepthMaterial = this._customDepthMaterial; - // Update any child - component.childrenUIs.forEach( ( childUI ) => { + } - this.traverseUpdates( childUI ); + } - } ); + get customDepthMaterial() { return this._customDepthMaterial; } - // before sending onAfterUpdate - if ( request && request.needCallback ) { + /** + * According to the list of materialProperties + * some properties are sent to material + * @private + */ + _transferToMaterial( options = null ) { - component.onAfterUpdate(); + Mediator.mediate(this, this._material, options, this._materialMediation, this.customDepthMaterial); } - } + _transferToMesh( options = null ) { -} + Mediator.mediate( this, this._main, options, this._meshMediation ); -// TODO move these into the class (Webpack unfortunately doesn't understand -// `static` property syntax, despite browsers already supporting this) -UpdateManager.components = []; -UpdateManager.requestedUpdates = {}; + } -;// CONCATENATED MODULE: ./src/utils/font/FontWeight.js -const LIGHTER = '100'; -const _100 = '100'; -const _200 = '200'; -const _300 = '300'; -const FontWeight_NORMAL = '400'; -const _400 = '400'; -const _500 = '500'; -const _600 = '600'; -const BOLD = '700'; -const _700 = '700'; -const _800 = '800'; -const BOLDER = '900'; -const _900 = '900'; + /** + * + * @param {Vector4} vector4 + * @param {string|number|Array.} value + * @private + */ + _fourDimensionsValueSetter( vector4, value ) { + if( value instanceof external_THREE_namespaceObject.Vector4 ) { + vector4.copy( value ); + return ; -;// CONCATENATED MODULE: ./src/utils/font/FontStyle.js -const FontStyle_NORMAL = "normal"; -const ITALIC = "italic"; -const OBLIQUE = "oblique"; + } -const MAX_OBLIQUE_ANGLE = 90; + if (typeof value === 'string' || value instanceof String) { -/** - * Get the oblique style with custom angle - * @param angleInDegree - */ -function obliqueCustomAngle( angleInDegree ){ + value = value.split(" "); - // Clamp the angle - angleInDegree = angleInDegree < - MAX_OBLIQUE_ANGLE ? - MAX_OBLIQUE_ANGLE : angleInDegree > MAX_OBLIQUE_ANGLE ? MAX_OBLIQUE_ANGLE : angleInDegree; + } - return `${OBLIQUE} ${angleInDegree}deg`; + if( Array.isArray( value ) ) { -} + value = value.map( v => parseFloat(v) ); + switch ( value.length ) { -;// CONCATENATED MODULE: ./src/utils/Defaults.js + 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("Four Dimension property has more than four values"); + return; + } + } + if( !isNaN(value) ) { + vector4.setScalar( value ); + } + } -/** List the default values of the lib components */ -/* harmony default export */ const Defaults = ({ - container: null, - fontFamily: null, - fontSize: 0.05, - fontKerning: 'normal', // FontKerning would act like css : "none"|"normal"|"auto"("auto" not yet implemented) - fontStyle: FontStyle_NORMAL, - fontWeight: FontWeight_NORMAL, - bestFit: 'none', - offset: 0.01, - interLine: 0.01, - breakOn: '- ,.:?!\n',// added '\n' to also acts as friendly breaks when white-space:normal - whiteSpace: PRE_LINE, - contentDirection: COLUMN, - alignItems: CENTER, - justifyContent: JustifyContent_START, - fontTexture: null, - textAlign: TextAlign_CENTER, - fontColor: new external_THREE_namespaceObject.Color( 0xffffff ), - fontOpacity: 1, - fontPXRange: 4, - fontSupersampling: true, - borderRadius: 0.01, - borderWidth: 0, - borderColor: new external_THREE_namespaceObject.Color( 'black' ), - borderOpacity: 1, - backgroundSize: "cover", - backgroundColor: new external_THREE_namespaceObject.Color( 0x222222 ), - backgroundWhiteColor: new external_THREE_namespaceObject.Color( 0xffffff ), - backgroundOpacity: 0.8, - backgroundOpaqueOpacity: 1.0, - // this default value is a function to avoid initialization issues (see issue #126) - backgroundTexture: makeBackgroundTexture, - hiddenOverflow: false, - letterSpacing: 0 -}); + /** + * @param {FontVariant} value + */ + set font( value ) { -// -let defaultBgTexture; + this._font = value; -function makeBackgroundTexture() { + } - if ( !defaultBgTexture ) { + /** + * + * @returns {FontVariant} + */ + get font() { - const ctx = document.createElement( 'canvas' ).getContext( '2d' ); - ctx.canvas.width = 1; - ctx.canvas.height = 1; - ctx.fillStyle = '#ffffff'; - ctx.fillRect( 0, 0, 1, 1 ); - defaultBgTexture = new external_THREE_namespaceObject.CanvasTexture( ctx.canvas ); - defaultBgTexture.isDefault = true; + return this._font; } - return defaultBgTexture; - -} + /********************************************************************************************************************* + * MESH MEDIATION + ********************************************************************************************************************/ -;// CONCATENATED MODULE: ./src/components/core/MeshUIComponent.js + /** + * + * @param {boolean} value + */ + set visible( value ) { + this._visible = value; + // @TODO: Instead of direct execution of _rebuildChildrenList + // It could be better to "dirtying" the children list and compute it only once on next frame + this.parentUI?._rebuildChildrenLists(); + } + /** + * + * @return {boolean} + */ + get visible() { return this._visible; } + /** + * + * @param {boolean} value + */ + set castShadow( value ) { + this._castShadow = value; + if ( this._main ) { + this._main.castShadow = this._castShadow; + } + } + /** + * + * @return {boolean} + */ + get castShadow() { return this._castShadow; } -/** + /** + * + * @param {boolean} value + */ + set receiveShadow( value ) { -Job: -- Set this component attributes and call updates accordingly -- Getting this component attribute, from itself or from its parents -- Managing this component's states + this._receiveShadow = value; -This is the core module of three-mesh-ui. Every component is composed with it. -It owns the principal public methods of a component : set, setupState and setState. + if ( this._main ) { - */ -function MeshUIComponent( Base ) { + this._main.receiveShadow = this._receiveShadow; - return class MeshUIComponent extends Base { + } - constructor( options ) { + } - super( options ); + /** + * + * @return {boolean} + */ + get receiveShadow() { return this._receiveShadow; } - this.states = {}; - this.currentState = undefined; - this.isUI = true; - this.autoLayout = true; + /** + * + * @param {number} value + */ + set renderOrder( value ) { - // children - this.childrenUIs = []; - this.childrenBoxes = []; - this.childrenTexts = []; - this.childrenInlines = []; + this._renderOrder = value; - // parents - this.parentUI = null; - // update parentUI when this component will be added or removed - this.addEventListener( 'added', this._rebuildParentUI ); - this.addEventListener( 'removed', this._rebuildParentUI ); - } + if( this._main ) { - ///////////// - /// GETTERS - ///////////// + this._main.renderOrder = this._renderOrder; - getClippingPlanes() { + } - const planes = []; + } - if ( this.parentUI ) { + /** + * + * @return {number} + */ + get renderOrder( ) { return this._renderOrder; } - if ( this.isBlock && this.parentUI.getHiddenOverflow() ) { + /********************************************************************************************************************* + * MATERIAL MEDIATION + ********************************************************************************************************************/ - const yLimit = ( this.parentUI.getHeight() / 2 ) - ( this.parentUI.padding || 0 ); - const xLimit = ( this.parentUI.getWidth() / 2 ) - ( this.parentUI.padding || 0 ); + /** + * + * @param {number} value + */ + set side( value ) { - const newPlanes = [ - new external_THREE_namespaceObject.Plane( new external_THREE_namespaceObject.Vector3( 0, 1, 0 ), yLimit ), - new external_THREE_namespaceObject.Plane( new external_THREE_namespaceObject.Vector3( 0, -1, 0 ), yLimit ), - new external_THREE_namespaceObject.Plane( new external_THREE_namespaceObject.Vector3( 1, 0, 0 ), xLimit ), - new external_THREE_namespaceObject.Plane( new external_THREE_namespaceObject.Vector3( -1, 0, 0 ), xLimit ) - ]; + this._side = value; - newPlanes.forEach( plane => { + if ( this._material ) this._material.side = value; - plane.applyMatrix4( this.parent.matrixWorld ); + } - } ); + /** + * + * @return {number} + */ + get side() { return this._side; } - planes.push( ...newPlanes ); +} - } +;// CONCATENATED MODULE: ./src/components/core/InlineManager.js +/** - if ( this.parentUI.parentUI ) { +Job: Positioning inline elements according to their dimensions inside this component - planes.push( ...this.parentUI.getClippingPlanes() ); +Knows: This component dimensions, and its children dimensions - } +This module is used for Block composition (Object.assign). A Block is responsible +for the positioning of its inline elements. In order for it to know what is the +size of these inline components, parseParams must be called on its children first. - } +It's worth noting that a Text is not positioned as a whole, but letter per letter, +in order to create a line break when necessary. It's Text that merge the various letters +in its own updateLayout function. - return planes; + */ - } - /** Get the highest parent of this component (the parent that has no parent on top of it) */ - getHighestParent() { - if ( !this.parentUI ) { - return this; - } - return this.parent.getHighestParent(); +class InlineManager extends MeshUIComponent{ - } + constructor(options) { - /** - * look for a property in this object, and if does not find it, find in parents or return default value - * @private - */ - _getProperty( propName ) { + super(options); - if ( this[ propName ] === undefined && this.parentUI ) { + } - return this.parent._getProperty( propName ); + /** Compute children .inlines objects position, according to their pre-computed dimensions */ + computeInlinesPosition() { - } else if ( this[ propName ] !== undefined ) { + // computed by BoxComponent + const INNER_WIDTH = this.innerWidth; + // const INNER_WIDTH = this.getInnerWidth(); + const INNER_HEIGHT = this.innerHeight; + // const INNER_HEIGHT = this.getInnerHeight(); - return this[ propName ]; + // got by MeshUIComponent + const JUSTIFICATION = this.getJustifyContent(); + const ALIGNMENT = this.getTextAlign(); - } + // Compute lines + const lines = this.computeLines(); - return Defaults[ propName ]; + ///////////////////////////////////////////////////////////////// + // Position lines according to justifyContent and contentAlign + ///////////////////////////////////////////////////////////////// - } + // Vertical positioning + justifyInlines( this, lines, JUSTIFICATION, INNER_HEIGHT); - // + // Horizontal positioning + textAlign( this, lines, ALIGNMENT, INNER_WIDTH ); - getFontSize() { - return this._getProperty( 'fontSize' ); + // Make lines accessible to provide helpful informations + this.lines = lines; } - getFontKerning() { + /** + * computes lines based on children's inlines array. + * @private + */ + computeLines() { - return this._getProperty( 'fontKerning' ); + // computed by BoxComponent + const INNER_WIDTH = this.innerWidth; + const INTERLINE = this.getInterLine(); - } + // Will stock the characters of each line, so that we can + // correct lines position before to merge + const lines = new Lines( new Line() ); - getFontStyle() { + let lastInlineOffset = 0; + this.childrenInlines.forEach( ( inlineComponent ) => { - return this._getProperty( 'fontStyle' ); + // Abort condition - } + if ( !inlineComponent.inlines ) return; - getFontWeight() { + ////////////////////////////////////////////////////////////// + // Compute offset of each children according to its dimensions + ////////////////////////////////////////////////////////////// - return this._getProperty( 'fontWeight' ); + const FONTSIZE = inlineComponent._fitFontSize || inlineComponent.getFontSize(); + const LETTERSPACING = inlineComponent.isText ? inlineComponent.getLetterSpacing() * FONTSIZE : 0; + const WHITESPACE = inlineComponent.getWhiteSpace(); + const BREAKON = inlineComponent.getBreakOn(); - } + const whiteSpaceOptions = { + WHITESPACE, + LETTERSPACING, + BREAKON, + INNER_WIDTH + } - getLetterSpacing() { + lastInlineOffset += inlineComponent._margin.w + inlineComponent._padding.w; - return this._getProperty( 'letterSpacing' ); + inlineComponent.inlines.forEach( ( inline, i, inlines ) => { - } + const line = lines[lines.length - 1]; + // Line break + const shouldBreak = Whitespace_shouldBreak(inlines,i,lastInlineOffset, whiteSpaceOptions ); - getFontTexture() { + if ( shouldBreak ) { - if( this._font && this._font.isReady ){ + lines.push( new Line( inline ) ); - return this._font.texture; + inline.offsetX = inline.xoffset; - } + // restart the lastInlineOffset as zero. + if ( inline.width === 0 ) { + lastInlineOffset = 0; + return; + } - return this._getProperty( 'fontTexture' ); + // 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; - } + } - getFontFamily() { + lines[ lines.length - 1 ].push( inline ); + inline.offsetX = lastInlineOffset + inline.xoffset + inline.kerning; - return this._getProperty( 'fontFamily' ); + inline.cumulativeWidth = inline.xadvance + inline.kerning + LETTERSPACING; + lastInlineOffset += inline.cumulativeWidth; - } + // in case of lineBreak mandatory + if( line.length-1 === 1) { - getBreakOn() { + if ( line[ line.length - 2 ].width === 0 ) { - return this._getProperty( 'breakOn' ); + // remove the offset of the character following the newline + inline.offsetX -= inline.xoffset; + lastInlineOffset -= inline.xoffset; - } + } - getWhiteSpace() { + } - return this._getProperty( 'whiteSpace' ); + } ); - } + lastInlineOffset += inlineComponent._margin.y + inlineComponent._padding.y; - getTextAlign() { + } ); - return this._getProperty( 'textAlign' ); + // Compute single line and combined lines dimensions + const WHITESPACE = this.getWhiteSpace(); - } + let width = 0; + let lineOffsetY = 0; + lines[0].y = 0; - getFontColor() { + lines.forEach( ( line, i ) => { - return this._getProperty( 'fontColor' ); + // starts by processing whitespace, it will return a collapsed left offset + const whiteSpaceOffset = collapseWhitespaceOnInlines( line, WHITESPACE ); - } + // + let lineHeight = 0; + let lineBase = 0; + line.forEach( ( inline ) => { - getFontSupersampling() { + lineHeight = Math.max( lineHeight, inline.lineHeight ); + lineBase = Math.max( lineBase, inline.lineBase ); - return this._getProperty( 'fontSupersampling' ); + inline.offsetX -= whiteSpaceOffset; - } + }); - getFontOpacity() { + line.lineHeight = lineHeight; + line.lineBase = lineBase; - return this._getProperty( 'fontOpacity' ); + const baseLineDelta = lineHeight - lineBase; - } + // process yoffset + line.forEach( ( inline ) => { - getFontPXRange() { + inline.offsetY = lineOffsetY - line.lineHeight + baseLineDelta + lines[ 0 ].lineHeight; - return this._getProperty( 'fontPXRange' ); + }); - } + if( i !== 0 ){ - getBorderRadius() { + // get the previousLine y and increase + line.y = lines[i-1].y - line.lineHeight - INTERLINE; - return this._getProperty( 'borderRadius' ); + } - } + lineOffsetY = lineOffsetY - line.lineHeight - INTERLINE - getBorderWidth() { + // - return this._getProperty( 'borderWidth' ); + 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 ]; - getBorderColor() { + // Right + Left ( left is negative ) + line.width = ( lastInline.offsetX + lastInline.cumulativeWidth + lastInline.paddingRight + lastInline.marginRight ) + line[ 0 ].offsetX; - return this._getProperty( 'borderColor' ); + width = Math.max( width, line.width); + } - } + } ); - getBorderOpacity() { + lines.height = Math.abs(lineOffsetY + INTERLINE ); + lines.width = width; - return this._getProperty( 'borderOpacity' ); + return lines; } - /// SPECIALS - - /** return the first parent with a 'threeOBJ' property */ - getContainer() { - if ( !this.threeOBJ && this.parent ) { +} - return this.parent.getContainer(); +;// CONCATENATED MODULE: ./src/utils/block-layout/Padding.js +/** + * @TODO: This could be integrated in AlignItems + * @param {BoxComponent} boxComponent + * @param {string} DIRECTION + * @param {string} ALIGNMENT + */ +const padItems = function( boxComponent, DIRECTION, ALIGNMENT ){ - } else if ( this.threeOBJ ) { + let snap = 'center'; + let snapXon = 'center'; + let snapYon = 'center'; - return this; + const padding = boxComponent._padding; + const border = boxComponent._borderWidth; - } + if( DIRECTION.indexOf('column') !== -1 ) { - return Defaults.container; + if( ALIGNMENT === 'start' ) { + snap = snapXon = 'left'; + }else if( ALIGNMENT === 'end' ){ + snap = snapXon ='right'; + }else { + snap = 'centerX'; + } + } else { + /* eslint-disable no-lonely-if */ + if( ALIGNMENT === 'start' ) { + snap = snapYon = 'top'; + }else if( ALIGNMENT === 'end' ){ + snap = snapYon ='bottom'; + }else{ + snap = 'centerY'; } + /* eslint-enable no-lonely-if */ - /** Get the number of UI parents above this elements (0 if no parent) */ - getParentsNumber( i ) { + } - i = i || 0; + // 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 ( this.parentUI ) { - return this.parentUI.getParentsNumber( i + 1 ); + if( snapXon === 'left' ) { - } + x = (padding.w - padding.y) / 2 + (border.w - border.y) / 2; - return i; + } else if( snapXon === 'right' ) { - } + x = - ( padding.y - padding.w ) / 2 - ( border.y - border.w ) / 2; - //////////////////////////////////// - /// GETTERS WITH NO PARENTS LOOKUP - //////////////////////////////////// + } - getBackgroundOpacity() { + if( snapYon === 'top' ) { - return ( !this.backgroundOpacity && this.backgroundOpacity !== 0 ) ? - Defaults.backgroundOpacity : this.backgroundOpacity; + y = - (padding.x - padding.z) / 2 - (border.x - border.z) / 2; - } + } else if( snapYon === 'bottom' ) { - getBackgroundColor() { + y = (padding.z - padding.x) / 2 + (border.z - border.x) / 2; - return this.backgroundColor || Defaults.backgroundColor; + } - } - getBackgroundTexture() { + boxComponent.childrenBoxes.forEach( ( child ) => { - return this.backgroundTexture || Defaults.backgroundTexture(); + let marginX = 0; + let marginY = 0; + // let marginY = ( -child._margin.x + child._margin.z ) /2; + // let marginY = ( -child._margin.x + child._margin.z ) /2; - } + if( snap === 'top' ) { - /** - * @deprecated - * @returns {string} - */ - getAlignContent() { + marginY = - child._margin.x; - return this.alignContent || Defaults.alignContent; + } else if( snap === 'bottom' ) { - } + marginY = child._margin.z; - getAlignItems() { + } else if( snap === 'left' ) { - return this.alignItems || Defaults.alignItems; + marginX = child._margin.w; - } + } else if( snap === 'right' ) { - getContentDirection() { + marginX = - child._margin.y; - return this.contentDirection || Defaults.contentDirection; + } else if( snap === 'centerX' ) { - } + marginX = ( child._margin.w - child._margin.y ) /2; - getJustifyContent() { + } else if( snap === 'centerY' ) { - return this.justifyContent || Defaults.justifyContent; + marginY = ( - child._margin.x + child._margin.z ) /2; } - getInterLine() { + boxComponent.childrenPos[ child.id ]['x'] += x + marginX; + boxComponent.childrenPos[ child.id ]['y'] += y + marginY; - return ( this.interLine === undefined ) ? Defaults.interLine : this.interLine; - } - getOffset() { + } ); - return ( this.offset === undefined ) ? Defaults.offset : this.offset; - } +} - getBackgroundSize() { +;// CONCATENATED MODULE: ./src/components/core/BoxComponent.js +/** - return ( this.backgroundSize === undefined ) ? Defaults.backgroundSize : this.backgroundSize; +Job: Handle everything related to a BoxComponent element dimensioning and positioning - } +Knows: Parents and children dimensions and positions - getHiddenOverflow() { +It's worth noting that in three-mesh-ui, it's the parent Block that computes +its children position. A Block can only have either only box components (Block) +as children, or only inline components (Text, InlineBlock). - return ( this.hiddenOverflow === undefined ) ? Defaults.hiddenOverflow : this.hiddenOverflow; + */ - } - getBestFit() { - return ( this.bestFit === undefined ) ? Defaults.bestFit : this.bestFit; - } - /////////////// - /// UPDATE - /////////////// - /** - * Filters children in order to compute only one times children lists - * @private - */ - _rebuildChildrenLists() { - // Stores all children that are ui - this.childrenUIs = this.children.filter( child => child.isUI ); +class BoxComponent extends InlineManager { - // Stores all children that are box - this.childrenBoxes = this.children.filter( child => child.isBoxComponent ); - // Stores all children that are inline - this.childrenInlines = this.children.filter( child => child.isInline ); + constructor( options ) { - // Stores all children that are text - this.childrenTexts = this.children.filter( child => child.isText ); - } + super( options ); - /** - * Try to retrieve parentUI after each structural change - * @private - */ - _rebuildParentUI = () => { + this.isBoxComponent = true; + this.childrenPos = {}; - if ( this.parent && this.parent.isUI ) { + // Box properties only update once per update layout + this.offsetWidth = 0; + this.offsetHeight = 0; + this.innerWidth = 0; + this.innerHeight = 0; - this.parentUI = this.parent; + this.centerX = 0; + this.centerY = 0; - } else { + } + + /** + * + */ + computeBoxProperties() { - this.parentUI = null; + this.offsetWidth = this.getOffsetWidth(); + this.offsetHeight = this.getOffsetHeight(); - } + this.innerWidth = this.getInnerWidth(); + this.innerHeight = this.getInnerHeight(); - }; + this.centerX = this.getCenterX(); + this.centerY = this.getCenterY(); - /** - * When the user calls component.add, it registers for updates, - * then call THREE.Object3D.add. - */ - add() { + } - for ( const id of Object.keys( arguments ) ) { - // An inline component relies on its parent for positioning - if ( arguments[ id ].isInline ) this.update( null, true ); + /** + * Return the sum of all this component's children sides + their margin + * @param {string} side + * @return {number} + */ + getChildrenSideSum( side ) { - } + return this.childrenBoxes.reduce( ( accu, child ) => { - const result = super.add( ...arguments ); + const CHILD_SIZE = ( side === 'width' ) ? + ( child.getOffsetWidth() + child._margin.y + child._margin.w ) : + ( child.getOffsetHeight() + child._margin.x + child._margin.z ); - this._rebuildChildrenLists(); + return accu + CHILD_SIZE; - return result; + }, 0 ); } /** - * When the user calls component.remove, it registers for updates, - * then call THREE.Object3D.remove. - */ - remove() { + * Look in parent record what is the instructed position for this component, then set its position + **/ + setPosFromParentRecords() { - for ( const id of Object.keys( arguments ) ) { + if ( this.parentUI && this.parentUI.childrenPos[ this.id ] ) { - // An inline component relies on its parent for positioning - if ( arguments[ id ].isInline ) this.update( null, true ); + this.position.x = this.parentUI.childrenPos[ this.id ].x; + this.position.y = this.parentUI.childrenPos[ this.id ].y; } - const result = super.remove( ...arguments ); - - this._rebuildChildrenLists(); - - return result; - } - // + /** + * Position inner elements according to dimensions and layout parameters. + * */ + computeChildrenPosition() { - update( updateParsing, updateLayout, updateInner ) { + if ( this.childrenUIs.length > 0 ) { - UpdateManager.requestUpdate( this, updateParsing, updateLayout, updateInner ); + const DIRECTION = this.getContentDirection(); + const ALIGNMENT = this.getAlignItems(); - } + let directionalOffset; - onAfterUpdate() { - } + switch ( DIRECTION ) { - /** - * Set this component's passed parameters. - * If necessary, take special actions. - * Update this component unless otherwise specified. - */ - set( options ) { + case ROW : + directionalOffset = -this.innerWidth / 2; + break; - let parsingNeedsUpdate, layoutNeedsUpdate, innerNeedsUpdate; + case ROW_REVERSE : + directionalOffset = this.innerWidth / 2; + break; - // Register to the update manager, so that it knows when to update + case COLUMN : + directionalOffset = this.innerHeight / 2; + break; - UpdateManager.register( this ); + case COLUMN_REVERSE : + directionalOffset = -this.innerHeight / 2; + break; - // Abort if no option passed + } - if ( !options || JSON.stringify( options ) === JSON.stringify( {} ) ) return; - // DEPRECATION Warnings until -------------------------------------- 7.x.x --------------------------------------- + const REVERSE = -Math.sign( directionalOffset ); - // Align content has been removed - if ( options[ 'alignContent' ] ) { + contentDirection( this, DIRECTION, directionalOffset, REVERSE ); + justifyContent( this, DIRECTION, directionalOffset, REVERSE ); + alignItems( this, DIRECTION ); + padItems( this, DIRECTION, ALIGNMENT ); - options[ 'alignItems' ] = options[ 'alignContent' ]; + } - if ( !options[ 'textAlign' ] ) { + } - options[ 'textAlign' ] = options[ 'alignContent' ]; - } + /** + * Returns the highest linear dimension among all the children of the passed component + * MARGIN INCLUDED + * @param {string} direction + * @return {number} + */ + getHighestChildSizeOn( direction ) { - console.warn( '`alignContent` property has been deprecated, please rely on `alignItems` and `textAlign` instead.' ); + return this.childrenBoxes.reduce( ( accu, child ) => { - delete options[ 'alignContent' ]; + const maxSize = direction === 'width' ? + child.getOffsetWidth() + child._margin.y + child._margin.w : + child.getOffsetHeight() + child._margin.x + child._margin.z; - } + return Math.max( accu, maxSize ); - // Align items left top bottom right will be removed - if ( options[ 'alignItems' ] ) { + }, 0 ); - warnAboutDeprecatedAlignItems( options[ 'alignItems' ] ); + } - } + /** + * Obtain the outer width according to box-sizing + * @return {number} + */ + getOffsetWidth() { + const base = this.getStretchedWidth() || this.width || this.getAutoWidth(); + if ( this.getBoxSizing() === 'border-box' ) { - // Set this component parameters according to options, and trigger updates accordingly - // The benefit of having two types of updates, is to put everthing that takes time - // in one batch, and the rest in the other. This way, efficient animation is possible with - // attribute from the light batch. + return base; - for ( const prop of Object.keys( options ) ) { + } - if ( this[ prop ] != options[ prop ] ) { + return base + this._padding.y + this._padding.w + this._borderWidth.y + this._borderWidth.w; - switch ( prop ) { + } - case 'content' : - case 'fontWeight' : - case 'fontStyle' : - case 'whiteSpace': // @TODO : Whitespace could also just be layouting - if ( this.isText ) parsingNeedsUpdate = true; - layoutNeedsUpdate = true; - this[ prop ] = options[ prop ]; - break; + /** + * Obtain the outer height according to box-sizing + * @return {number} + */ + getOffsetHeight() { - // Only layout now - Not anymore parsing - case 'fontSize' : - case 'fontKerning' : - case 'breakOn': - layoutNeedsUpdate = true; - this[ prop ] = options[ prop ]; - break; + const base = this.getStretchedHeight() || this.height || this.getAutoHeight(); - case 'bestFit' : - if ( this.isBlock ) { - parsingNeedsUpdate = true; - layoutNeedsUpdate = true; - } - this[ prop ] = options[ prop ]; - break; + if ( this.getBoxSizing() === 'border-box' ) { - case 'width' : - case 'height' : - case 'padding' : - // @TODO: I don't think this is true anymore - if ( this.isInlineBlock || ( this.isBlock && this.getBestFit() != 'none' ) ) parsingNeedsUpdate = true; - layoutNeedsUpdate = true; - this[ prop ] = options[ prop ]; - break; + return base; - case 'letterSpacing' : - case 'interLine' : - // @TODO: I don't think this is true anymore - if ( this.isBlock && this.getBestFit() != 'none' ) parsingNeedsUpdate = true; - layoutNeedsUpdate = true; - this[ prop ] = options[ prop ]; - break; + } - case 'margin' : - case 'contentDirection' : - case 'justifyContent' : - case 'alignContent' : - case 'alignItems' : - case 'textAlign' : - case 'textType' : - layoutNeedsUpdate = true; - this[ prop ] = options[ prop ]; - break; + return base + this._padding.x + this._padding.z + this._borderWidth.x + this._borderWidth.z; - case 'fontColor' : - case 'fontOpacity' : - case 'fontSupersampling' : - case 'offset' : - case 'backgroundColor' : - case 'backgroundOpacity' : - case 'backgroundTexture' : - case 'backgroundSize' : - case 'borderRadius' : - case 'borderWidth' : - case 'borderColor' : - case 'borderOpacity' : - innerNeedsUpdate = true; - this[ prop ] = options[ prop ]; - break; + } - case 'hiddenOverflow' : - this[ prop ] = options[ prop ]; - break; + /** + * Obtain the inner width according to box-sizing + * @return {number} + */ + getInnerWidth() { - } + const base = this.width || this.getAutoWidth(); - } + if ( this.getBoxSizing() === 'border-box' ) { - } + return base - ( this._padding.y + this._padding.w + this._borderWidth.y + this._borderWidth.w ); + } - // special cases, this.update() must be called only when some files finished loading + return base; - // Selection of fontFamily and font property - // 1. Preferred way, give a {FontFamily} property - if ( options.fontFamily instanceof FontFamily ) { + } - this.fontFamily = options.fontFamily; - this.font = options.fontFamily.getVariant( FontWeight_NORMAL, FontStyle_NORMAL ); + /** + * Obtain the inner height according to box-sizing + * @return {number} + */ + getInnerHeight() { - } + const base = this.height || this.getAutoHeight(); - // 1.1 Preferred way, a bit annoying to check options.fontTexture ( retro-compatibility ) - else if( typeof options.fontFamily === 'string' && !options.fontTexture ) { + if ( this.getBoxSizing() === 'border-box' ) { - const fontFamily = font_FontLibrary.getFontFamily( options.fontFamily ); + return base - (this._padding.x + this._padding.z + this._borderWidth.x + this._borderWidth.z ); - if( fontFamily ){ + } - this.fontFamily = fontFamily; - this.font = fontFamily.getVariant( FontWeight_NORMAL, FontStyle_NORMAL ); + return base; - } + } - } - // 2. < v7.x.x way - else if ( options.fontFamily && options.fontTexture ) { - // Set from old way, check if that family is already registered - const fontName = options.fontFamily.pages ? options.fontFamily.info.face : options.fontFamily; + getBoxSizing() { return this.boxSizing || 'border-box'; } - let fontFamily = font_FontLibrary.getFontFamily( fontName ); + /** + * Retrieve the center X according to box sized dimensions + * @return {number} + */ + getCenterX() { + const leftSide = this._padding.w + this._borderWidth.w; + const rightSide = this._padding.y + this._borderWidth.y; - if ( !fontFamily ) { + return (leftSide - rightSide ) / 2; + } - fontFamily = font_FontLibrary.addFontFamily( fontName ) - .addVariant( FontWeight_NORMAL, FontStyle_NORMAL, options.fontFamily, options.fontTexture ); + /** + * Retrieve the center Y according to box sized dimensions + * @return {number} + */ + getCenterY() { + const topSide = this._padding.x + this._borderWidth.x; + const bottomSide = this._padding.z + this._borderWidth.z; - } + return ( bottomSide - topSide ) / 2; + } - this.fontFamily = fontFamily; - // @TODO: Add more variant selection - this.font = fontFamily.getVariant( FontWeight_NORMAL, FontStyle_NORMAL ); + /** + * Retrieve the automatic height from children boxes + * @return {number} + */ + getAutoHeight() { - } + const DIRECTION = this.getContentDirection(); - // if font kerning changes for a child of a block with Best Fit enabled, we need to trigger parsing for the parent as well. - if ( this.parentUI && this.parentUI.getBestFit() != 'none' ) this.parentUI.update( true, true, false ); + switch ( DIRECTION ) { - // Call component update + case 'row' : + case 'row-reverse' : + return this.getHighestChildSizeOn( 'height' ); - this.update( parsingNeedsUpdate, layoutNeedsUpdate, innerNeedsUpdate ); + case 'column' : + case 'column-reverse' : + return this.getChildrenSideSum( 'height' ); - if ( layoutNeedsUpdate ) this.getHighestParent().update( false, true, false ); + default : + console.error( `Invalid contentDirection : ${DIRECTION}` ); + break; } - ///////////////////// - // STATES MANAGEMENT - ///////////////////// + } - /** Store a new state in this component, with linked attributes */ - setupState( options ) { + /** + * + * @return {number} + */ + getAutoWidth() { - this.states[ options.state ] = { - attributes: options.attributes, - onSet: options.onSet - }; + const DIRECTION = this.getContentDirection(); - } + switch ( DIRECTION ) { - /** Set the attributes of a stored state of this component */ - setState( state ) { + case 'row' : + case 'row-reverse' : + return this.getChildrenSideSum( 'width' ); - const savedState = this.states[ state ]; - if ( !savedState ) { - console.warn( `state "${state}" does not exist within this component` ); - return; - } + case 'column' : + case 'column-reverse' : + return this.getHighestChildSizeOn( 'width' ); - if ( state === this.currentState ) return; + default : + console.error( `Invalid contentDirection : ${DIRECTION}` ); + break; - this.currentState = state; + } - if ( savedState.onSet ) savedState.onSet(); + } - if ( savedState.attributes ) this.set( savedState.attributes ); + /** + * + * @return {number} + */ + getStretchedHeight(){ - } + if( this.parentUI && this.parentUI.getAlignItems() === 'stretch' && this.parentUI.getContentDirection().indexOf('row') !== -1 ) { - /** Get completely rid of this component and its children, also unregister it for updates */ - clear() { + return this.parentUI.getInnerHeight(); - this.traverse( ( obj ) => { + } - UpdateManager.disposeOf( obj ); + return 0; + } - if ( obj.material ) obj.material.dispose(); + /** + * + * @return {number} + */ + getStretchedWidth(){ - if ( obj.geometry ) obj.geometry.dispose(); + if( this.parentUI && this.parentUI.getAlignItems() === 'stretch' && this.parentUI.getContentDirection().indexOf('column') !== -1 ) { - } ); + return this.parentUI.getInnerWidth(); } - }; + return 0; + } } -;// CONCATENATED MODULE: ./src/components/core/MaterialManager.js -/* eslint-disable camelcase */ -//@TODO: Get rid of non camelcase uniforms +;// CONCATENATED MODULE: ./src/frame/Frame.js /** + * Returns a basic plane mesh. + */ +class Frame extends external_THREE_namespaceObject.Mesh { -Job: -- Host the materials of a given component. -- Update a component's materials clipping planes. -- Update a material uniforms and such. + constructor( material ) { -Knows: -- Its component materials. -- Its component ancestors clipping planes. + const geometry = new external_THREE_namespaceObject.PlaneBufferGeometry(); - */ -function MaterialManager( Base ) { + // Add uv for borders computations by copying uv + geometry.setAttribute('uvB', new external_THREE_namespaceObject.BufferAttribute( + new Float32Array( + geometry.getAttribute('uv').array + ), 2)).name = 'uvB'; - return class MaterialManager extends Base { + super( geometry, material ); - constructor( options ) { + this.name = 'MeshUI-Frame'; - super( options ); + } - this.textUniforms = { - u_texture: { value: null }, - u_color: { value: null }, - u_opacity: { value: null }, - u_pxRange: { value: null }, - u_useRGSS: { value: null }, - }; +} - this.backgroundUniforms = { - u_texture: { value: null }, - u_color: { value: null }, - u_opacity: { value: null }, - u_backgroundMapping: { value: null }, - u_borderWidth: { value: null }, - u_borderColor: { value: null }, - u_borderRadiusTopLeft: { value: null }, - u_borderRadiusTopRight: { value: null }, - u_borderRadiusBottomRight: { value: null }, - u_borderRadiusBottomLeft: { value: null }, - u_borderOpacity: { value: null }, - u_size: { value: new external_THREE_namespaceObject.Vector2( 1, 1 ) }, - u_tSize: { value: new external_THREE_namespaceObject.Vector2( 1, 1 ) } - }; +;// CONCATENATED MODULE: ./src/frame/renderers/ShaderChunk/frame-border.pars.vertex.glsl.js +/* harmony default export */ const frame_border_pars_vertex_glsl = (/* glsl */` - } +// FrameBorder vertex pars +attribute vec2 uvB; +varying vec2 vUvB; - /** - * Update backgroundMaterial uniforms. - * Used within MaterialManager and in Block and InlineBlock innerUpdates. - */ - updateBackgroundMaterial() { +`); - this.backgroundUniforms.u_texture.value = this.getBackgroundTexture(); +;// CONCATENATED MODULE: ./src/frame/renderers/ShaderChunk/frame-border.vertex.glsl.js +/* harmony default export */ const frame_border_vertex_glsl = (/* glsl */` - this.backgroundUniforms.u_tSize.value.set( - this.backgroundUniforms.u_texture.value.image.width, - this.backgroundUniforms.u_texture.value.image.height - ); + // FrameBorder vertex shader + vUvB = uvB; - if ( this.size ) this.backgroundUniforms.u_size.value.copy( this.size ); +`); - if ( this.backgroundUniforms.u_texture.value.isDefault ) { +;// CONCATENATED MODULE: ./src/frame/renderers/ShaderChunk/frame-border.pars.fragment.glsl.js +/* harmony default export */ const frame_border_pars_fragment_glsl = (/* glsl */` - this.backgroundUniforms.u_color.value = this.getBackgroundColor(); +// borders sequences are : x:TOP, y:RIGHT, z:BOTTOM, w:LEFT +uniform vec4 borderWidth; +uniform vec3 borderColor; +uniform float borderOpacity; +uniform vec4 borderRadius; - this.backgroundUniforms.u_opacity.value = this.getBackgroundOpacity(); +varying vec2 vUvB; - } else { +// Borders +float getEdgeDist() { - this.backgroundUniforms.u_color.value = this.backgroundColor || Defaults.backgroundWhiteColor; + // This allows to go the uv position in a [-1, 1] referencial system + vec2 ndc = vec2( vUvB.x * 2.0 - 1.0, vUvB.y * 2.0 - 1.0 ); - this.backgroundUniforms.u_opacity.value = ( !this.backgroundOpacity && this.backgroundOpacity !== 0 ) ? - Defaults.backgroundOpaqueOpacity : - this.backgroundOpacity; + // + vec2 planeSpaceCoord = vec2( frameSize.x * 0.5 * ndc.x, frameSize.y * 0.5 * ndc.y ); + vec2 corner = frameSize * 0.5; + vec2 offsetCorner = corner - abs( planeSpaceCoord ); - } + float innerRadDist = min( offsetCorner.x, offsetCorner.y ) * -1.0; - this.backgroundUniforms.u_backgroundMapping.value = ( () => { + if (vUvB.x < 0.5 && vUvB.y >= 0.5) { + float roundedDist = length( max( abs( planeSpaceCoord ) - frameSize * 0.5 + borderRadius.x, 0.0 ) ) - borderRadius.x; + float s = step( innerRadDist * -1.0, borderRadius.x ); + return mix( innerRadDist, roundedDist, s ); + } + if (vUvB.x >= 0.5 && vUvB.y >= 0.5) { + float roundedDist = length( max( abs( planeSpaceCoord ) - frameSize * 0.5 + borderRadius.y, 0.0 ) ) - borderRadius.y; + float s = step( innerRadDist * -1.0, borderRadius.y ); + return mix( innerRadDist, roundedDist, s ); + } + if (vUvB.x >= 0.5 && vUvB.y < 0.5) { + float roundedDist = length( max( abs( planeSpaceCoord ) - frameSize * 0.5 + borderRadius.z, 0.0 ) ) - borderRadius.z; + float s = step( innerRadDist * -1.0, borderRadius.z ); + return mix( innerRadDist, roundedDist, s ); + } + if (vUvB.x < 0.5 && vUvB.y < 0.5) { + float roundedDist = length( max( abs( planeSpaceCoord ) - frameSize * 0.5 + borderRadius.w, 0.0 ) ) - borderRadius.w; + float s = step( innerRadDist * -1.0, borderRadius.w ); + return mix( innerRadDist, roundedDist, s ); + } +} - switch ( this.getBackgroundSize() ) { +`); - case 'stretch': - return 0; - case 'contain': - return 1; - case 'cover': - return 2; +;// CONCATENATED MODULE: ./src/frame/renderers/ShaderChunk/frame-border.fragment.glsl.js +/* harmony default export */ const frame_border_fragment_glsl = (/* glsl */` - } +float edgeDist = getEdgeDist(); +float change = fwidth( edgeDist ); - } )(); +float alpha = smoothstep( change, 0.0, edgeDist ); +diffuseColor.a *= alpha; - const borderRadius = this.getBorderRadius(); - this.backgroundUniforms.u_borderWidth.value = this.getBorderWidth(); - this.backgroundUniforms.u_borderColor.value = this.getBorderColor(); - this.backgroundUniforms.u_borderOpacity.value = this.getBorderOpacity(); +// if the length square is not zerp +if( borderWidth.x * borderWidth.x + borderWidth.y * borderWidth.y + borderWidth.z * borderWidth.z + borderWidth.w * borderWidth.w > 0.0 ) +{ - // + vec4 borderColor = vec4( borderColor, borderOpacity * alpha ); + float stp = smoothstep( edgeDist + change, edgeDist, borderWidth.x * -1.0 ); - if ( Array.isArray( borderRadius ) ) { + // @TODO: Implement border width sequence : top,right,bottom,left + // if( vUvB.x <= borderWidth.w || vUvB.x >= 1.0 - borderWidth.y || vUvB.y >= 1.0 - borderWidth.x || vUvB.y <= borderWidth.z ) + // { - this.backgroundUniforms.u_borderRadiusTopLeft.value = borderRadius[ 0 ]; - this.backgroundUniforms.u_borderRadiusTopRight.value = borderRadius[ 1 ]; - this.backgroundUniforms.u_borderRadiusBottomRight.value = borderRadius[ 2 ]; - this.backgroundUniforms.u_borderRadiusBottomLeft.value = borderRadius[ 3 ]; + // // would be nicer with smoothstep + // diffuseColor.rgb = borderColor.rgb; - } else { + //} - this.backgroundUniforms.u_borderRadiusTopLeft.value = borderRadius; - this.backgroundUniforms.u_borderRadiusTopRight.value = borderRadius; - this.backgroundUniforms.u_borderRadiusBottomRight.value = borderRadius; - this.backgroundUniforms.u_borderRadiusBottomLeft.value = borderRadius; + diffuseColor = mix( diffuseColor, borderColor, stp ); - } +} +`); - } +;// CONCATENATED MODULE: ./src/frame/renderers/ShaderChunk/frame-common.pars.fragment.glsl.js +/* harmony default export */ const frame_common_pars_fragment_glsl = (/* glsl */` - /** - * Update backgroundMaterial uniforms. - * Used within MaterialManager and in Text innerUpdates. - */ - updateTextMaterial() { +// To be removed - required for both border and background +uniform vec2 frameSize; +uniform vec2 textureSize; - this.textUniforms.u_texture.value = this.getFontTexture(); - this.textUniforms.u_color.value = this.getFontColor(); - this.textUniforms.u_opacity.value = this.getFontOpacity(); - this.textUniforms.u_pxRange.value = this.getFontPXRange(); - this.textUniforms.u_useRGSS.value = this.getFontSupersampling(); +`); - } +;// CONCATENATED MODULE: ./src/frame/renderers/ShaderChunk/frame-background.pars.fragment.glsl.js +/* harmony default export */ const frame_background_pars_fragment_glsl = (/* glsl */` - /** Called by Block, which needs the background material to create a mesh */ - getBackgroundMaterial() { +#ifdef USE_MAP - if ( !this.backgroundMaterial || !this.backgroundUniforms ) { +vec4 sampleTexture() { - this.backgroundMaterial = this._makeBackgroundMaterial(); + vec2 uv = vUv; - } + // default stretch + #if BACKGROUND_MAPPING != 0 - return this.backgroundMaterial; + float textureRatio = textureSize.x / textureSize.y; + float panelRatio = frameSize.x / frameSize.y; + float ratio = panelRatio / textureRatio; + float ratio2 = textureRatio / panelRatio; + // contain + #if BACKGROUND_MAPPING == 1 + if ( textureRatio < panelRatio ) { // repeat on X + float newX = uv.x * ratio; + newX += 0.5 - 0.5 * ratio; + uv.x = newX; + } else { // repeat on Y + float newY = uv.y * ratio2; + newY += 0.5 - 0.5 * ratio2; + uv.y = newY; + } + #else + // cover + if ( textureRatio < panelRatio ) { // stretch on Y + float newY = uv.y * ratio2; + newY += 0.5 - 0.5 * ratio2; + uv.y = newY; + } else { // stretch on X + float newX = uv.x * ratio; + newX += 0.5 - 0.5 * ratio; + uv.x = newX; } - /** Called by Text to get the font material */ - getFontMaterial() { + #endif - if ( !this.fontMaterial || !this.textUniforms ) { + #endif - this.fontMaterial = this._makeTextMaterial(); + return texture2D( map, uv ); - } +} +#endif +`); - return this.fontMaterial; +;// CONCATENATED MODULE: ./src/frame/renderers/ShaderChunk/frame-background.fragment.glsl.js +/* harmony default export */ const frame_background_fragment_glsl = (/* glsl */` +#ifdef USE_MAP - } + vec4 textureSample = sampleTexture(); + diffuseColor *= textureSample; - /** @private */ - _makeTextMaterial() { +#endif +`); - return new external_THREE_namespaceObject.ShaderMaterial( { - uniforms: this.textUniforms, - transparent: true, - clipping: true, - vertexShader: textVertex, - fragmentShader: textFragment, - extensions: { - derivatives: true - } - } ); +;// CONCATENATED MODULE: ./src/renderers/shaders/ShaderChunkUI.js - } - /** @private */ - _makeBackgroundMaterial() { - return new external_THREE_namespaceObject.ShaderMaterial( { - uniforms: this.backgroundUniforms, - transparent: true, - clipping: true, - vertexShader: backgroundVertex, - fragmentShader: backgroundFragment, - extensions: { - derivatives: true - } - } ); - } - /** - * Update a component's materials clipping planes. - * Called every frame. - */ - updateClippingPlanes( value ) { - const newClippingPlanes = value !== undefined ? value : this.getClippingPlanes(); - if ( JSON.stringify( newClippingPlanes ) !== JSON.stringify( this.clippingPlanes ) ) { - this.clippingPlanes = newClippingPlanes; - if ( this.fontMaterial ) this.fontMaterial.clippingPlanes = this.clippingPlanes; - if ( this.backgroundMaterial ) this.backgroundMaterial.clippingPlanes = this.clippingPlanes; - } - } - }; -} +/* eslint-disable camelcase */ +const ShaderChunkUI = { + msdf_alphaglyph_pars_vertex: msdf_alphaglyph_pars_vertex_glsl, + msdf_alphaglyph_vertex: msdf_alphaglyph_vertex_glsl, + msdf_offset_vertex: msdf_offsetglyph_vertex_glsl, + msdf_alphaglyph_pars_fragment: msdf_alphaglyph_pars_fragment_glsl, + msdf_alphaglyph_fragment: msdf_alphaglyph_fragment_glsl, + frame_border_pars_vertex: frame_border_pars_vertex_glsl, + frame_border_vertex: frame_border_vertex_glsl, + frame_common_pars: frame_common_pars_fragment_glsl, + frame_border_pars_fragment: frame_border_pars_fragment_glsl, + frame_border_fragment: frame_border_fragment_glsl, + frame_background_pars_fragment: frame_background_pars_fragment_glsl, + frame_background_fragment: frame_background_fragment_glsl, +}; +/* eslint-enable camelcase */ + +;// CONCATENATED MODULE: ./src/frame/renderers/ShaderLib/framematerial.glsl.js +// import { ShaderChunkUI } from 'three-mesh-ui'; -//////////////// -// Text shaders -//////////////// -const textVertex = ` +const framematerial_glsl_vertexShader = /* glsl */` +// Would be automatic on three materials and from USE_UV +#ifdef USE_MAP varying vec2 vUv; +#endif + +${ShaderChunkUI.frame_border_pars_vertex} #include void main() { + #ifdef USE_MAP vUv = uv; + #endif + + ${ShaderChunkUI.frame_border_vertex} + vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); gl_Position = projectionMatrix * mvPosition; - gl_Position.z -= 0.00001; #include } -`; - -// +` -const textFragment = ` +const framematerial_glsl_fragmentShader = /* glsl */` -uniform sampler2D u_texture; -uniform vec3 u_color; -uniform float u_opacity; -uniform float u_pxRange; -uniform bool u_useRGSS; +// Basic +uniform vec3 diffuse; +uniform float opacity; -varying vec2 vUv; +${ShaderChunkUI.frame_common_pars} -#include +${ShaderChunkUI.frame_border_pars_fragment} -// functions from the original msdf repo: -// https://github.com/Chlumsky/msdfgen#using-a-multi-channel-distance-field -float median(float r, float g, float b) { - return max(min(r, g), min(max(r, g), b)); -} +#ifdef USE_MAP +varying vec2 vUv; +uniform sampler2D map; +#endif -float screenPxRange() { - vec2 unitRange = vec2(u_pxRange)/vec2(textureSize(u_texture, 0)); - vec2 screenTexSize = vec2(1.0)/fwidth(vUv); - return max(0.5*dot(unitRange, screenTexSize), 1.0); -} +${ShaderChunkUI.frame_background_pars_fragment} -float tap(vec2 offsetUV) { - vec3 msd = texture( u_texture, offsetUV ).rgb; - float sd = median(msd.r, msd.g, msd.b); - float screenPxDistance = screenPxRange() * (sd - 0.5); - float alpha = clamp(screenPxDistance + 0.5, 0.0, 1.0); - return alpha; -} +#include void main() { - float alpha; - - if ( u_useRGSS ) { - - // shader-based supersampling based on https://bgolus.medium.com/sharper-mipmapping-using-shader-based-supersampling-ed7aadb47bec - // per pixel partial derivatives - vec2 dx = dFdx(vUv); - vec2 dy = dFdy(vUv); - - // rotated grid uv offsets - vec2 uvOffsets = vec2(0.125, 0.375); - vec2 offsetUV = vec2(0.0, 0.0); - - // supersampled using 2x2 rotated grid - alpha = 0.0; - offsetUV.xy = vUv + uvOffsets.x * dx + uvOffsets.y * dy; - alpha += tap(offsetUV); - offsetUV.xy = vUv - uvOffsets.x * dx - uvOffsets.y * dy; - alpha += tap(offsetUV); - offsetUV.xy = vUv + uvOffsets.y * dx - uvOffsets.x * dy; - alpha += tap(offsetUV); - offsetUV.xy = vUv - uvOffsets.y * dx + uvOffsets.x * dy; - alpha += tap(offsetUV); - alpha *= 0.25; + vec4 diffuseColor = vec4( diffuse, opacity ); - } else { + // map + ${ShaderChunkUI.frame_background_fragment} - alpha = tap( vUv ); + ${ShaderChunkUI.frame_border_fragment} - } + if( diffuseColor.a < 0.02 ) discard; + // output + gl_FragColor = diffuseColor; - // apply the opacity - alpha *= u_opacity; - // this is useful to avoid z-fighting when quads overlap because of kerning - if ( alpha < 0.02) discard; + #include +} +` +;// CONCATENATED MODULE: ./src/frame/utils/FrameMaterialUtils.js - gl_FragColor = vec4( u_color, alpha ); - #include +//JSDoc related import +/* eslint-disable no-unused-vars */ -} -`; -////////////////////// -// Background shaders -////////////////////// -const backgroundVertex = ` -varying vec2 vUv; +/* eslint-enable no-unused-vars */ -#include -void main() { +class FrameMaterialUtils { - vUv = uv; - vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); - gl_Position = projectionMatrix * mvPosition; - #include -} -`; + /** + * + * @returns {Object<{m: string, t?: (function((Material|ShaderMaterial), string, *): void)}>} + */ + static get mediation() { -// + return _mediationDefinitions; -const backgroundFragment = ` + } -uniform sampler2D u_texture; -uniform vec3 u_color; -uniform float u_opacity; -uniform float u_borderRadiusTopLeft; -uniform float u_borderRadiusTopRight; -uniform float u_borderRadiusBottomLeft; -uniform float u_borderRadiusBottomRight; -uniform float u_borderWidth; -uniform vec3 u_borderColor; -uniform float u_borderOpacity; -uniform vec2 u_size; -uniform vec2 u_tSize; -uniform int u_backgroundMapping; + /** + * Alter a material options with required fontMaterial options and or default values + * @param {Object.} materialOptions + */ + static ensureMaterialOptions( materialOptions ) { + materialOptions.transparent = true; + materialOptions.alphaTest = materialOptions.alphaTest || 0.02; + } -varying vec2 vUv; + /** + * As three-mesh-ui FontMaterial relies on webgl preprocessors, + * lets force the material to have a proper defines object + * @param {Material|ShaderMaterial} threeMaterial + */ + static ensureDefines( threeMaterial ) { + if ( !threeMaterial.defines ) { + threeMaterial.defines = {}; + } + } -#include + /* eslint-disable no-unused-vars */ + /** + * + * @param {Material|ShaderMaterial} threeMaterial + * @param {Object.} materialOptions + */ + static ensureUserData( threeMaterial, materialOptions ) { + threeMaterial.userData.borderColor = { value: null }; + threeMaterial.userData.borderRadius = { value: new external_THREE_namespaceObject.Vector4(0,0,0,0) }; + threeMaterial.userData.borderWidth = { value: new external_THREE_namespaceObject.Vector4(0,0,0,0) }; + threeMaterial.userData.borderOpacity = { value: null }; + threeMaterial.userData.frameSize = { value: new external_THREE_namespaceObject.Vector2( 1, 1 ) }; + threeMaterial.userData.textureSize = { value: new external_THREE_namespaceObject.Vector2( 1, 1 ) }; -float getEdgeDist() { - vec2 ndc = vec2( vUv.x * 2.0 - 1.0, vUv.y * 2.0 - 1.0 ); - vec2 planeSpaceCoord = vec2( u_size.x * 0.5 * ndc.x, u_size.y * 0.5 * ndc.y ); - vec2 corner = u_size * 0.5; - vec2 offsetCorner = corner - abs( planeSpaceCoord ); - float innerRadDist = min( offsetCorner.x, offsetCorner.y ) * -1.0; - if (vUv.x < 0.5 && vUv.y >= 0.5) { - float roundedDist = length( max( abs( planeSpaceCoord ) - u_size * 0.5 + u_borderRadiusTopLeft, 0.0 ) ) - u_borderRadiusTopLeft; - float s = step( innerRadDist * -1.0, u_borderRadiusTopLeft ); - return mix( innerRadDist, roundedDist, s ); - } - if (vUv.x >= 0.5 && vUv.y >= 0.5) { - float roundedDist = length( max( abs( planeSpaceCoord ) - u_size * 0.5 + u_borderRadiusTopRight, 0.0 ) ) - u_borderRadiusTopRight; - float s = step( innerRadDist * -1.0, u_borderRadiusTopRight ); - return mix( innerRadDist, roundedDist, s ); } - if (vUv.x >= 0.5 && vUv.y < 0.5) { - float roundedDist = length( max( abs( planeSpaceCoord ) - u_size * 0.5 + u_borderRadiusBottomRight, 0.0 ) ) - u_borderRadiusBottomRight; - float s = step( innerRadDist * -1.0, u_borderRadiusBottomRight ); - return mix( innerRadDist, roundedDist, s ); + /* eslint-enable no-unused-vars */ + + /** + * + * @param {any} shader + * @param {Material|ShaderMaterial} threeMaterial + */ + static bindUniformsWithUserData( shader, threeMaterial ) { + + shader.uniforms.borderColor = threeMaterial.userData.borderColor; + shader.uniforms.borderRadius = threeMaterial.userData.borderRadius; + shader.uniforms.borderWidth = threeMaterial.userData.borderWidth; + shader.uniforms.borderOpacity = threeMaterial.userData.borderOpacity; + shader.uniforms.frameSize = threeMaterial.userData.frameSize; + shader.uniforms.textureSize = threeMaterial.userData.textureSize; } - if (vUv.x < 0.5 && vUv.y < 0.5) { - float roundedDist = length( max( abs( planeSpaceCoord ) - u_size * 0.5 + u_borderRadiusBottomLeft, 0.0 ) ) - u_borderRadiusBottomLeft; - float s = step( innerRadDist * -1.0, u_borderRadiusBottomLeft ); - return mix( innerRadDist, roundedDist, s ); + + /** + * + * @param shader + */ + static injectShaderChunks( shader ) { + FrameMaterialUtils.injectVertexShaderChunks( shader ); + FrameMaterialUtils.injectFragmentShaderChunks( shader ); } -} -vec4 sampleTexture() { - float textureRatio = u_tSize.x / u_tSize.y; - float panelRatio = u_size.x / u_size.y; - vec2 uv = vUv; - if ( u_backgroundMapping == 1 ) { // contain - if ( textureRatio < panelRatio ) { // repeat on X - float newX = uv.x * ( panelRatio / textureRatio ); - newX += 0.5 - 0.5 * ( panelRatio / textureRatio ); - uv.x = newX; - } else { // repeat on Y - float newY = uv.y * ( textureRatio / panelRatio ); - newY += 0.5 - 0.5 * ( textureRatio / panelRatio ); - uv.y = newY; - } - } else if ( u_backgroundMapping == 2 ) { // cover - if ( textureRatio < panelRatio ) { // stretch on Y - float newY = uv.y * ( textureRatio / panelRatio ); - newY += 0.5 - 0.5 * ( textureRatio / panelRatio ); - uv.y = newY; - } else { // stretch on X - float newX = uv.x * ( panelRatio / textureRatio ); - newX += 0.5 - 0.5 * ( panelRatio / textureRatio ); - uv.x = newX; - } + /** + * + * @param shader + */ + static injectVertexShaderChunks( shader ) { + shader.vertexShader = shader.vertexShader.replace( + '#include ', + '#include \n' + ShaderChunkUI.frame_border_pars_vertex + ); + + // vertex chunks + shader.vertexShader = shader.vertexShader.replace( + '#include ', + '#include \n' + ShaderChunkUI.frame_border_vertex + ) + } - return texture2D( u_texture, uv ).rgba; -} -void main() { + /** + * + * @param shader + */ + static injectFragmentShaderChunks( shader ) { + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + '#include \n' + ShaderChunkUI.frame_background_pars_fragment + ) - float edgeDist = getEdgeDist(); - float change = fwidth( edgeDist ); + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + '#include \n' + ShaderChunkUI.frame_border_pars_fragment + ) - vec4 textureSample = sampleTexture(); - vec3 blendedColor = textureSample.rgb * u_color; + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + '#include \n' + ShaderChunkUI.frame_common_pars + ) - float alpha = smoothstep( change, 0.0, edgeDist ); - float blendedOpacity = u_opacity * textureSample.a * alpha; + // fragment chunks + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + ShaderChunkUI.frame_background_fragment + ) + + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + ShaderChunkUI.frame_border_fragment+'\n#include ' + ) - vec4 frameColor = vec4( blendedColor, blendedOpacity ); - if ( u_borderWidth <= 0.0 ) { - gl_FragColor = frameColor; - } else { - vec4 borderColor = vec4( u_borderColor, u_borderOpacity * alpha ); - float stp = smoothstep( edgeDist + change, edgeDist, u_borderWidth * -1.0 ); - gl_FragColor = mix( frameColor, borderColor, stp ); } - #include } -`; -;// CONCATENATED MODULE: ./src/content/Frame.js +const _backgroundSizeTransformer = function( target, property, value ) { + value = ['stretch','contain','cover'].indexOf(value); + asPreprocessorValueTransformer(target, 'BACKGROUND_MAPPING', value); +} /** - * Returns a basic plane mesh. + * + * @type {Object.<{m:string, t?:(fontMaterial:Material|ShaderMaterial, materialProperty:string, value:any) => void}>} */ -class Frame extends external_THREE_namespaceObject.Mesh { +const _mediationDefinitions = { + alphaTest: { m: 'alphaTest', t: alphaTestTransformer }, + backgroundTexture: { m: 'map' }, + backgroundColor: { m: 'color' }, + backgroundOpacity: { m:'opacity' }, + backgroundSize: { m: 'u_backgroundMapping', t: _backgroundSizeTransformer }, + _borderWidth: { m: 'borderWidth', t: uniformOrUserDataTransformer }, + borderColor: { m: 'borderColor', t: uniformOrUserDataTransformer }, + _borderRadius: { m: 'borderRadius', t: uniformOrUserDataTransformer }, + borderOpacity: { m: 'borderOpacity', t: uniformOrUserDataTransformer }, + size: { m: 'frameSize', t: uniformOrUserDataTransformer }, + tSize: { m: 'textureSize', t: uniformOrUserDataTransformer } +} - constructor( material ) { - const geometry = new external_THREE_namespaceObject.PlaneGeometry(); +;// CONCATENATED MODULE: ./src/frame/materials/FrameMaterial.js - super( geometry, material ); - this.castShadow = true; - this.receiveShadow = true; - this.name = 'MeshUI-Frame'; + +class FrameMaterial extends external_THREE_namespaceObject.ShaderMaterial { + + + /** + * This static method is mandatory for extending ThreeMeshUI.FrameMaterial + * It will provide a transfer description for properties from ThreeMeshUI.Text to THREE.Material + * @see {FrameMaterialUtils.mediation} + * @returns {Object.<{m:string, t?:(frameMaterial:Material|ShaderMaterial, materialProperty:string, value:any) => void}>} + */ + static get mediation() { + + return FrameMaterialUtils.mediation; } -} + constructor() { + super ( { + uniforms: { + alphaTest: { value: 0.02 }, + map: { value: null }, + diffuse: { value: new external_THREE_namespaceObject.Color(0xffffff) }, + opacity: { value: 1.0 }, + borderColor: { value: null }, + borderRadius: { value: new external_THREE_namespaceObject.Vector4(0,0,0,0) }, + borderWidth: { value: new external_THREE_namespaceObject.Vector4(0,0,0,0) }, + borderOpacity: { value: null }, + frameSize: { value: new external_THREE_namespaceObject.Vector2( 1, 1 ) }, + textureSize: { value: new external_THREE_namespaceObject.Vector2( 1, 1 ) } + }, + side: external_THREE_namespaceObject.FrontSide, + transparent: true, + clipping: true, + vertexShader: framematerial_glsl_vertexShader, + fragmentShader: framematerial_glsl_fragmentShader, + extensions: { + derivatives: true + } + } ); + + // webgl preprocessor AlphaTest set by default + this.defines[ 'USE_ALPHATEST' ] = ''; + this.needsUpdate = true; + } -;// CONCATENATED MODULE: ./src/utils/mix.js -let _Base = null; + set map( value ) { -/** - * A function for applying multiple mixins more tersely (less verbose) - * @param {Function[]} mixins - All args to this function should be mixins that take a class and return a class. - */ -function mix( ...mixins ) { + this.uniforms.map.value = value; + if( !value ) { + + if( this.defines['USE_UV'] !== undefined ) { + + delete this.defines['USE_UV']; + this.needsUpdate = true; + + } + + } else if( this.defines['USE_UV'] === undefined ) { - // console.log('initial Base: ', _Base); + this.defines['USE_UV'] = ''; + this.needsUpdate = true; + + } + + this.needsUpdate = true; + + } + + get map(){ + return this.uniforms.map.value; + } + + /** + * + * @returns {number} + */ + get alphaTest() { + + return this.uniforms.alphaTest.value; - if( !_Base ){ - throw new Error("Cannot use mixins with Base null"); } - let Base = _Base; - _Base = null; - let i = mixins.length; - let mixin; + /** + * + * @param {number} v + */ + set alphaTest( v ) { + this.uniforms.alphaTest.value = v; + } - while ( --i >= 0 ) { + /** + * + * @param {number} v + */ + set opacity( v ) { - mixin = mixins[ i ]; - Base = mixin( Base ); + if( this.uniforms ) + this.uniforms.opacity.value = v; } - return Base; + /** + * The color will be the diffuse uniform + * @returns {number} + */ + get opacity() { -} + return this.uniforms.opacity.value; -mix.withBase = ( Base ) => { + } - _Base = Base; + /** + * The color will be the diffuse uniform + * @returns {Color} + */ + get color() { - return mix; + return this.uniforms.diffuse.value; -}; + } -;// CONCATENATED MODULE: ./src/components/Block.js + /** + * + * @param {Color} v + */ + set color( v ) { + + this.uniforms.diffuse.value = v; + + } +} +;// CONCATENATED MODULE: ./src/components/Block.js @@ -4387,14 +6426,11 @@ Job: - Calls BoxComponent's API to position its children box components - Calls InlineManager's API to position its children inline components - Call creation and update functions of its background planes - */ -class Block extends mix.withBase( external_THREE_namespaceObject.Object3D )( - BoxComponent, - InlineManager, - MaterialManager, - MeshUIComponent -) { + + + +class Block extends BoxComponent { constructor( options ) { @@ -4406,56 +6442,53 @@ class Block extends mix.withBase( external_THREE_namespaceObject.Object3D )( this.size = new external_THREE_namespaceObject.Vector2( 1, 1 ); - this.frame = new Frame( this.getBackgroundMaterial() ); + // this._main = new Frame( this.getBackgroundMaterial() ); + this._material = new FrameMaterial(); + + /** + * + * @type {Frame} + * @protected + */ + this._main = new Frame( this._material ); + + this._materialMediation = { ...FrameMaterialUtils.mediation }; // This is for hiddenOverflow to work - this.frame.onBeforeRender = () => { + this._main.onBeforeRender = () => { if ( this.updateClippingPlanes ) { this.updateClippingPlanes(); - } - - }; - - this.add( this.frame ); - - // Lastly set the options parameters to this object, which will trigger an update - - this.set( options ); - - } - - //////////// - // UPDATE - //////////// - - parseParams() { - - const bestFit = this.getBestFit(); + } - if ( bestFit != 'none' && this.childrenTexts.length ) { + }; - this.calculateBestFit( bestFit ); + this.add( this._main ); - } else { + // Lastly set the options parameters to this object, which will trigger an update - this.childrenTexts.forEach( child => { + this.set( options ); - child._fitFontSize = undefined; + this._transferToMaterial(); - } ); - } } + get frame() { return this._main; } + //////////// + // UPDATE + //////////// + + parseParams() {} + updateLayout() { // Get temporary dimension + this.computeBoxProperties(); - const WIDTH = this.getWidth(); - - const HEIGHT = this.getHeight(); + const WIDTH = this.offsetWidth; + const HEIGHT = this.offsetHeight; if ( !WIDTH || !HEIGHT ) { @@ -4465,11 +6498,11 @@ class Block extends mix.withBase( external_THREE_namespaceObject.Object3D )( } this.size.set( WIDTH, HEIGHT ); - this.frame.scale.set( WIDTH, HEIGHT, 1 ); + this._main.scale.set( WIDTH, HEIGHT, 1 ); - if ( this.frame ) this.updateBackgroundMaterial(); + // if ( this._main ) this.updateBackgroundMaterial(); - this.frame.renderOrder = this.getParentsNumber(); + this._main.renderOrder = this.getParentsNumber(); // Position this element according to earlier parent computation. // Delegate to BoxComponent. @@ -4515,35 +6548,12 @@ class Block extends mix.withBase( external_THREE_namespaceObject.Object3D )( } - if ( this.frame ) this.updateBackgroundMaterial(); + // if ( this._main ) this.updateBackgroundMaterial(); } } -;// CONCATENATED MODULE: ./src/components/core/InlineComponent.js -/** - -Job: nothing yet, but adding a isInline parameter to an inline component - -Knows: parent dimensions - - */ -function InlineComponent( Base ) { - - return class InlineComponent extends Base { - - constructor( options ) { - - super( options ); - - this.isInline = true; - - } - - }; -} - ;// CONCATENATED MODULE: ./src/utils/deepDelete.js @@ -5507,8 +7517,14 @@ function computeMorphedAttributes( object ) { +//JSDoc related imports +/* eslint-disable no-unused-vars */ + + + +/* eslint-enable no-unused-vars */ /** @@ -5520,18 +7536,16 @@ Knows: - Its text content (string) - Font attributes ('font', 'fontSize'.. etc..) - Parent block - */ -class Text extends mix.withBase( external_THREE_namespaceObject.Object3D )( - InlineComponent, - MaterialManager, - MeshUIComponent -) { + + +class Text extends MeshUIComponent { constructor( options ) { - super(); + super( options ); + this.isInline = true; this.isText = true; // adds internal properties @@ -5544,14 +7558,14 @@ class Text extends mix.withBase( external_THREE_namespaceObject.Object3D )( /** * - * @type {MSDFTypographyCharacter[]} + * @type {MSDFTypographicGlyph[]} * @private */ this._textContentGlyphs = null; /** * - * @type {MSDFInlineCharacter[]} + * @type {MSDFInlineGlyph[]} * @private */ this._textContentInlines = null; @@ -5563,40 +7577,13 @@ class Text extends mix.withBase( external_THREE_namespaceObject.Object3D )( } /** - * Temporary code - * @param {FontVariant} value - */ - set font( value ) { - - // if a previous font isset, be sure not event remains - if ( this._font && !this._font.isReady ) { - - this._font.removeEventListener( 'ready', this._handleFontVariantReady ); - - } - - this._font = value; - - // new font, means rebuild inlines, now or soon - if ( !this._font.isReady ) { - - this.inlines = null; - this._font.addEventListener( 'ready', this._handleFontVariantReady ); - - } else { - - this._handleFontVariantReady(); - - } - - } - - /** - * + * Trigger some update when the font is ready * @private */ _handleFontVariantReady = () => { + this._transferToMaterial(); + // request parse update and parent layout this.update( true, true, false ); this.getHighestParent().update( false, true, false ); @@ -5606,6 +7593,11 @@ class Text extends mix.withBase( external_THREE_namespaceObject.Object3D )( }; + /** + * When adding a text to a parent ui element, + * acquire parent font, if needed + * @private + */ _acquireFont = () => { if( !this._font ) { @@ -5646,39 +7638,62 @@ class Text extends mix.withBase( external_THREE_namespaceObject.Object3D )( } - /** - * - * @returns {FontVariant} - */ - get font() { - return this._font; - } - /******************************************************************************************************************* * GETTERS - SETTERS ******************************************************************************************************************/ - // get whiteSpace(){ - // - // // initialisation can look on parents - // if( !this._whiteSpace ) this._whiteSpace = this.getWhiteSpace(); - // - // return this._whiteSpace; - // - // } - // - // set whiteSpace( value ) { - // - // if( this._whiteSpace === value ) return; - // - // value = Whitespace.isValid( value ); - // - // this._whiteSpace = value; - // - // // request parse and layout - // this.update( true, true, false ); - // - // } + /** + * @override + * @param {FontVariant} value + */ + set font( value ) { + + // if a previous font isset, be sure no event remains + if ( this._font && !this._font.isReady ) { + + this._font.removeEventListener( 'ready', this._handleFontVariantReady ); + + } + + this._font = value; + + // new font, means rebuild inlines, now or soon + if ( !this._font.isReady ) { + + this.inlines = null; + this._font.addEventListener( 'ready', this._handleFontVariantReady ); + + } else { + + this._handleFontVariantReady(); + + } + + // update font material according to font variant + if( !this._material ) { + + this.material = new this._font.fontMaterial(); + + } else { + + + + // @TODO : Only recreate a material instance if needed, + // prevent user that its custom material may no longer be compatible with update fontVariant implementation + const isDefaultMaterial = this._material.isDefault && this._material.isDefault(); + if( isDefaultMaterial && !(this._material instanceof this._font.fontMaterial) ) { + + this.material = new this._font.fontMaterial(); + + } else { + + this._transferToMaterial(); + + } + + } + + } _buildContentKernings(){ @@ -5700,6 +7715,7 @@ class Text extends mix.withBase( external_THREE_namespaceObject.Object3D )( } + /////////// // UPDATES /////////// @@ -5728,11 +7744,28 @@ class Text extends mix.withBase( external_THREE_namespaceObject.Object3D )( // Now that we know exactly which characters will be printed // Store the character description ( typographic properties ) - this._textContentGlyphs = this._textContent.split( '' ).map( ( char ) => this._font.getTypographyCharacter( char ) ); + this._textContentGlyphs = this._textContent.split( '' ).map( ( char ) => this._font.getTypographicGlyph( char ) ); // And from the descriptions ( which are static/freezed per character per font ) // Build the inline - this._textContentInlines = this._textContentGlyphs.map( ( glyphBox ) => glyphBox.asInlineCharacter() ); + this._textContentInlines = this._textContentGlyphs.map( ( glyphBox ) => glyphBox.asInlineGlyph() ); + this._buildContentKernings(); + + // 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; + + } + + this.inlines = this._textContentInlines; @@ -5756,35 +7789,36 @@ class Text extends mix.withBase( external_THREE_namespaceObject.Object3D )( const charactersAsGeometries = this.inlines.map( inline => - this._font.getGeometryCharacter( inline ) + this._font.getGeometricGlyph( inline, this.getSegments() ) .translate( inline.offsetX, inline.offsetY, 0 ) ); const mergedGeom = mergeBufferGeometries( charactersAsGeometries ); - this.textContent = new external_THREE_namespaceObject.Mesh( mergedGeom, this.getFontMaterial() ); + // console.log(this.uuid); - this.textContent.renderOrder = Infinity; + this._main = new external_THREE_namespaceObject.Mesh( mergedGeom, this._material ); + if( this.customDepthMaterial ){ - // This is for hiddenOverflow to work - this.textContent.onBeforeRender = this._onBeforeRender + this._main.customDepthMaterial = this.customDepthMaterial; - this.updateTextMaterial(); + } - this.add( this.textContent ); + this._transferToMesh(); - } + this._main.renderOrder = Infinity; - this.position.z = this.getOffset(); + // This is for hiddenOverflow to work + this._main.onBeforeRender = this._onBeforeRender - } + this.add( this._main ); - updateInner() { + } - this.position.z = this.getOffset(); + } - if ( this.textContent ) this.updateTextMaterial(); + updateInner() { } @@ -5808,7 +7842,7 @@ class Text extends mix.withBase( external_THREE_namespaceObject.Object3D )( /** * - * @type {MSDFInlineCharacter} + * @type {MSDFInlineGlyph} */ const inline = this._textContentInlines[ i ]; @@ -5852,9 +7886,6 @@ class Text extends mix.withBase( external_THREE_namespaceObject.Object3D )( - - - /** * Job: * - computing its own size according to user measurements or content measurement @@ -5864,28 +7895,26 @@ class Text extends mix.withBase( external_THREE_namespaceObject.Object3D )( * - Its measurements parameter * - Parent block */ -class InlineBlock extends mix.withBase( external_THREE_namespaceObject.Object3D )( - InlineComponent, - BoxComponent, - InlineManager, - MaterialManager, - MeshUIComponent -) { +class InlineBlock extends MeshUIComponent { constructor( options ) { super( options ); + this.isInline = true; this.isInlineBlock = true; // this.size = new external_THREE_namespaceObject.Vector2( 1, 1 ); - this.frame = new Frame( this.getBackgroundMaterial() ); + this._material = new FrameMaterial(); + this._main = new Frame( this._material ); + + this._materialMediation = { ...FrameMaterialUtils.mediation }; // This is for hiddenOverflow to work - this.frame.onBeforeRender = () => { + this._main.onBeforeRender = () => { if ( this.updateClippingPlanes ) { @@ -5895,46 +7924,39 @@ class InlineBlock extends mix.withBase( external_THREE_namespaceObject.Object3D }; - this.add( this.frame ); + this.add( this._main ); // Lastly set the options parameters to this object, which will trigger an update - this.set( options ); - - } + if( options.backgroundOpacity === undefined ){ - /////////// - // UPDATES - /////////// + options.backgroundOpacity = 1.0 - parseParams() { + } - // Get image dimensions + if( options.backgroundColor === undefined && options.backgroundTexture ) { - if ( !this.width ) console.warn( 'inlineBlock has no width. Set to 0.3 by default' ); - if ( !this.height ) console.warn( 'inlineBlock has no height. Set to 0.3 by default' ); + options.backgroundColor = new external_THREE_namespaceObject.Color(0xffffff); + } // Add an object that can be seen and CharacterInline - this.inlines = [ { - lineBreak : 'possible', - kerning : 0, - offsetX : 0, - offsetY : 0, - width: this.width || 0.3, - height: this.height || 0.3, - anchor: 0, // @TODO: Could be useful - xadvance: this.width || 0.3, - xoffset: 0, - yoffset: 0, - lineHeight : this.height || 0.3, - lineBase: this.height || 0.3 - }]; + this.inline = new InlineBlockInline(this); + this.inlines = [ this.inline ]; + + this.set( options ); + + this._transferToMaterial(); } - // + /////////// + // UPDATES + /////////// + + parseParams(){ + } /** * Create text content @@ -5946,56 +7968,131 @@ class InlineBlock extends mix.withBase( external_THREE_namespaceObject.Object3D */ updateLayout() { - const WIDTH = this.getWidth(); - const HEIGHT = this.getHeight(); + const PADDING = this._padding.w + this._padding.y; + const WIDTH = this.inlineWidth; + const HEIGHT = this.inlineHeight; - if ( this.inlines ) { - const options = this.inlines[ 0 ]; + // basic translation to put the plane's left bottom corner at the center of its space + // this.position.set( WIDTH / 2 , HEIGHT / 2, 0 ); + this.position.set( (WIDTH + PADDING)/2, HEIGHT / 2, 0 ); - // basic translation to put the plane's left bottom corner at the center of its space - this.position.set( options.width / 2, options.height / 2, 0 ); + // translation required by inlineManager to position this component inline + this.position.x += this.inline.offsetX; + this.position.y += this.inline.offsetY; - // translation required by inlineManager to position this component inline - this.position.x += options.offsetX; - this.position.y += options.offsetY; + this.position.y += this.inline.anchor; - this.position.y += options.anchor; + this.size.set( WIDTH, HEIGHT ); + this._main.scale.set( WIDTH, HEIGHT, 1 ); - } + this._main.renderOrder = this.getParentsNumber(); - this.size.set( WIDTH, HEIGHT ); - this.frame.scale.set( WIDTH, HEIGHT, 1 ); + this.position.z = this.getOffset(); - if ( this.frame ) this.updateBackgroundMaterial(); + } - this.frame.renderOrder = this.getParentsNumber(); + // - // Position inner elements according to dimensions and layout parameters. - // Delegate to BoxComponent. + updateInner() { - if ( this.childrenInlines.length ) { + } - this.computeInlinesPosition(); + /********************************************************************************************************************* + * POVIDES INLINE SIZING + ********************************************************************************************************************/ - } + /** + * + * @return {number} + */ + get inlineXAdvance(){ - this.computeChildrenPosition(); + const pad = this._padding.w + this._padding.y; + return (this.width || 0.3) + pad; - this.position.z = this.getOffset(); + } + + /** + * + * @return {number} + */ + get inlineWidth() { + + return this.width || 0.3; } - // + /** + * + * @return {number} + */ + get inlineHeight() { - updateInner() { + return this.height || 0.3; - this.position.z = this.getOffset(); + } + +} + +/** + * InlineBlock has its own Inline implementation + */ +class InlineBlockInline extends Inline { + + /** + * + * @param {InlineBlock} parent + */ + constructor( parent ) { + + super(); - if ( this.frame ) this.updateBackgroundMaterial(); + /** + * @TODO: This currently make a circular reference that should ideally be removed + * @type {InlineBlock} + * @private + */ + this._parent = parent; } + /** + * Rely on the parent for size computation + * @override + * @returns {number} + */ + get xadvance() { return this._parent.inlineXAdvance; } + + /** + * Rely on the parent for size computation + * @override + * @returns {number} + */ + get width() { return this._parent.inlineWidth; } + + /** + * Rely on the parent for size computation + * @override + * @returns {number} + */ + get height() { return this._parent.inlineHeight; } + + + /** + * Rely on the parent for size computation + * @override + * @returns {number} + */ + get lineHeight() { return this._parent.inlineHeight; } + + /** + * Rely on the parent for size computation + * @override + * @returns {number} + */ + get lineBase() { return this._parent.inlineHeight; } + } ;// CONCATENATED MODULE: ./src/utils/Keymaps.js @@ -6735,9 +8832,6 @@ and if not passed tries to detect the language. If not found, it uses the basic - - - // const textureLoader = new external_THREE_namespaceObject.TextureLoader(); @@ -6747,7 +8841,7 @@ const textureLoader = new external_THREE_namespaceObject.TextureLoader(); /** * Job: high-level component that returns a keyboard */ -class Keyboard extends mix.withBase( external_THREE_namespaceObject.Object3D )( BoxComponent, MeshUIComponent ) { +class Keyboard extends BoxComponent { constructor( options ) { @@ -7084,22 +9178,32 @@ class Keyboard extends mix.withBase( external_THREE_namespaceObject.Object3D )( + + + + const update = () => UpdateManager.update(); + const ThreeMeshUI = { Block: Block, Text: Text, InlineBlock: InlineBlock, Keyboard: Keyboard, FontLibrary: font_FontLibrary, + FontStyle: FontStyle_namespaceObject, + FontWeight: FontWeight_namespaceObject, update, TextAlign: TextAlign_namespaceObject, Whitespace: Whitespace_namespaceObject, JustifyContent: JustifyContent_namespaceObject, AlignItems: AlignItems_namespaceObject, - ContentDirection: ContentDirection_namespaceObject + ContentDirection: ContentDirection_namespaceObject, + MSDFFontMaterialUtils: MSDFFontMaterialUtils, + ShaderChunkUI: ShaderChunkUI, }; + if ( typeof __webpack_require__.g !== 'undefined' ) __webpack_require__.g.ThreeMeshUI = ThreeMeshUI; @@ -7114,7 +9218,14 @@ if ( typeof __webpack_require__.g !== 'undefined' ) __webpack_require__.g.ThreeM + + + + /* harmony default export */ const three_mesh_ui = ((/* unused pure expression or super */ null && (ThreeMeshUI))); + + + /******/ })() ; \ No newline at end of file diff --git a/config/karma.conf.js b/config/karma.conf.js index 08655124..5c445616 100644 --- a/config/karma.conf.js +++ b/config/karma.conf.js @@ -16,19 +16,19 @@ module.exports = function ( config ) { { pattern: './tests/utils/**/*.js', watched: true, included: false }, ], basePath: "../", - reporters: [ 'mocha' /*, 'coverage-istanbul'*/ ], + reporters: [ 'mocha', 'coverage-istanbul' ], preprocessors: { "./build/**/*.js": ["karma-coverage-istanbul-instrumenter"], "./src/**/*.js": ["karma-coverage-istanbul-instrumenter"], }, - // coverageIstanbulInstrumenter: { - // esModules: true - // }, - // - // coverageIstanbulReporter: { - // reports: [ "text" ], - // }, + coverageIstanbulInstrumenter: { + esModules: true + }, + + coverageIstanbulReporter: { + reports: [ "text" ], + }, port: 9876, // karma web server port colors: true, // logLevel: config.LOG, diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..5819cb1f --- /dev/null +++ b/tests/README.md @@ -0,0 +1,17 @@ +Running tests +```shell +# Running all tests +npm run test + +# Running a specific file, or glob pattern +npm run test -- --specs='./tests/specs/utils/block-layout/cases/row-center.js' + +# Running all tests each time a build is built ( watching /build/** ) +npm run tdd:test + +# Running all tests each time a build is built ( watching /build/** ) +npm run tdd:test -- --specs='./tests/specs/utils/block-layout/cases/row-center.js' + + +``` + diff --git a/tests/specs/core/box-component.js b/tests/specs/core/box-component.js index 4372d99c..ca10bb1e 100644 --- a/tests/specs/core/box-component.js +++ b/tests/specs/core/box-component.js @@ -37,6 +37,10 @@ describe("BoxComponent", function () { }); + /** + * Job: + * Being sure properties that update box sizing work properly + */ describe("Computed widths, heights & centers", function() { beforeEach( function() { diff --git a/tests/specs/core/mesh-ui-component.js b/tests/specs/core/mesh-ui-component.js new file mode 100644 index 00000000..06d824bf --- /dev/null +++ b/tests/specs/core/mesh-ui-component.js @@ -0,0 +1,306 @@ +import { buildThreeSetup } from '../../utils/TestThree.js'; +import { preloadFonts } from '../../utils/TestFonts.js'; +import { fiveBlockContainer } from '../../utils/TestStructure.js'; +import { imprecise } from '../../utils/TestNumber.js'; + +describe("MeshUIComponent", function () { + + + let scene, camera, renderer, render; + let fontFamily; + + before( function ( done ) { + + ( { scene, camera, renderer, render } = buildThreeSetup() ); + fontFamily = preloadFonts( done ); + + } ); + + let container, child1, child2, child3, child4, child5; + + + + before( function () { + + //build a container with 5 children blocks + ( { container, child1, child2, child3, child4, child5 } = fiveBlockContainer( scene ) ); + + } ); + + + /** + * Job: + * Being sure properties are correctly updated + */ + describe( "Margins", function () { + + it('Margins should work in duplicated 4 dimensions', ()=>{ + + container.set({margin:0.1}); + expect(container._margin.x).equals(container._margin.y); + expect(container._margin.y).equals(container._margin.z); + expect(container._margin.z).equals(container._margin.w); + expect(container._margin.w).equals(0.1); + + }); + + it('Margins should work in duplicated 2 dimensions', ()=>{ + + container.set({margin:'0.1 0.2'}); + expect(container._margin.x).equals(container._margin.z); + expect(container._margin.y).equals(container._margin.w); + expect(container._margin.y).equals(0.2); + expect(container._margin.x).equals(0.1); + + }); + + it('Margins should work in duplicated 2 dimensions array', ()=>{ + + container.set({margin:[0.2,0.1]}); + expect(container._margin.x).equals(container._margin.z); + expect(container._margin.y).equals(container._margin.w); + expect(container._margin.y).equals(0.1); + expect(container._margin.x).equals(0.2); + + }); + + it('Margins should work in 4 dimensions', ()=>{ + + container.set({margin:'0.1 0.2 0.3 0.4'}); + expect(container._margin.x).equals(0.1); + expect(container._margin.y).equals(0.2); + expect(container._margin.z).equals(0.3); + expect(container._margin.w).equals(0.4); + + }); + + it('Margins should work with top', ()=>{ + + container.set({marginTop:0}); + expect(container._margin.x).equals(0); + // while other remains + expect(container._margin.y).equals(0.2); + expect(container._margin.z).equals(0.3); + expect(container._margin.w).equals(0.4); + + }); + + it('Margins should work with right', ()=>{ + + container.set({marginRight:0}); + expect(container._margin.x).equals(0); + expect(container._margin.y).equals(0); + // while other remains + expect(container._margin.z).equals(0.3); + expect(container._margin.w).equals(0.4); + + }); + + it('Margins should work with bottom', ()=>{ + + container.set({marginBottom:0}); + expect(container._margin.x).equals(0); + expect(container._margin.y).equals(0); + expect(container._margin.z).equals(0); + // while other remains + expect(container._margin.w).equals(0.4); + + }); + + it('Margins should work with left', ()=>{ + + container.set({marginLeft:0}); + expect(container._margin.x).equals(0); + expect(container._margin.y).equals(0); + expect(container._margin.z).equals(0); + expect(container._margin.w).equals(0); + + }); + + + } ); + + + //padding + + describe( "Paddings", function () { + + it('Paddings should work in duplicated 4 dimensions', ()=>{ + + container.set({padding:0.1}); + expect(container._padding.x).equals(container._padding.y); + expect(container._padding.y).equals(container._padding.z); + expect(container._padding.z).equals(container._padding.w); + expect(container._padding.w).equals(0.1); + + }); + + it('Paddings should work in duplicated 2 dimensions', ()=>{ + + container.set({padding:'0.1 0.2'}); + expect(container._padding.x).equals(container._padding.z); + expect(container._padding.y).equals(container._padding.w); + expect(container._padding.y).equals(0.2); + expect(container._padding.x).equals(0.1); + + }); + + it('Paddings should work in duplicated 2 dimensions array', ()=>{ + + container.set({padding:[0.2,0.1]}); + expect(container._padding.x).equals(container._padding.z); + expect(container._padding.y).equals(container._padding.w); + expect(container._padding.y).equals(0.1); + expect(container._padding.x).equals(0.2); + + }); + + it('Paddings should work in 4 dimensions', ()=>{ + + container.set({padding:'0.1 0.2 0.3 0.4'}); + expect(container._padding.x).equals(0.1); + expect(container._padding.y).equals(0.2); + expect(container._padding.z).equals(0.3); + expect(container._padding.w).equals(0.4); + + }); + + it('Paddings should work with top', ()=>{ + + container.set({paddingTop:0}); + expect(container._padding.x).equals(0); + // while other remains + expect(container._padding.y).equals(0.2); + expect(container._padding.z).equals(0.3); + expect(container._padding.w).equals(0.4); + + }); + + it('Paddings should work with right', ()=>{ + + container.set({paddingRight:0}); + expect(container._padding.x).equals(0); + expect(container._padding.y).equals(0); + // while other remains + expect(container._padding.z).equals(0.3); + expect(container._padding.w).equals(0.4); + + }); + + it('Paddings should work with bottom', ()=>{ + + container.set({paddingBottom:0}); + expect(container._padding.x).equals(0); + expect(container._padding.y).equals(0); + expect(container._padding.z).equals(0); + // while other remains + expect(container._padding.w).equals(0.4); + + }); + + it('Paddings should work with left', ()=>{ + + container.set({paddingLeft:0}); + expect(container._padding.x).equals(0); + expect(container._padding.y).equals(0); + expect(container._padding.z).equals(0); + expect(container._padding.w).equals(0); + + }); + + + } ); + + + // Borders + + describe( "Border Widths", function () { + + it('Border Widths should work in duplicated 4 dimensions', ()=>{ + + container.set({borderWidth:0.1}); + expect(container._borderWidth.x).equals(container._borderWidth.y); + expect(container._borderWidth.y).equals(container._borderWidth.z); + expect(container._borderWidth.z).equals(container._borderWidth.w); + expect(container._borderWidth.w).equals(0.1); + + }); + + it('Border Widths should work in duplicated 2 dimensions', ()=>{ + + container.set({borderWidth:'0.1 0.2'}); + expect(container._borderWidth.x).equals(container._borderWidth.z); + expect(container._borderWidth.y).equals(container._borderWidth.w); + expect(container._borderWidth.y).equals(0.2); + expect(container._borderWidth.x).equals(0.1); + + }); + + it('Border Widths should work in duplicated 2 dimensions array', ()=>{ + + container.set({borderWidth:[0.2,0.1]}); + expect(container._borderWidth.x).equals(container._borderWidth.z); + expect(container._borderWidth.y).equals(container._borderWidth.w); + expect(container._borderWidth.y).equals(0.1); + expect(container._borderWidth.x).equals(0.2); + + }); + + it('Border Widths should work in 4 dimensions', ()=>{ + + container.set({borderWidth:'0.1 0.2 0.3 0.4'}); + expect(container._borderWidth.x).equals(0.1); + expect(container._borderWidth.y).equals(0.2); + expect(container._borderWidth.z).equals(0.3); + expect(container._borderWidth.w).equals(0.4); + + }); + + it('Border Widths should work with top', ()=>{ + + container.set({borderTopWidth:0}); + expect(container._borderWidth.x).equals(0); + // while other remains + expect(container._borderWidth.y).equals(0.2); + expect(container._borderWidth.z).equals(0.3); + expect(container._borderWidth.w).equals(0.4); + + }); + + it('Border Widths should work with right', ()=>{ + + container.set({borderRightWidth:0}); + expect(container._borderWidth.x).equals(0); + expect(container._borderWidth.y).equals(0); + // while other remains + expect(container._borderWidth.z).equals(0.3); + expect(container._borderWidth.w).equals(0.4); + + }); + + it('Border Widths should work with bottom', ()=>{ + + container.set({borderBottomWidth:0}); + expect(container._borderWidth.x).equals(0); + expect(container._borderWidth.y).equals(0); + expect(container._borderWidth.z).equals(0); + // while other remains + expect(container._borderWidth.w).equals(0.4); + + }); + + it('Border Widths should work with left', ()=>{ + + container.set({borderLeftWidth:0}); + expect(container._borderWidth.x).equals(0); + expect(container._borderWidth.y).equals(0); + expect(container._borderWidth.z).equals(0); + expect(container._borderWidth.w).equals(0); + + }); + + + } ); + + +});