rj-action-library/Runtime/UI/Styling/UINumber.cs

646 lines
17 KiB
C#
Raw Normal View History

2024-08-09 13:52:49 +00:00
using Godot;
using Rokojori;
2025-06-23 11:16:01 +00:00
using System.Collections.Generic;
using System.Linq;
2024-08-09 13:52:49 +00:00
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class UINumber : Resource
{
[Export]
public float value = 0;
[Export]
public string unit = "";
2024-09-14 06:41:52 +00:00
[ExportGroup("Animation")]
[Export]
public bool isAnimated;
[Export]
public Curve animationCurve;
2024-08-09 13:52:49 +00:00
2024-09-14 06:41:52 +00:00
[Export]
public float animationDuration;
[Export]
public float animationOffset;
[Export]
2025-01-08 18:46:17 +00:00
public TimeLine timeLine;
2024-08-09 13:52:49 +00:00
public bool IsNone => unit == "none";
public UINumber GetMultiplied( float scale )
{
var uiNumber = new UINumber();
uiNumber.CopyFrom( this );
uiNumber.value = value * scale;
return uiNumber;
}
public static UINumber Create( float value, string unit = "" )
{
var uiNumber = new UINumber();
uiNumber.unit = unit;
uiNumber.value = value;
return uiNumber;
}
public static UINumber EM( float em )
{
return Create( em, "em" );
}
public static UINumber PW( float pw )
{
return Create( pw, "pw" );
}
public static UINumber PH( float ph )
{
return Create( ph, "ph" );
}
2024-09-14 06:41:52 +00:00
public float ComputeRaw( Control control, float alternative )
2024-08-09 13:52:49 +00:00
{
return UINumber.Compute( control, this, alternative );
}
2024-09-14 06:41:52 +00:00
public static bool IsNullOrNone( UIStylePropertyContainer container, UIStyleNumberProperty property )
{
2025-06-19 17:22:25 +00:00
return IsNullOrNone( UIStyle.GetUINumberProperty( container, property, "", container ) );
2024-09-14 06:41:52 +00:00
}
2024-08-09 13:52:49 +00:00
public static bool IsNullOrNone( UINumber number )
{
if ( number == null )
{
return true;
}
return number.IsNone;
}
2024-09-14 06:41:52 +00:00
public static int ComputeInt( Control control, UINumber number, float alternative = 0, float relative = 100 )
{
return Mathf.RoundToInt( Compute( control, number, alternative, relative ) );
}
public static int ComputeInt( Control control, UIStyleNumberProperty property, float alternative = 0, float relative = 100 )
{
2025-06-19 17:22:25 +00:00
return ComputeInt( control, property, "", alternative, relative );
}
public static int ComputeInt( Control control, UIStyleNumberProperty property, string shaderPropertyName = "", float alternative = 0, float relative = 100 )
{
return Mathf.RoundToInt( Compute( control, property, shaderPropertyName, alternative, relative ) );
2024-09-14 06:41:52 +00:00
}
public bool Equals( UINumber other )
{
if ( other == this )
{
return true;
}
if ( other == null )
{
return false;
}
if ( other.value != value )
{
return false;
}
if ( other.unit != unit )
{
return false;
}
if ( other.isAnimated != isAnimated )
{
return false;
}
if ( other.animationCurve != animationCurve )
{
return false;
}
if ( other.animationDuration != animationDuration )
{
return false;
}
if ( other.animationOffset != animationOffset )
{
return false;
}
if ( other.timeLine != timeLine )
{
return false;
}
return true;
}
public void CopyFrom( UINumber other )
2024-09-14 06:41:52 +00:00
{
if ( other == this )
{
return;
}
value = other.value;
unit = other.unit;
isAnimated = other.isAnimated;
animationCurve = other.animationCurve;
animationDuration = other.animationDuration;
animationOffset = other.animationOffset;
timeLine = other.timeLine;
}
public static float Compute( Control control, UIStyleNumberProperty property, float alternative = 0, float relative = 100 )
{
2025-06-19 17:22:25 +00:00
return Compute( control, property, "", alternative, relative );
}
2024-09-14 06:41:52 +00:00
2025-06-19 17:22:25 +00:00
public static float Compute( Control control, UIStyleNumberProperty property, string shaderPropertyName = "", float alternative = 0, float relative = 100 )
{
2025-06-23 11:16:01 +00:00
var container = control as UIStylePropertyContainerNode;
2025-06-19 17:22:25 +00:00
// Get selected ui number
2025-06-23 11:16:01 +00:00
var uiNumber = UIStyle.GetUINumberProperty( container, property, shaderPropertyName, container );
2025-06-19 17:22:25 +00:00
var computedNumber = Compute( control, uiNumber, alternative, relative );
2024-09-14 06:41:52 +00:00
2025-06-23 11:16:01 +00:00
if ( uiNumber != null && uiNumber.isAnimated )
{
container.SetAnimatedFlag();
}
2025-06-19 17:22:25 +00:00
var transition = UIStyle.GetTransition( container, property, shaderPropertyName );
var transitionAll = UIStyle.GetTransitionSettingsAll( container );
2024-09-14 06:41:52 +00:00
2025-06-19 17:22:25 +00:00
var usesTransition = transition != null || transitionAll != null && transitionAll.transitionAllProperties;
2024-09-14 06:41:52 +00:00
if ( ! usesTransition )
{
2025-06-19 17:22:25 +00:00
return computedNumber;
2024-09-14 06:41:52 +00:00
}
var activeNumberTransitions = container.GetActiveUINumberTransitions();
2025-06-19 17:22:25 +00:00
var propertyTransition = activeNumberTransitions.Find( t => t != null && t.propertyType.Matches( property, shaderPropertyName ) );
propertyTransition = EnsureTransitionObject( propertyTransition, control, uiNumber, property, shaderPropertyName );
2024-09-14 06:41:52 +00:00
2025-06-19 17:22:25 +00:00
var endValueChanged = uiNumber != propertyTransition.value;
2024-09-14 06:41:52 +00:00
2025-06-19 17:22:25 +00:00
if ( endValueChanged )
{
var transitionSettings = UIStyle.GetTransitionSettings( container, property, shaderPropertyName );
propertyTransition.StartTransition( control, uiNumber, transitionSettings );
}
2024-09-14 06:41:52 +00:00
2025-06-19 17:22:25 +00:00
if ( ! propertyTransition.transitioning )
2024-09-14 06:41:52 +00:00
{
2025-06-19 17:22:25 +00:00
return computedNumber;
2024-09-14 06:41:52 +00:00
}
2025-06-19 17:22:25 +00:00
var computedTransitionValue = 0f;
for ( int i = 0; i < propertyTransition.lastValues.Count; i++ )
2024-09-14 06:41:52 +00:00
{
2025-06-19 17:22:25 +00:00
var lastValue = Compute( control, propertyTransition.lastValues[ i ], 0f );
computedTransitionValue += lastValue * propertyTransition.lastWeights[ i ];
2024-09-14 06:41:52 +00:00
}
2025-06-19 17:22:25 +00:00
var phase = propertyTransition.GetUnclampedTransitionPhase();
var lerpWeight = propertyTransition.ComputeTransitionWeight( phase );
2024-09-14 06:41:52 +00:00
2025-06-19 17:22:25 +00:00
if ( phase >= 1f )
2024-09-14 06:41:52 +00:00
{
2025-06-19 17:22:25 +00:00
propertyTransition.EndTransition();
2024-09-14 06:41:52 +00:00
}
2025-06-19 17:22:25 +00:00
var result = Mathf.Lerp( computedTransitionValue, computedNumber, lerpWeight );
return result;
// if ( propertyTransition == null )
// {
// propertyTransition = new ActiveStyleTransition<UINumber, UIStyleNumberProperty>();
// propertyTransition.propertyType = property;
// propertyTransition.value = number;
// propertyTransition.transitioning = false;
// activeNumberTransitions.Add( propertyTransition );
// return computedValue;
// }
// else
// {
// if ( propertyTransition.value != number && ! propertyTransition.transitioning && UIStyle.GetTransitionSettings( container, property ) != null )
// {
// var transitionSettings = UIStyle.GetTransitionSettings( container, property );
// propertyTransition.timeLine = UI.GetTimeLine( control, transitionSettings.timeLine );
// propertyTransition.start = propertyTransition.timeLine.position;
// propertyTransition.end = propertyTransition.start + transitionSettings.duration;
// propertyTransition.transitioning = true;
// propertyTransition.curve = transitionSettings.curve;
// }
// }
// if ( propertyTransition.value == number )
// {
// propertyTransition.transitioning = false;
// return computedValue;
// }
// var computedTransitionValue = Compute( control, propertyTransition.value, alternative, relative );
// var transitionPhase = propertyTransition.timeLine.ComputeRange( propertyTransition.start, propertyTransition.end );
// if ( transitionPhase >= 1 )
// {
// activeNumberTransitions.Remove( propertyTransition );
// }
// var amount = MathX.Clamp01( transitionPhase );
// var curveAmount = amount;
2024-09-14 06:41:52 +00:00
2025-06-19 17:22:25 +00:00
// if ( propertyTransition.curve != null )
// {
// curveAmount = propertyTransition.curve.Sample( curveAmount );
// }
// return Mathf.Lerp( computedTransitionValue, computedValue, curveAmount );
}
static ActiveStyleTransition<UINumber, UIStyleNumberPropertyAndName> EnsureTransitionObject(
ActiveStyleTransition<UINumber, UIStyleNumberPropertyAndName> propertyTransition,
Control control, UINumber uiNUmber, UIStyleNumberProperty property, string shaderPropertyName )
{
if ( propertyTransition != null )
2024-09-14 06:41:52 +00:00
{
2025-06-19 17:22:25 +00:00
return propertyTransition;
2024-09-14 06:41:52 +00:00
}
2025-06-19 17:22:25 +00:00
var container = control as UIStylePropertyContainer;
propertyTransition = new ActiveStyleTransition<UINumber, UIStyleNumberPropertyAndName>();
propertyTransition.propertyType = UIStyleNumberPropertyAndName.Create( property, shaderPropertyName );
var transitionSettings = UIStyle.GetTransitionSettings( container, property, shaderPropertyName );
propertyTransition.value = uiNUmber;
propertyTransition.timeLine = UI.GetTimeLine( control, transitionSettings.timeLine );
propertyTransition.transitioning = false;
container.GetActiveUINumberTransitions().Add( propertyTransition );
return propertyTransition;
2024-09-14 06:41:52 +00:00
}
2024-08-09 13:52:49 +00:00
public static float Compute( Control control, UINumber number, float alternative = 0, float relative = 100 )
{
if ( number == null || control == null || number.IsNone )
{
return alternative;
}
2024-08-11 17:38:06 +00:00
var width = UI.GetWindowWidth( control );
var height = UI.GetWindowHeight( control );
2024-08-09 13:52:49 +00:00
return Compute( control, number, width, height, relative );
}
2025-06-19 17:22:25 +00:00
public static float em( Control control )
{
var ui = UIHolder.GetUI( control );
return ui == null ? 12 : ui.X_computedFontSizePixels;
}
public static float uiw( Control control )
{
var ui = UIHolder.GetUI( control );
return ui == null ? 1920 : ui.Size.X;
}
public static float uih( Control control )
2024-08-09 13:52:49 +00:00
{
2025-06-19 17:22:25 +00:00
var ui = UIHolder.GetUI( control );
return ui == null ? 1080 : ui.Size.Y;
2024-08-09 13:52:49 +00:00
}
2025-06-19 17:22:25 +00:00
2024-08-09 13:52:49 +00:00
public static float Compute( Control control, UINumber number, float width, float height, float relative )
2024-09-14 06:41:52 +00:00
{
2025-06-23 11:16:01 +00:00
var value = ComputeFormulaValue( control, number, width, height, relative );
2024-09-14 06:41:52 +00:00
if ( ! number.isAnimated || number.animationCurve == null )
{
return value;
}
2025-06-19 17:22:25 +00:00
var timeLine = UI.GetTimeLine( control, number.timeLine );
var phase = timeLine.ComputePhase( number.animationDuration, number.animationOffset );
2024-09-14 06:41:52 +00:00
return number.animationCurve.Sample( phase ) * value;
}
static readonly string[] _variables = new string[]
{
"em",
"vw", "vh",
"pw", "ph",
"cw","ch",
"cx","cy",
2025-06-19 17:22:25 +00:00
"uiw", "uih",
"relative",
"value"
};
2025-06-23 11:16:01 +00:00
static MapList<string,float[]> cached = new MapList<string, float[]>();
class CachedExpressionData
{
public string formula;
public Godot.Collections.Array inputs;
public float result;
public bool Matches( UINumber number, Godot.Collections.Array inputs )
{
if ( number.unit != formula )
{
return false;
}
for ( int i = 0; i < inputs.Count; i++ )
{
if ( (float)inputs.ElementAt( i ) != (float)this.inputs.ElementAt( i ) )
{
return false;
}
}
return true;
}
}
static List<CachedExpressionData> cachedExpressions = new List<CachedExpressionData>();
static int maxCachedExpressions = 50;
static int GetCacheIndex( UINumber number, Godot.Collections.Array inputs )
{
return cachedExpressions.FindIndex( c => c.Matches( number, inputs ) );
}
static float ComputeFormulaValue( Control control, UINumber number, float width, float height, float relative )
2024-08-09 13:52:49 +00:00
{
if ( number == null )
{
return 0;
}
switch ( number.unit )
{
case "em":
{
2025-06-19 17:22:25 +00:00
return number.value * em( control );
2025-06-23 11:16:01 +00:00
}
2025-06-19 17:22:25 +00:00
2024-08-09 13:52:49 +00:00
case "vw":
{
return number.value * width / 100f;
}
case "vh":
{
return number.value * height / 100f;
}
2024-08-11 17:38:06 +00:00
case "pw":
{
2025-06-19 17:22:25 +00:00
var parent = control.GetParent() as Control;
2024-08-11 17:38:06 +00:00
return number.value / 100f * ( parent == null ? width : parent.Size.X );
}
2025-06-23 11:16:01 +00:00
case "ph":
{
var parent = control.GetParent() as Control;
return number.value / 100f * ( parent == null ? height : parent.Size.Y );
}
2024-09-14 06:41:52 +00:00
case "cw":
{
2025-06-19 17:22:25 +00:00
var parent = control.GetParent() as Control;
2024-09-14 06:41:52 +00:00
return number.value / 100f * ( parent == null ? width : UILayouting.GetContentSize( parent ).X );
}
case "ch":
{
2025-06-19 17:22:25 +00:00
var parent = control.GetParent() as Control;
2024-09-14 06:41:52 +00:00
return number.value / 100f * ( parent == null ? height : UILayouting.GetContentSize( parent ).Y );
}
case "cx":
{
2025-06-19 17:22:25 +00:00
var parent = control.GetParent() as Control;
2024-09-14 06:41:52 +00:00
return number.value / 100f * ( parent == null ? 0 : UILayouting.GetContentOffset( parent ).X );
}
case "cy":
{
2025-06-19 17:22:25 +00:00
var parent = control.GetParent() as Control;
2024-09-14 06:41:52 +00:00
return number.value / 100f * ( parent == null ? 0 : UILayouting.GetContentOffset( parent ).Y );
}
2025-06-19 17:22:25 +00:00
case "uiw":
{
return number.value * uiw( control );
}
case "uih":
{
return number.value * uih( control );
}
2025-06-23 11:16:01 +00:00
2024-08-11 17:38:06 +00:00
2024-09-14 06:41:52 +00:00
case "":
2024-08-09 13:52:49 +00:00
{
return number.value;
}
case "%":
{
return number.value * relative / 100f;
}
}
2025-06-23 11:16:01 +00:00
var parentControl = control.GetParent() as Control;;
var inputs = GetInputs( control, number, parentControl, width, height, relative );
var cacheIndex = GetCacheIndex( number, inputs );
if ( cacheIndex != -1 )
{
var cachedResult = cachedExpressions[ cacheIndex ].result;
cachedExpressions.SwapTowardsBegin( cacheIndex );
// control.LogInfo( "Using cached value", cachedExpressions[ cacheIndex ].formula, ">", cachedResult );
return cachedResult;
}
2024-08-09 13:52:49 +00:00
var expression = new Expression();
var expressionText = number.unit == null ? "" : RegexUtility.Replace( number.unit, "%", " * relative " );
2025-06-23 11:16:01 +00:00
expressionText = RegexUtility.Replace( expressionText, @"\b(\d+)\s*(em|v[wh]|p[wh]|c[wh]|c[xy]|ui[wh])\b", "$1*$2" );
if ( ! IsValid( expressionText ) )
{
return 0;
}
var parseResult = expression.Parse( expressionText, _variables );
2024-08-09 13:52:49 +00:00
if ( Error.Ok != parseResult )
{
return 0;
}
2025-06-23 11:16:01 +00:00
var scale = number.unit.IndexOf( "value" ) == -1 ? number.value : 1;
2024-08-11 17:38:06 +00:00
2025-06-23 11:16:01 +00:00
var result = (float) ( scale * expression.Execute( inputs ).AsDouble() );
Cache( number.unit, inputs, result );
return result;
}
static void Cache( string formula, Godot.Collections.Array inputs, float result )
{
CachedExpressionData data = null;
if ( cachedExpressions.Count < maxCachedExpressions )
{
data = new CachedExpressionData();
cachedExpressions.Add( data );
}
else
{
data = GodotRandom.Get().From( cachedExpressions, 4 * maxCachedExpressions / 5, maxCachedExpressions - 1 );
}
data.formula = formula;
data.inputs = inputs;
data.result = result;
}
static Godot.Collections.Array GetInputs( Control control, UINumber number, Control parentControl, float width, float height, float relative )
{
var inputs = new Godot.Collections.Array();
2025-06-19 17:22:25 +00:00
// em
inputs.Add( em( control ) );
2024-09-14 06:41:52 +00:00
// vw, vh
inputs.Add( width / 100f ); inputs.Add( height / 100f );
// pw, ph
2024-08-11 17:38:06 +00:00
inputs.Add( ( parentControl == null ? width : parentControl.Size.X ) / 100f );
inputs.Add( ( parentControl == null ? height : parentControl.Size.Y ) / 100f );
2024-08-09 13:52:49 +00:00
2024-09-14 06:41:52 +00:00
// cw, ch
inputs.Add( ( parentControl == null ? width : UILayouting.GetContentSize( parentControl ).X ) / 100f );
2025-06-23 11:16:01 +00:00
inputs.Add( ( parentControl == null ? height : UILayouting.GetContentSize( parentControl ).Y ) / 100f );
2024-09-14 06:41:52 +00:00
// cx, cy
inputs.Add( ( parentControl == null ? 0 : UILayouting.GetContentOffset( parentControl ).X ) / 100f );
inputs.Add( ( parentControl == null ? 0 : UILayouting.GetContentOffset( parentControl ).Y ) / 100f );
2025-06-19 17:22:25 +00:00
// uiw, uih
inputs.Add( uiw( control ) );
inputs.Add( uih( control ) );
2024-09-14 06:41:52 +00:00
// "relative"
inputs.Add( relative / 100f );
2024-08-09 13:52:49 +00:00
2025-06-23 11:16:01 +00:00
// value
2024-08-09 13:52:49 +00:00
inputs.Add( number.value );
2025-06-23 11:16:01 +00:00
return inputs;
2024-08-09 13:52:49 +00:00
}
static bool IsValid( string expressionText )
{
if ( expressionText == null || expressionText == "" )
{
return false;
}
var lexer = new CSharpLexer();
var list = lexer.LexToList( expressionText );
if ( lexer.hasError )
{
return false;
}
lexer.GrabMatches( list, expressionText );
for ( int i = 0; i < list.Count; i++ )
{
var token = list[ i ];
if ( token.Is( LexerMatcherLibrary.CwordMatcher ) )
{
var value = token.match;
if ( ! Arrays.Contains( _variables, value ) )
{
// RJLog.Log( "Unknown variable:", "'" + value + "'", " in: ", "'" + expressionText + "'" );
return false;
}
}
}
return true;
}
2024-08-09 13:52:49 +00:00
}
}