using Godot; using Godot.Collections; using System; using System.Collections.Generic; using System.Text; namespace Rokojori { [Tool] [GlobalClass,Icon("res://addons/rokojori_action_library/Icons/UI.svg")] public partial class UI : Control, IDisposable { public static readonly CachedResource whiteTexture = new CachedResource( "res://addons/rokojori_action_library/Assets/Textures/white.svg" ); public static readonly CachedResource blackTexture = new CachedResource( "res://addons/rokojori_action_library/Assets/Textures/black.svg" ); [Export] public UISettings settings; [Export] public float fontZoom = 1; [Export] public UINumberVariable[] variables = []; [Export] public int numUpdates = 0; [Export] public Control focusedControl; [ExportToolButton("Update Variables")] public Callable updateVariablesButton => Callable.From( ()=> { CreateVariableValues(); } ); [ExportToolButton("Clear UI Number Cache")] public Callable clearUINumberCacheButton => Callable.From( ()=> { CreateVariableValues(); } ); System.Collections.Generic.Dictionary _variableValues = null; void CreateVariableValues() { _variableValues = new System.Collections.Generic.Dictionary(); _cachedVariables = new List(); variables.ForEach( ( v )=> { _variableValues[ v.variableName ] = v.GetFormula(); _cachedVariables.Add( (UINumberVariable)v.Duplicate() ); } ); variables.ForEach( ( v )=> { UINumber.ClearCacheFor( v ); } ); } public string InsertVariables( string value, ref bool hasVariables ) { if ( variables == null || variables.Length == 0 ) { hasVariables = false; return value; } if ( _variableValues == null ) { CreateVariableValues(); } var tokens = UIExpressionLexer.Lex( value ); var output = new StringBuilder(); var tokensContainVariable = false; tokens.ForEach( ( tk )=> { if ( tk.isErrorOrDone ) { return; } if ( tk.Is( LexerMatcherLibrary.CwordMatcher ) && _variableValues.ContainsKey( tk.match ) ) { output.Append( _variableValues[ tk.match ] ); tokensContainVariable = true; return; } output.Append( tk.match ); } ); hasVariables = tokensContainVariable; return output.ToString(); } public enum UpdateMode { Always, Optimized, Only_Manual_Updates } [Export] public UpdateMode updateMode = UpdateMode.Always; [Export] public bool useParentSize = false; [Export] public Vector2 uiSize = Vector2.Zero; [ExportGroup("Functions")] [ExportToolButton( "Mouse:Stop => Pass")] public Callable StopToPassButton => Callable.From( ()=>{ MakeStopToPass(); } ); [Export] public Node[] stopToPassRoots = []; [ExportGroup("Read Only")] [Export] public float X_computedFontSizePixels = 1; List _customDisposers = []; public void AddForDisposing( ICustomDisposer customDisposer ) { _customDisposers.Add( customDisposer ); } protected override void Dispose( bool disposing ) { _customDisposers.ForEach( cd => cd.Dispose() ); _customDisposers = []; } [Export] public string[] customDisposerIDs = []; public UINumber GetUINumber( UIStyleNumberProperty property ) { if ( UIStyleNumberProperty.FontSize == property ) { var uiNumber = new UINumber(); uiNumber.value = X_computedFontSizePixels; uiNumber.unit = "px"; // this.LogInfo( "Font Size:", uiNumber.value, uiNumber.unit ); return uiNumber; } return null; } public void MakeStopToPass() { var roots = stopToPassRoots; if ( roots == null || roots.Length == 0 ) { roots = [ this ]; } foreach ( var n in roots ) { if ( n is Control c ) { if ( c.MouseFilter == MouseFilterEnum.Stop ) { c.MouseFilter = MouseFilterEnum.Pass; } } n.ForEach( ( c )=> { if ( c.MouseFilter == MouseFilterEnum.Stop ) { c.MouseFilter = MouseFilterEnum.Pass; } } ); } } Vector2 cachedSize; public static UI Get( Control c ) { return UIHolder.GetUI( c ); } public static UIStylePropertyContainer GetStylePropertyContainerParent( Control c ) { var it = c as Node; var walker = NodesWalker.Get(); it = walker.Parent( it ); if ( it is UI ) { return null; } while ( it != null ) { if ( it is UIStylePropertyContainer ) { return it as UIStylePropertyContainer; } it = walker.Parent( it ); if ( it is UI ) { it = null; } } return null; } public static TimeLine GetTimeLine( Control c, TimeLine timeLine ) { if ( timeLine != null ) { return timeLine; } var ui = Get( c ); if ( ui == null || ui.settings == null ) { return null; } return ui.settings.defaultTimeline; } Vector2 windowSize = new Vector2(); bool _resizeFlag = false; bool _fontSizeFlag = false; public bool sizeChanged => _resizeFlag; public bool fontSizeChanged => _fontSizeFlag; // [Export] // public UISlider focusedSlider = null; // [Export] // public UIListSelection focusedListSelection = null; public override void _Process( double delta ) { if ( settings == null ) { uiSize = Vector2.Zero; return; } _resizeFlag = false; _fontSizeFlag = false; var newWindowSize = GetWindowSize(); if ( windowSize != newWindowSize ) { _resizeFlag = true; } if ( useParentSize ) { var parentSize = cachedSize; if ( GetParent() as Control != null ) { parentSize = GetParent().Size; } else { parentSize = newWindowSize; } if ( parentSize != cachedSize ) { Size = parentSize; cachedSize = parentSize; _resizeFlag = true; } } focusedControl = GetViewport().GuiGetFocusOwner() as Control; if ( focusedControl != null && focusedControl is UIHolderControl uih && focusedControl is UIFocusProcessor fp ) { if ( uih.GetUI() == this ) { fp.OnFocusProcess( delta ); } } onProcess.DispatchEvent( (float)delta ); UpdateFontSize(); UpdateVariables(); UpdateUIElements(); uiSize = Size; // customDisposerIDs = _customDisposers.Map( c => c.GetUID() + ":" + c.GetInfo() ).ToArray(); } public void UpdateExternal( float delta) { onProcess.DispatchEvent( delta ); UpdateFontSize(); } List _cachedVariables = []; public void UpdateVariables() { if ( _cachedVariables.Count != variables.Length ) { CreateVariableValues(); return; } var recreate = false; for ( int i = 0; ! recreate && i < _cachedVariables.Count; i++ ) { var v = variables[ i ]; if ( _cachedVariables[ i ] == null && v == null ) { continue; } if ( _cachedVariables[ i ] == null || v == null ) { recreate = true; continue; } if ( _cachedVariables[ i ].IsEqualTo( v ) ) { continue; } _cachedVariables[ i ] = (UINumberVariable) v.Duplicate(); UINumber.ClearCacheFor( v ); _variableValues[ v.variableName ] = v.GetFormula(); } if ( recreate ) { CreateVariableValues(); } } public readonly EventSlot onInputEvent = new EventSlot(); public readonly EventSlot onProcess = new EventSlot(); public override void _Input( InputEvent ie ) { onInputEvent.DispatchEvent( ie ); } public void BindOwnChildren() { BindChildrenOf( this ); } public void BindChildrenOf( Node node ) { node.ForEach( img => img.SetUI( this ) ); node.ForEach( info => info.SetUI( this ) ); node.ForEach( text => text.SetUI( this ) ); node.ForEach( region => region.SetUI( this ) ); } public override void _Ready() { var sm = Unique.Get(); if ( sm != null ) { sm._onActiveDeviceChange.AddAction( a => UpdateUIInputs() ); } UpdateUIInputs(); } void UpdateUIInputs() { Nodes.ForEach( this, uii => uii.updateInfo = true ); } void UpdateFontSize() { if ( settings == null ) { return; } var oldFontSize = X_computedFontSizePixels; X_computedFontSizePixels = UINumber.Compute( this, settings.fontSize ) * fontZoom; if ( oldFontSize != X_computedFontSizePixels ) { _fontSizeFlag = true; } if ( Theme != null ) { Theme.DefaultFontSize = Mathf.RoundToInt( X_computedFontSizePixels ); } } HashSet _dirty = new HashSet(); HashSet _updated = new HashSet(); public void SetDirty( UIStylePropertyContainerNode control ) { _dirty.Add( control ); } public void SetUpdated( UIStylePropertyContainerNode control ) { _updated.Add( control ); } void UpdateUIElements() { if ( UpdateMode.Always == updateMode ) { UpdateAllUIRegions(); } else if ( UpdateMode.Optimized == updateMode ) { if ( _fontSizeFlag || _resizeFlag ) { _dirty.Clear(); UpdateAllUIRegions(); } else { UpdateUIElementsOptimized(); } } } float time = 0; void UpdateUIElementsOptimized() { var timeNow = TimeLine.osTime; var elapsed = timeNow - time; time = timeNow; // this.LogInfo( Mathf.RoundToInt( elapsed * 1000f ) ); var parent = GetParent(); var sorted = new List( [ .. _dirty ]); if ( sorted.Count > 0 ) { // this.LogInfo( "Updating:", sorted.Count ); } _updated.Clear(); sorted.Sort( ( a, b ) => { return Mathf.RoundToInt( Mathf.Sign( a.GetUIAncestorDepth() - b.GetUIAncestorDepth() ) ); } ); var updated = 0; sorted.ForEach( ( s )=> { if ( _updated.Contains( s ) ) { return; } // ( s as Control).LogInfo( "Updating:", s ); UpdateElement( s ); updated ++; } ); numUpdates = updated; _updated.Clear(); _dirty.RemoveWhere( d => ! d.IsDirty() ); foreach ( var d in _dirty ) { d.ResetDirtyFlags(); } } void UpdateElement( UIStylePropertyContainerNode control ) { if ( control is UIRegion region ) { region.Layout(); } else { UILayouting.UpdateChild( control as Control ); if ( UIStyle.Position( control ) == UIPosition.From_Layout ) { UILayouting.SetPositionInParentAnchor( control ); } } } public void UpdateAllUIRegions() { Nodes.ForEachDirectChild( this, r => r.Layout() ); } public static float GetWindowWidth( Control control ) { if ( Engine.IsEditorHint() ) { return ProjectSettings.GetSetting( "display/window/size/viewport_width" ).AsInt32(); } else { return control.GetWindow().Size.X; } } public static float GetWindowHeight( Control control ) { if ( Engine.IsEditorHint() ) { return ProjectSettings.GetSetting( "display/window/size/viewport_height" ).AsInt32(); } else { return control.GetWindow().Size.Y; } } public Vector2 GetWindowSize() { if ( Engine.IsEditorHint() ) { var width = ProjectSettings.GetSetting( "display/window/size/viewport_width" ).AsInt32(); var height = ProjectSettings.GetSetting( "display/window/size/viewport_height" ).AsInt32(); return new Vector2( width, height ); } else { return GetWindow().Size; } } System.Collections.Generic.Dictionary _audioPlayers = new System.Collections.Generic.Dictionary(); public AudioStreamPlayer _GetAudioPlayer( UISoundData data ) { AudioStreamPlayer freePlayer = null; foreach ( var p in _audioPlayers ) { var player = p.Key; if ( player.Playing ) { continue; } if ( player.Stream != data.audio ) { freePlayer = player; continue; } return player; } if ( freePlayer != null ) { return freePlayer; } var newPlayer = this.CreateChild(); _audioPlayers[ newPlayer ] = 0; return newPlayer; } public void PlaySound( UISoundData soundData ) { var player = _GetAudioPlayer( soundData ); player.Stream = soundData.audio; player.VolumeLinear = soundData.volume; player.PitchScale = MathAudio.SemitonesToFrequencyScaler( soundData.pitchSemitonesOffset ); player.Playing = true; } // public void Focus( Control control ) // { // var focusParent = // while ( control ) // } } }