rokojori_action_library/Runtime/UI/UI.cs

673 lines
14 KiB
C#

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<Texture2D> whiteTexture = new CachedResource<Texture2D>(
"res://addons/rokojori_action_library/Assets/Textures/white.svg"
);
public static readonly CachedResource<Texture2D> blackTexture = new CachedResource<Texture2D>(
"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<string,string> _variableValues = null;
void CreateVariableValues()
{
_variableValues = new System.Collections.Generic.Dictionary<string, string>();
_cachedVariables = new List<UINumberVariable>();
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<ICustomDisposer> _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<Control>(
( 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<Control>().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<UINumberVariable> _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<InputEvent> onInputEvent = new EventSlot<InputEvent>();
public readonly EventSlot<float> onProcess = new EventSlot<float>();
public override void _Input( InputEvent ie )
{
onInputEvent.DispatchEvent( ie );
}
public void BindOwnChildren()
{
BindChildrenOf( this );
}
public void BindChildrenOf( Node node )
{
node.ForEach<UIImage>( img => img.SetUI( this ) );
node.ForEach<UIInputInfo>( info => info.SetUI( this ) );
node.ForEach<UIText>( text => text.SetUI( this ) );
node.ForEach<UIRegion>( region => region.SetUI( this ) );
}
public override void _Ready()
{
var sm = Unique<SensorManager>.Get();
if ( sm != null )
{
sm._onActiveDeviceChange.AddAction( a => UpdateUIInputs() );
}
UpdateUIInputs();
}
void UpdateUIInputs()
{
Nodes.ForEach<UIInputInfo>( 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<UIStylePropertyContainerNode> _dirty = new HashSet<UIStylePropertyContainerNode>();
HashSet<UIStylePropertyContainerNode> _updated = new HashSet<UIStylePropertyContainerNode>();
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<UIStylePropertyContainerNode>( [ .. _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<UIRegion>( 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<AudioStreamPlayer,float> _audioPlayers = new System.Collections.Generic.Dictionary<AudioStreamPlayer,float>();
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<AudioStreamPlayer>();
_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 )
// }
}
}