Code/Applier.cs
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Runtime.InteropServices;
using Sandbox;
using Sandbox.Rendering;
using Sandbox.UI;
using Goo.Internal;
using EnginePanelTransform = Sandbox.UI.PanelTransform;
namespace Goo;
internal static class Applier
{
// Fast path takes the concrete List<Op> via CollectionsMarshal.AsSpan so the JIT
// emits an indexed-span loop, avoiding the boxed IEnumerator<Op> and per-element
// 64B copy that an interface-typed foreach over a value-type Op would incur.
public static void Apply(Panel root, IReadOnlyList<Op> ops)
{
if (ops is List<Op> list)
{
var span = CollectionsMarshal.AsSpan(list);
for (int i = 0; i < span.Length; i++)
ApplyOne(root, in span[i]);
return;
}
foreach (var op in ops)
ApplyOne(root, in op);
}
static void ApplyOne(Panel root, in Op op)
{
var host = WalkPath(root, op.HostPath);
switch (op.Kind)
{
case OpKind.CreateText:
{
var label = host.AddChild(new StatefulLabel { Text = op.StringPayload! });
host.SetChildIndex(label, op.Index);
break;
}
case OpKind.UpdateText:
{
if (host.GetChild(op.Index, false) is Label label)
label.Text = op.StringPayload!;
break;
}
case OpKind.CreateContainer:
{
var panel = host.AddChild(new StatefulDrawPanel());
// Default FlexDirection; SetStyle overrides if the StyleList carries one.
panel.Style.FlexDirection = FlexDirection.Row;
host.SetChildIndex(panel, op.Index);
break;
}
case OpKind.CreateImage:
{
var img = host.AddChild(new StatefulImage());
if (op.StringPayload is not null)
img.SetTexture(op.StringPayload);
else if (op.Texture is not null)
img.Texture = op.Texture;
host.SetChildIndex(img, op.Index);
break;
}
case OpKind.UpdateImage:
{
if (host.GetChild(op.Index, false) is Sandbox.UI.Image img)
{
if (op.StringPayload is not null)
img.SetTexture(op.StringPayload);
else if (op.Texture is not null)
img.Texture = op.Texture;
}
break;
}
case OpKind.CreateScenePanel:
{
// ScenePath ctor path triggers engine's RenderScene.Load(SceneLoadOptions) and engine owns the scene.
// Scene-ref path uses the parameterless ctor and assigns RenderScene, which flips _ownsScene = false.
var scenePanel = op.StringPayload is not null
? new StatefulScenePanel(op.StringPayload)
: new StatefulScenePanel();
if (op.StringPayload is null && op.Scene is not null)
scenePanel.RenderScene = op.Scene;
scenePanel.RenderOnce = op.RenderOnce;
host.AddChild(scenePanel);
host.SetChildIndex(scenePanel, op.Index);
break;
}
case OpKind.UpdateScenePanel:
{
if (host.GetChild(op.Index, false) is Sandbox.UI.ScenePanel scenePanel)
{
// ScenePath hot-swap: re-load via SceneLoadOptions on the existing RenderScene.
// Scene-ref hot-swap: assign RenderScene (disposes prior owned scene if any).
if (op.StringPayload is not null)
{
var options = new SceneLoadOptions { ShowLoadingScreen = false };
if (options.SetScene(op.StringPayload))
scenePanel.RenderScene.Load(options);
}
else if (op.Scene is not null)
{
scenePanel.RenderScene = op.Scene;
}
scenePanel.RenderOnce = op.RenderOnce;
}
break;
}
case OpKind.CreateSvgPanel:
{
var svg = new StatefulSvgPanel();
if (op.StringPayload is not null)
svg.Src = op.StringPayload;
if (op.Color is not null)
svg.Color = op.Color;
host.AddChild(svg);
host.SetChildIndex(svg, op.Index);
break;
}
case OpKind.UpdateSvgPanel:
{
if (host.GetChild(op.Index, false) is Sandbox.UI.SvgPanel svg)
{
svg.Src = op.StringPayload;
svg.Color = op.Color;
}
break;
}
case OpKind.CreateSector:
{
var p = new StatefulShapePanel();
// Shapes with no explicit size collapse to 0 under the engine's
// default Yoga flex layout. Fill the parent unless the user
// declared a Width/Height (SetStyle path preserves this default).
p.Style.Width = Length.Percent(100);
p.Style.Height = Length.Percent(100);
host.AddChild(p);
p.ApplyShape(BlobKind.Sector, in op.Shape);
host.SetChildIndex(p, op.Index);
break;
}
case OpKind.UpdateSector:
{
if (host.GetChild(op.Index, false) is StatefulShapePanel p)
p.ApplyShape(BlobKind.Sector, in op.Shape);
break;
}
case OpKind.CreateArc:
{
var p = new StatefulShapePanel();
p.Style.Width = Length.Percent(100);
p.Style.Height = Length.Percent(100);
host.AddChild(p);
p.ApplyShape(BlobKind.Arc, in op.Shape);
host.SetChildIndex(p, op.Index);
break;
}
case OpKind.UpdateArc:
{
if (host.GetChild(op.Index, false) is StatefulShapePanel p)
p.ApplyShape(BlobKind.Arc, in op.Shape);
break;
}
case OpKind.CreatePolygon:
{
var p = new StatefulShapePanel();
p.Style.Width = Length.Percent(100);
p.Style.Height = Length.Percent(100);
host.AddChild(p);
if (op.Points is not null && op.Points.Length >= 3)
p.ApplyPolygon(op.Points);
host.SetChildIndex(p, op.Index);
break;
}
case OpKind.UpdatePolygon:
{
if (host.GetChild(op.Index, false) is StatefulShapePanel p)
{
if (op.Points is not null && op.Points.Length >= 3)
p.ApplyPolygon(op.Points);
else
p.Style.BackgroundImage = null;
}
break;
}
case OpKind.CreateWebPanel:
{
var wp = new StatefulWebPanel();
// WebPanel needs PointerEvents to receive mouse/wheel/key events; default to All so a bare one is interactive (preserved unless the user overrides).
wp.Style.PointerEvents = PointerEvents.All;
// Engine writes the webview texture to BackgroundImage without a size; force 100% so it fills the panel instead of centering with black bars.
wp.Style.BackgroundSizeX = Length.Percent(100);
wp.Style.BackgroundSizeY = Length.Percent(100);
if (op.StringPayload is not null)
wp.Url = op.StringPayload;
if (op.Paused)
wp.Surface.InBackgroundMode = true;
host.AddChild(wp);
host.SetChildIndex(wp, op.Index);
break;
}
case OpKind.UpdateWebPanel:
{
if (host.GetChild(op.Index, false) is Sandbox.UI.WebPanel wp)
{
wp.Url = op.StringPayload;
wp.Surface.InBackgroundMode = op.Paused;
}
break;
}
case OpKind.CreateTextEntry:
{
var te = new StatefulTextEntry();
// TextEntry needs PointerEvents to receive focus on click; default to All so a bare one is interactive (preserved unless the user overrides).
te.Style.PointerEvents = PointerEvents.All;
// Create-time uses the Text setter (safe: not focused yet, so the Value-setter HasFocus guard is irrelevant; the Update arm uses Value for controlled mode).
if (op.StringPayload is not null)
te.Text = op.StringPayload;
if (op.Placeholder is not null)
te.Placeholder = op.Placeholder;
if (op.MaxLength.HasValue)
te.MaxLength = op.MaxLength.Value;
te.Disabled = op.Disabled;
te.Numeric = op.Numeric;
if (op.MinValue.HasValue)
te.MinValue = op.MinValue.Value;
if (op.MaxValue.HasValue)
te.MaxValue = op.MaxValue.Value;
if (op.NumberFormat is not null)
te.NumberFormat = op.NumberFormat;
te.Multiline = op.Multiline;
te._onChange = op.OnChange;
te._onSubmit = op.OnSubmit;
te._onFocus = op.OnFocus;
te._onBlur = op.OnBlur;
te._onCancel = op.OnCancel;
// OnChange/OnSubmit live outside BlobEvents, so a text-only entry (no mouse
// handlers) never gets a SetEvents op. Wire RequestRebuild here too so typing
// auto-rebuilds. Same policy the SetEvents arm reads from BuildContext.
var teCtx = BuildContext._current;
te.RequestRebuild = (teCtx != null && teCtx.AutoRebuildOnEvents) ? teCtx.RootRebuild : null;
host.AddChild(te);
host.SetChildIndex(te, op.Index);
break;
}
case OpKind.UpdateTextEntry:
{
if (host.GetChild(op.Index, false) is Sandbox.UI.TextEntry te)
{
// Controlled mode uses the Value setter so the engine's HasFocus guard no-ops mid-typing; uncontrolled mode never touches text (engine owns it after InitialText).
if (op.IsControlled)
te.Value = op.StringPayload ?? "";
te.Placeholder = op.Placeholder;
te.MaxLength = op.MaxLength;
te.Disabled = op.Disabled;
te.Numeric = op.Numeric;
te.MinValue = op.MinValue;
te.MaxValue = op.MaxValue;
te.NumberFormat = op.NumberFormat;
te.Multiline = op.Multiline;
if (te is Goo.Internal.StatefulTextEntry ste)
{
ste._onChange = op.OnChange;
ste._onSubmit = op.OnSubmit;
ste._onFocus = op.OnFocus;
ste._onBlur = op.OnBlur;
ste._onCancel = op.OnCancel;
// See CreateTextEntry: keep RequestRebuild wired in case this entry
// never receives a SetEvents op (no mouse handlers in BlobEvents).
var steCtx = BuildContext._current;
ste.RequestRebuild = (steCtx != null && steCtx.AutoRebuildOnEvents) ? steCtx.RootRebuild : null;
}
}
break;
}
case OpKind.RemoveAt:
{
host.GetChild(op.Index, false)?.Delete(true);
break;
}
case OpKind.MoveAt:
{
var child = host.GetChild(op.Index, false);
if (child is not null)
host.SetChildIndex(child, op.ToIndex);
break;
}
case OpKind.SetStyle:
{
ApplyAllDeclaredFields(host, host.Style, op.Style!);
break;
}
case OpKind.SetEvents:
{
// HostPath for SetEvents resolves directly to the target panel
// (not its parent), so `host` here is the target.
if (host is IStatefulEventHost evHost)
{
evHost.ApplyEvents(op.Events);
// Auto-rebuild-after-dispatch: wire the panel's rebuild request from the
// owning GooPanel, unless it opted out. BuildContext._current is the
// owning panel's context for the whole Apply pass.
var ctx = BuildContext._current;
evHost.RequestRebuild = (ctx != null && ctx.AutoRebuildOnEvents) ? ctx.RootRebuild : null;
if (op.Events.AnyNonNull && !evHost.UserSetPointerEvents)
host.Style.PointerEvents = PointerEvents.All;
}
break;
}
case OpKind.SetDrawState:
{
if (host is StatefulDrawPanel p)
{
p._drawState = new DrawState(
op.DrawMaterial,
op.DrawUniforms,
op.DrawCallback);
}
break;
}
}
}
static void ApplyAllDeclaredFields(Panel host, PanelStyle target, StyleList style)
{
// Track whether the user declared PointerEvents in their style; the SetEvents handler
// uses this flag to decide whether to auto-gate to PointerEvents.All when any handler is set.
if (host is IStatefulEventHost evHost)
evHost.UserSetPointerEvents = style.TryGet(StyleField.PointerEvents, out _);
Length? padding = TryLen(style, StyleField.Padding);
Length? paddingLeft = TryLen(style, StyleField.PaddingLeft) ?? padding;
Length? paddingTop = TryLen(style, StyleField.PaddingTop) ?? padding;
Length? paddingRight = TryLen(style, StyleField.PaddingRight) ?? padding;
Length? paddingBottom = TryLen(style, StyleField.PaddingBottom) ?? padding;
Length? margin = TryLen(style, StyleField.Margin);
Length? marginLeft = TryLen(style, StyleField.MarginLeft) ?? margin;
Length? marginTop = TryLen(style, StyleField.MarginTop) ?? margin;
Length? marginRight = TryLen(style, StyleField.MarginRight) ?? margin;
Length? marginBottom = TryLen(style, StyleField.MarginBottom) ?? margin;
Length? gap = TryLen(style, StyleField.Gap);
Length? rowGap = TryLen(style, StyleField.RowGap) ?? gap;
Length? columnGap = TryLen(style, StyleField.ColumnGap) ?? gap;
Length? borderRadius = TryLen(style, StyleField.BorderRadius);
Length? borderTopLeftRadius = TryLen(style, StyleField.BorderTopLeftRadius) ?? borderRadius;
Length? borderTopRightRadius = TryLen(style, StyleField.BorderTopRightRadius) ?? borderRadius;
Length? borderBottomRightRadius = TryLen(style, StyleField.BorderBottomRightRadius) ?? borderRadius;
Length? borderBottomLeftRadius = TryLen(style, StyleField.BorderBottomLeftRadius) ?? borderRadius;
// BorderColor fan-out: shorthand default applied to per-edge reads.
Color? borderColor = TryColor(style, StyleField.BorderColor);
Color? borderLeftColor = TryColor(style, StyleField.BorderLeftColor) ?? borderColor;
Color? borderTopColor = TryColor(style, StyleField.BorderTopColor) ?? borderColor;
Color? borderRightColor = TryColor(style, StyleField.BorderRightColor) ?? borderColor;
Color? borderBottomColor = TryColor(style, StyleField.BorderBottomColor) ?? borderColor;
// BorderWidth fan-out: shorthand default applied to per-edge reads.
Length? borderWidth = TryLen(style, StyleField.BorderWidth);
Length? borderLeftWidth = TryLen(style, StyleField.BorderLeftWidth) ?? borderWidth;
Length? borderTopWidth = TryLen(style, StyleField.BorderTopWidth) ?? borderWidth;
Length? borderRightWidth = TryLen(style, StyleField.BorderRightWidth) ?? borderWidth;
Length? borderBottomWidth = TryLen(style, StyleField.BorderBottomWidth) ?? borderWidth;
// FlexDirection is non-nullable on PanelStyle; default to Row when undeclared.
target.FlexDirection = TryFlex(style, StyleField.FlexDirection) ?? FlexDirection.Row;
target.JustifyContent = TryJustify(style, StyleField.JustifyContent);
target.AlignItems = TryAlign(style, StyleField.AlignItems);
target.Display = TryDisplay(style, StyleField.Display);
// Shape panels need to fill their parent by default so they don't collapse
// to 0 under flex layout; explicit user Width/Height still wins.
target.Width = TryLen(style, StyleField.Width)
?? (host is Goo.Internal.StatefulShapePanel ? Length.Percent(100) : (Length?)null);
target.Height = TryLen(style, StyleField.Height)
?? (host is Goo.Internal.StatefulShapePanel ? Length.Percent(100) : (Length?)null);
// On shape panels BackgroundColor is redirected to BackgroundTint (multiplies into the alpha mask) rather than written directly, which would render a solid rect behind the silhouette. Handled with BackgroundTint below.
bool _isShape = host is Goo.Internal.StatefulShapePanel;
bool _isWebPanel = host is Goo.Internal.StatefulWebPanel;
bool _isTextEntry = host is Goo.Internal.StatefulTextEntry;
if (_isShape)
target.BackgroundColor = null;
else
target.BackgroundColor = TryColor(style, StyleField.BackgroundColor);
target.Padding = padding;
target.PaddingLeft = paddingLeft; target.PaddingTop = paddingTop;
target.PaddingRight = paddingRight; target.PaddingBottom = paddingBottom;
target.Margin = margin;
target.MarginLeft = marginLeft; target.MarginTop = marginTop;
target.MarginRight = marginRight; target.MarginBottom = marginBottom;
target.RowGap = rowGap; target.ColumnGap = columnGap;
target.BorderTopLeftRadius = borderTopLeftRadius;
target.BorderTopRightRadius = borderTopRightRadius;
target.BorderBottomRightRadius = borderBottomRightRadius;
target.BorderBottomLeftRadius = borderBottomLeftRadius;
// Align properties.
target.AlignContent = TryAlign(style, StyleField.AlignContent);
target.AlignSelf = TryAlign(style, StyleField.AlignSelf);
// Aspect ratio.
target.AspectRatio = TrySingle(style, StyleField.AspectRatio);
// Backdrop filter properties.
target.BackdropFilterBlur = TryLen(style, StyleField.BackdropFilterBlur);
target.BackdropFilterBrightness = TryLen(style, StyleField.BackdropFilterBrightness);
target.BackdropFilterContrast = TryLen(style, StyleField.BackdropFilterContrast);
target.BackdropFilterHueRotate = TryLen(style, StyleField.BackdropFilterHueRotate);
target.BackdropFilterInvert = TryLen(style, StyleField.BackdropFilterInvert);
target.BackdropFilterSaturate = TryLen(style, StyleField.BackdropFilterSaturate);
target.BackdropFilterSepia = TryLen(style, StyleField.BackdropFilterSepia);
// Background properties.
target.BackgroundAngle = TryLen(style, StyleField.BackgroundAngle);
target.BackgroundBlendMode = TryString(style, StyleField.BackgroundBlendMode);
// On shape panels, BackgroundImage/Size are owned by ApplyShape (the baked mask); skip them here so SetStyle does not clobber the mask. Other panels pass through (undeclared clears).
if (!_isShape)
target.BackgroundImage = TryTexture(style, StyleField.BackgroundImage);
target.BackgroundPlaybackPaused = TryBool(style, StyleField.BackgroundPlaybackPaused);
target.BackgroundPositionX = TryLen(style, StyleField.BackgroundPositionX);
target.BackgroundPositionY = TryLen(style, StyleField.BackgroundPositionY);
target.BackgroundRepeat = TryBackgroundRepeat(style, StyleField.BackgroundRepeat);
if (!_isShape)
{
// WebPanel needs BackgroundSize=100% so the engine-managed webview texture
// fills the panel instead of being centered at native size with black bars
// around it. Explicit user value still wins.
target.BackgroundSizeX = TryLen(style, StyleField.BackgroundSizeX)
?? (_isWebPanel ? Length.Percent(100) : (Length?)null);
target.BackgroundSizeY = TryLen(style, StyleField.BackgroundSizeY)
?? (_isWebPanel ? Length.Percent(100) : (Length?)null);
}
// BackgroundTint: on shape panels BackgroundColor multiplies into the alpha mask via this property (explicit BackgroundTint overrides); elsewhere only an explicit value is honored.
if (_isShape)
target.BackgroundTint = TryColor(style, StyleField.BackgroundTint)
?? TryColor(style, StyleField.BackgroundColor);
else
target.BackgroundTint = TryColor(style, StyleField.BackgroundTint);
// Border color: shorthand first, per-edge override.
target.BorderColor = borderColor;
target.BorderLeftColor = borderLeftColor; target.BorderTopColor = borderTopColor;
target.BorderRightColor = borderRightColor; target.BorderBottomColor = borderBottomColor;
// Border image properties.
target.BorderImageFill = TryBorderImageFill(style, StyleField.BorderImageFill);
target.BorderImageRepeat = TryBorderImageRepeat(style, StyleField.BorderImageRepeat);
target.BorderImageSource = TryTexture(style, StyleField.BorderImageSource);
target.BorderImageTint = TryColor(style, StyleField.BorderImageTint);
target.BorderImageWidthBottom = TryLen(style, StyleField.BorderImageWidthBottom);
target.BorderImageWidthLeft = TryLen(style, StyleField.BorderImageWidthLeft);
target.BorderImageWidthRight = TryLen(style, StyleField.BorderImageWidthRight);
target.BorderImageWidthTop = TryLen(style, StyleField.BorderImageWidthTop);
// Border width: shorthand first, per-edge override.
target.BorderWidth = borderWidth;
target.BorderLeftWidth = borderLeftWidth; target.BorderTopWidth = borderTopWidth;
target.BorderRightWidth = borderRightWidth; target.BorderBottomWidth = borderBottomWidth;
// Position edges.
target.Bottom = TryLen(style, StyleField.Bottom);
target.Left = TryLen(style, StyleField.Left);
target.Right = TryLen(style, StyleField.Right);
target.Top = TryLen(style, StyleField.Top);
// Caret.
target.CaretColor = TryColor(style, StyleField.CaretColor);
// Cursor. TextEntry defaults to "text" so consumers don't have to opt in
// to the I-beam; explicit user value wins.
target.Cursor = TryString(style, StyleField.Cursor)
?? (_isTextEntry ? "text" : null);
// Filter properties.
target.FilterBlur = TryLen(style, StyleField.FilterBlur);
target.FilterBorderColor = TryColor(style, StyleField.FilterBorderColor);
target.FilterBorderWidth = TryLen(style, StyleField.FilterBorderWidth);
target.FilterBrightness = TryLen(style, StyleField.FilterBrightness);
target.FilterContrast = TryLen(style, StyleField.FilterContrast);
target.FilterHueRotate = TryLen(style, StyleField.FilterHueRotate);
target.FilterInvert = TryLen(style, StyleField.FilterInvert);
target.FilterSaturate = TryLen(style, StyleField.FilterSaturate);
target.FilterSepia = TryLen(style, StyleField.FilterSepia);
target.FilterTint = TryColor(style, StyleField.FilterTint);
// Flex properties.
target.FlexBasis = TryLen(style, StyleField.FlexBasis);
target.FlexGrow = TrySingle(style, StyleField.FlexGrow);
target.FlexShrink = TrySingle(style, StyleField.FlexShrink);
target.FlexWrap = TryWrap(style, StyleField.FlexWrap);
// Font properties.
target.FontColor = TryColor(style, StyleField.FontColor);
target.FontFamily = TryString(style, StyleField.FontFamily);
target.FontSize = TryLen(style, StyleField.FontSize);
target.FontSmooth = TryFontSmooth(style, StyleField.FontSmooth);
target.FontStyle = TryFontStyle(style, StyleField.FontStyle);
target.FontVariantNumeric = TryFontVariantNumeric(style, StyleField.FontVariantNumeric);
target.FontWeight = TryInt32(style, StyleField.FontWeight);
// Image rendering.
target.ImageRendering = TryImageRendering(style, StyleField.ImageRendering);
// Letter and line spacing.
target.LetterSpacing = TryLen(style, StyleField.LetterSpacing);
target.LineHeight = TryLen(style, StyleField.LineHeight);
// Mask properties.
target.MaskAngle = TryLen(style, StyleField.MaskAngle);
target.MaskImage = TryTexture(style, StyleField.MaskImage);
target.MaskMode = TryMaskMode(style, StyleField.MaskMode);
target.MaskPositionX = TryLen(style, StyleField.MaskPositionX);
target.MaskPositionY = TryLen(style, StyleField.MaskPositionY);
target.MaskRepeat = TryBackgroundRepeat(style, StyleField.MaskRepeat);
target.MaskScope = TryMaskScope(style, StyleField.MaskScope);
target.MaskSizeX = TryLen(style, StyleField.MaskSizeX);
target.MaskSizeY = TryLen(style, StyleField.MaskSizeY);
// Min/max size.
target.MaxHeight = TryLen(style, StyleField.MaxHeight);
target.MaxWidth = TryLen(style, StyleField.MaxWidth);
target.MinHeight = TryLen(style, StyleField.MinHeight);
target.MinWidth = TryLen(style, StyleField.MinWidth);
// Mix blend mode.
target.MixBlendMode = TryString(style, StyleField.MixBlendMode);
// Object fit.
target.ObjectFit = TryObjectFit(style, StyleField.ObjectFit);
// Opacity.
target.Opacity = TrySingle(style, StyleField.Opacity);
// Order.
target.Order = TryInt32(style, StyleField.Order);
// Outline properties.
target.OutlineColor = TryColor(style, StyleField.OutlineColor);
target.OutlineOffset = TryLen(style, StyleField.OutlineOffset);
target.OutlineWidth = TryLen(style, StyleField.OutlineWidth);
// Overflow properties.
target.Overflow = TryOverflowMode(style, StyleField.Overflow);
target.OverflowX = TryOverflowMode(style, StyleField.OverflowX);
target.OverflowY = TryOverflowMode(style, StyleField.OverflowY);
// Perspective origin.
target.PerspectiveOriginX = TryLen(style, StyleField.PerspectiveOriginX);
target.PerspectiveOriginY = TryLen(style, StyleField.PerspectiveOriginY);
// Pointer events: WebPanel and TextEntry default to All (intrinsically interactive); also gate to All when handlers exist so a style-only rebuild does not clobber the events-driven gate. Explicit value wins.
bool hasHandlers = host is IStatefulEventHost peHost && peHost.HasEventHandlers;
target.PointerEvents = PointerEventsPolicy.Resolve(
TryPointerEvents(style, StyleField.PointerEvents), _isWebPanel, hasHandlers, isTextEntry: _isTextEntry);
target.Position = TryPositionMode(style, StyleField.Position);
// Sound.
target.SoundIn = TryString(style, StyleField.SoundIn);
target.SoundOut = TryString(style, StyleField.SoundOut);
// Text properties.
target.TextAlign = TryTextAlign(style, StyleField.TextAlign);
target.TextBackgroundAngle = TryLen(style, StyleField.TextBackgroundAngle);
target.TextDecorationColor = TryColor(style, StyleField.TextDecorationColor);
target.TextDecorationLine = TryTextDecoration(style, StyleField.TextDecorationLine);
target.TextDecorationSkipInk = TryTextSkipInk(style, StyleField.TextDecorationSkipInk);
target.TextDecorationStyle = TryTextDecorationStyle(style, StyleField.TextDecorationStyle);
target.TextDecorationThickness = TryLen(style, StyleField.TextDecorationThickness);
target.TextFilter = TryFilterMode(style, StyleField.TextFilter);
target.TextLineThroughOffset = TryLen(style, StyleField.TextLineThroughOffset);
target.TextOverflow = TryTextOverflow(style, StyleField.TextOverflow);
target.TextOverlineOffset = TryLen(style, StyleField.TextOverlineOffset);
target.TextStrokeColor = TryColor(style, StyleField.TextStrokeColor);
target.TextStrokeWidth = TryLen(style, StyleField.TextStrokeWidth);
target.TextTransform = TryTextTransform(style, StyleField.TextTransform);
target.TextUnderlineOffset = TryLen(style, StyleField.TextUnderlineOffset);
// Transform.
target.Transform = TryPanelTransform(style, StyleField.Transform);
// Transform origin.
target.TransformOriginX = TryLen(style, StyleField.TransformOriginX);
target.TransformOriginY = TryLen(style, StyleField.TransformOriginY);
// White space and word properties.
target.WhiteSpace = TryWhiteSpace(style, StyleField.WhiteSpace);
target.WordBreak = TryWordBreak(style, StyleField.WordBreak);
target.WordSpacing = TryLen(style, StyleField.WordSpacing);
// Z-index.
target.ZIndex = TryInt32(style, StyleField.ZIndex);
Color? hoverBg = TryColor(style, StyleField.HoverBackgroundColor);
Color? activeBg = TryColor(style, StyleField.ActiveBackgroundColor);
Color? focusBg = TryColor(style, StyleField.FocusBackgroundColor);
Color? hoverFg = TryColor(style, StyleField.HoverFontColor);
Color? activeFg = TryColor(style, StyleField.ActiveFontColor);
Color? focusFg = TryColor(style, StyleField.FocusFontColor);
int? transMs = TryInt32(style, StyleField.TransitionMs);
bool hasAnyVariant =
hoverBg.HasValue || activeBg.HasValue || focusBg.HasValue ||
hoverFg.HasValue || activeFg.HasValue || focusFg.HasValue;
if (hasAnyVariant && host is IStatefulHost stateful)
{
stateful.ApplyStateVariants(
baseBg: target.BackgroundColor,
baseFg: target.FontColor,
hoverBg: hoverBg, activeBg: activeBg, focusBg: focusBg,
hoverFg: hoverFg, activeFg: activeFg, focusFg: focusFg,
transitionMs: transMs);
}
}
// Each Try* ends with (T?)null not null: a bare null lets the ternary resolve through engine types' implicit string operator and NPE. See engine-fact memories.
static Length? TryLen(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.Length ? v.LengthVal : (Length?)null;
static Color? TryColor(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.Color ? v.ColorVal : (Color?)null;
static FlexDirection? TryFlex(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.FlexDirection ? ((FlexDirection)v.EnumVal) : (FlexDirection?)null;
static Justify? TryJustify(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.Justify ? ((Justify)v.EnumVal) : (Justify?)null;
static Align? TryAlign(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.Align ? ((Align)v.EnumVal) : (Align?)null;
static DisplayMode? TryDisplay(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.DisplayMode ? ((DisplayMode)v.EnumVal) : (DisplayMode?)null;
static string? TryString(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.String ? (string?)v.RefVal : null;
static float? TrySingle(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.Single ? System.BitConverter.Int32BitsToSingle(v.RefSlot) : (float?)null;
static bool? TryBool(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.Boolean ? v.RefSlot != 0 : (bool?)null;
static int? TryInt32(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.Int32 ? v.RefSlot : (int?)null;
static Texture? TryTexture(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.Texture ? (Texture?)v.RefVal : null;
static EnginePanelTransform? TryPanelTransform(StyleList s, StyleField f)
{
if (!s.TryGet(f, out var v)) return null;
if (v.Kind != StyleValueKind.PanelTransform) return null;
if (v.RefVal is not ImmutableList<EnginePanelTransform.Entry> list) return null;
return new EnginePanelTransform { List = list };
}
static Wrap? TryWrap(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.Wrap ? ((Wrap)v.EnumVal) : (Wrap?)null;
static BackgroundRepeat? TryBackgroundRepeat(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.BackgroundRepeat ? ((BackgroundRepeat)v.EnumVal) : (BackgroundRepeat?)null;
static BorderImageFill? TryBorderImageFill(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.BorderImageFill ? ((BorderImageFill)v.EnumVal) : (BorderImageFill?)null;
static BorderImageRepeat? TryBorderImageRepeat(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.BorderImageRepeat ? ((BorderImageRepeat)v.EnumVal) : (BorderImageRepeat?)null;
static FontSmooth? TryFontSmooth(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.FontSmooth ? ((FontSmooth)v.EnumVal) : (FontSmooth?)null;
static FontStyle? TryFontStyle(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.FontStyle ? ((FontStyle)v.EnumVal) : (FontStyle?)null;
static FontVariantNumeric? TryFontVariantNumeric(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.FontVariantNumeric ? ((FontVariantNumeric)v.EnumVal) : (FontVariantNumeric?)null;
static ImageRendering? TryImageRendering(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.ImageRendering ? ((ImageRendering)v.EnumVal) : (ImageRendering?)null;
static MaskMode? TryMaskMode(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.MaskMode ? ((MaskMode)v.EnumVal) : (MaskMode?)null;
static MaskScope? TryMaskScope(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.MaskScope ? ((MaskScope)v.EnumVal) : (MaskScope?)null;
static ObjectFit? TryObjectFit(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.ObjectFit ? ((ObjectFit)v.EnumVal) : (ObjectFit?)null;
static OverflowMode? TryOverflowMode(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.OverflowMode ? ((OverflowMode)v.EnumVal) : (OverflowMode?)null;
static PointerEvents? TryPointerEvents(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.PointerEvents ? ((PointerEvents)v.EnumVal) : (PointerEvents?)null;
static PositionMode? TryPositionMode(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.PositionMode ? ((PositionMode)v.EnumVal) : (PositionMode?)null;
static TextAlign? TryTextAlign(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.TextAlign ? ((TextAlign)v.EnumVal) : (TextAlign?)null;
static TextDecoration? TryTextDecoration(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.TextDecoration ? ((TextDecoration)v.EnumVal) : (TextDecoration?)null;
static TextDecorationStyle? TryTextDecorationStyle(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.TextDecorationStyle ? ((TextDecorationStyle)v.EnumVal) : (TextDecorationStyle?)null;
static TextSkipInk? TryTextSkipInk(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.TextSkipInk ? ((TextSkipInk)v.EnumVal) : (TextSkipInk?)null;
static FilterMode? TryFilterMode(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.FilterMode ? ((FilterMode)v.EnumVal) : (FilterMode?)null;
static TextOverflow? TryTextOverflow(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.TextOverflow ? ((TextOverflow)v.EnumVal) : (TextOverflow?)null;
static TextTransform? TryTextTransform(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.TextTransform ? ((TextTransform)v.EnumVal) : (TextTransform?)null;
static WhiteSpace? TryWhiteSpace(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.WhiteSpace ? ((WhiteSpace)v.EnumVal) : (WhiteSpace?)null;
static WordBreak? TryWordBreak(StyleList s, StyleField f)
=> s.TryGet(f, out var v) && v.Kind == StyleValueKind.WordBreak ? ((WordBreak)v.EnumVal) : (WordBreak?)null;
static Panel WalkPath(Panel root, int[] path)
{
var cur = root;
foreach (var idx in path)
{
cur = cur.GetChild(idx, false);
if (cur is null)
throw new InvalidOperationException(
$"Applier.WalkPath: child index {idx} is null while traversing HostPath=[{string.Join(",", path)}].");
}
return cur;
}
}