import * as PIXI from 'pixi.js' ; import { passtroughAllEvents } from '../../actions' ; import { colors , styles } from '../style' ; import F from './functions' ; /** Class representing single character in textbox */ class TextChar extends PIXI . Text { /** Text metrics for character */ private readonly m_Metrics : PIXI . TextMetrics ; public constructor ( char , left = 0 ) { super ( char , styles . controls . textbox ) ; this . position . set ( left , 0 ) ; this . m_Metrics = PIXI . TextMetrics . measureText ( this . text , styles . controls . textbox ) ; } /** Width in pixel of char */ public get width ( ) : number { return this . m_Metrics . width ; } /** Height in pixel of char */ public get height ( ) : number { return this . m_Metrics . height ; } } /** Class representing all text in textbox */ class TextContainer extends PIXI . Container { param text public constructor ( text ) { super ( ) ; this . text = text ; this . interactiveChildren = false ; } /** Text value */ public get text ( ) : string { return this . children . reduce ( ( text , current ) => text + current . text , '' ) ; } public set text ( text ) { this . removeChildren ( ) ; for ( let i = 0 ; i < text . length ; i ++ ) { this . addChild ( new TextChar ( text . charAt ( i ) , this . width ) ) ; } } /** Get char from a specific position (position is base 1 indexed) */ public getChar ( position ) { if ( position < 1 || position > this . children . length ) { throw new RangeError ( 'Argument position out of range' ) ; } return super . getChildAt ( position - 1 ) as TextChar ; } /** Add char at specific position (position is base 1 indexed) */ public insertChar ( char , position ? ) { if ( position === undefined ) { super . addChild ( char ) ; } else { if ( position < 0 || position > this . children . length ) { throw new RangeError ( 'Argument position out of range' ) ; } else if ( position === 0 ) { super . addChildAt ( char , 0 ) ; } else { super . addChildAt ( char , position ) ; } } this . alignTextChars ( ) ; } /** Remove char from specific position (position is base 1 indexed) */ public removeChar ( position ) { if ( position < 1 || position > this . children . length ) { throw new RangeError ( 'Argument position out of range' ) ; } super . removeChildAt ( position - 1 ) ; this . alignTextChars ( ) ; } /** Realign text chars if a character was inserted or removed */ private alignTextChars ( ) { let x = 0 ; for ( const textChar of this . children ) { textChar . x = x ; x += ( textChar as TextChar ) . width ; } } } /** Base Textbox Control */ export class Textbox extends PIXI . Container { /** Textbox regular background graphic */ private readonly m_Background : PIXI . Graphics ; /** Textbox active background graphic */ private readonly m_Active : PIXI . Graphics ; /** Container to hold text chars */ private readonly m_Text : TextContainer ; /** Maximum Text Length */ private readonly m_Length : number ; /** Text Input Filter */ private readonly m_Filter : string ; /** Caret Graphic */ private readonly m_CaretGraphic : PIXI . Graphics ; /** Caret Position */ private p_CaretPosition : number ; /** Indiciate whether pointer (mouse) is within the boundaries of the textbox */ private m_MouseInside : boolean ; param width param size param text param length param filter public constructor ( width , text = '' , length = 0 , filter = '' ) { super ( ) ; this . m_Length = length === 0 ? text . length : length ; this . m_Filter = filter ; const height = PIXI . TextMetrics . measureText ( text , styles . controls . textbox ) . height ; this . m_Background = F . DrawRectangle ( width , height + 6 , colors . controls . textbox . background . color , colors . controls . textbox . background . alpha , 1 , true ) ; this . addChild ( this . m_Background ) ; this . m_Active = F . DrawRectangle ( width , height + 6 , colors . controls . textbox . active . color , colors . controls . textbox . active . alpha , 1 , true ) ; this . m_Active . visible = false ; this . addChild ( this . m_Active ) ; this . m_Text = new TextContainer ( text ) ; this . m_Text . position . set ( 3 , 3 ) ; this . addChild ( this . m_Text ) ; this . m_CaretGraphic = new PIXI . Graphics ( ) ; this . m_CaretGraphic . lineStyle ( { width : 1 , color : colors . controls . textbox . foreground . color } ) . moveTo ( 0 , 0 ) . lineTo ( 0 , height ) ; this . m_CaretGraphic . y = 3 ; this . m_CaretGraphic . visible = false ; this . addChild ( this . m_CaretGraphic ) ; this . caretPosition = text . length ; this . interactive = true ; this . on ( 'pointerover' , ( ) => { this . m_MouseInside = true ; } ) ; this . on ( 'pointerout' , ( ) => { this . m_MouseInside = false ; } ) ; this . on ( 'pointerup' , this . onPointerUp ) ; } /** Textbox text */ public get text ( ) : string { return this . m_Text . text ; } public set text ( text ) { if ( this . m_Text . text !== text ) { this . m_Text . text = text ; this . caretPosition = text . length ; this . emit ( 'changed' ) ; } } /** Set caret to new position */ private get caretPosition ( ) : number { return this . p_CaretPosition ; } private set caretPosition ( position ) { if ( position < 0 || position > this . m_Text . children . length ) return ; this . p_CaretPosition = position ; if ( this . p_CaretPosition === 0 ) { this . m_CaretGraphic . x = 3 ; } else { const textChar = this . m_Text . getChildAt ( position - 1 ) as TextChar ; this . m_CaretGraphic . x = textChar . x + textChar . width + 3 ; } } param char private instertCharacter ( char ) { if ( char === undefined || char . length !== 1 ) return ; if ( this . m_Text . children . length >= this . m_Length ) return ; if ( this . m_Filter !== '' && this . m_Filter . indexOf ( char ) < 0 ) return ; this . m_Text . insertChar ( new TextChar ( char ) , this . caretPosition ) ; this . caretPosition += 1 ; this . emit ( 'changed' ) ; } param direction private removeCharacter ( direction ) { if ( direction === - 1 ) { if ( this . caretPosition < 1 ) return ; this . m_Text . removeChar ( this . caretPosition ) ; this . caretPosition -= 1 ; } else if ( direction === 1 ) { if ( this . caretPosition >= this . m_Text . children . length ) return ; this . m_Text . removeChar ( this . caretPosition + 1 ) ; } this . emit ( 'changed' ) ; } /** Pointer up event callback */ private readonly onPointerUp = ( ) : void => { if ( ! this . m_Active . visible ) { passtroughAllEvents ( this . keyPressedCallback . bind ( this ) ) ; this . m_CaretGraphic . visible = true ; this . m_Active . visible = true ; } } ; /** KeyboardJS key pressed event callback */ private readonly keyPressedCallback = ( e ) : boolean => { const disable = ( ) : void => { this . m_CaretGraphic . visible = false ; this . m_Active . visible = false ; } ; if ( e . pressedKeys . includes ( 'lclick' ) && ! this . m_MouseInside ) { disable ( ) ; return true ; } switch ( e . key ) { case 'Enter' : case 'Escape' : case 'Tab' : { disable ( ) ; return true ; } case 'ArrowRight' : { this . caretPosition += 1 ; break ; } case 'ArrowLeft' : { this . caretPosition -= 1 ; break ; } case 'Backspace' : { this . removeCharacter ( - 1 ) ; break ; } case 'Delete' : { this . removeCharacter ( 1 ) ; break ; } case 'Space' : default : { this . instertCharacter ( e . key ) ; } } return false ; } ; }