673 lines
14 KiB
C#
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 )
|
|
// }
|
|
|
|
}
|
|
} |