Core/Blob.cs
using System;
using System.Collections;
using System.Collections.Immutable;
using Sandbox;
using Sandbox.Rendering;
using Sandbox.UI;
namespace Goo;
/// <summary>Renders a run of text. Style it with the text and common style properties.</summary>
public readonly partial record struct Text : IBlob
{
public static BlobKind Kind => BlobKind.Text;
/// <summary>The text to display.</summary>
public string Content { get; init; }
/// <summary>Stable identity across rebuilds. Set it when this blob can change position among its siblings, so the reconciler matches it to the same panel.</summary>
public string? Key { get; init; }
internal readonly StyleList _style;
internal readonly Action<MousePanelEvent>? _onClick;
internal readonly Action<MousePanelEvent>? _onMouseEnter;
internal readonly Action<MousePanelEvent>? _onMouseLeave;
internal readonly Action<MousePanelEvent>? _onMouseDown;
internal readonly Action<MousePanelEvent>? _onMouseUp;
internal readonly Action<MousePanelEvent>? _onMouseMove;
/// <summary>Called when clicked.</summary>
public Action<MousePanelEvent>? OnClick { init => _onClick = value; }
/// <summary>Called when the pointer enters.</summary>
public Action<MousePanelEvent>? OnMouseEnter { init => _onMouseEnter = value; }
/// <summary>Called when the pointer leaves.</summary>
public Action<MousePanelEvent>? OnMouseLeave { init => _onMouseLeave = value; }
/// <summary>Called when a mouse button is pressed.</summary>
public Action<MousePanelEvent>? OnMouseDown { init => _onMouseDown = value; }
/// <summary>Called when a mouse button is released.</summary>
public Action<MousePanelEvent>? OnMouseUp { init => _onMouseUp = value; }
/// <summary>Called when the pointer moves over it.</summary>
public Action<MousePanelEvent>? OnMouseMove { init => _onMouseMove = value; }
public Text(string content)
{
Content = content;
Key = null;
_style = StyleList.Empty;
}
public bool Equals(Text other)
=> Content == other.Content && Key == other.Key && StyleList.ContentsEqual(_style, other._style);
public override int GetHashCode()
=> HashCode.Combine(Content, Key);
// _style and event handlers intentionally excluded; mirrors Container per StructIntentEqualityTests.
void IBlob.WriteTo(ref Frame frame)
{
frame.Kind = BlobKind.Text;
frame.Key = Key;
frame.Content = Content;
frame.Style = _style;
frame.Children = null;
frame.Texture = null;
frame.Path = null;
frame.Scene = null;
frame.RenderOnce = false;
frame.Paused = false;
frame.Color = null;
frame.Shape = default;
frame.Points = null;
frame.Material = null;
frame.Uniforms = ImmutableArray<UniformValue>.Empty;
frame.Draw = null;
frame.Placeholder = null;
frame.MaxLength = null;
frame.Disabled = false;
frame.Numeric = false;
frame.MinValue = null;
frame.MaxValue = null;
frame.NumberFormat = null;
frame.Multiline = false;
frame.OnChange = null;
frame.OnSubmit = null;
frame.OnFocus = null;
frame.OnBlur = null;
frame.OnCancel = null;
frame.IsControlled = false;
frame.ValueAndInitialTextBothSet = false;
frame.Events.OnClick = _onClick;
frame.Events.OnMouseEnter = _onMouseEnter;
frame.Events.OnMouseLeave = _onMouseLeave;
frame.Events.OnMouseDown = _onMouseDown;
frame.Events.OnMouseUp = _onMouseUp;
frame.Events.OnMouseMove = _onMouseMove;
}
}
/// <summary>
/// The general-purpose layout blob. Holds children and exposes the full style surface:
/// layout, flex, spacing, border, background, filters, transforms, and state variants.
/// </summary>
public readonly partial record struct Container : IBlob, IEnumerable
{
public static BlobKind Kind => BlobKind.Container;
/// <summary>Stable identity across rebuilds. Set it when this blob can change position among its siblings, so the reconciler matches it to the same panel.</summary>
public string? Key { get; init; }
internal readonly StyleList _style;
internal readonly Children _children;
internal readonly Action<MousePanelEvent>? _onClick;
internal readonly Action<MousePanelEvent>? _onMouseEnter;
internal readonly Action<MousePanelEvent>? _onMouseLeave;
internal readonly Action<MousePanelEvent>? _onMouseDown;
internal readonly Action<MousePanelEvent>? _onMouseUp;
internal readonly Action<MousePanelEvent>? _onMouseMove;
/// <summary>Called when clicked.</summary>
public Action<MousePanelEvent>? OnClick { init => _onClick = value; }
/// <summary>Called when the pointer enters.</summary>
public Action<MousePanelEvent>? OnMouseEnter { init => _onMouseEnter = value; }
/// <summary>Called when the pointer leaves.</summary>
public Action<MousePanelEvent>? OnMouseLeave { init => _onMouseLeave = value; }
/// <summary>Called when a mouse button is pressed.</summary>
public Action<MousePanelEvent>? OnMouseDown { init => _onMouseDown = value; }
/// <summary>Called when a mouse button is released.</summary>
public Action<MousePanelEvent>? OnMouseUp { init => _onMouseUp = value; }
/// <summary>Called when the pointer moves over it.</summary>
public Action<MousePanelEvent>? OnMouseMove { init => _onMouseMove = value; }
internal readonly Action<DragEvent>? _onDragStart;
internal readonly Action<DragEvent>? _onDrag;
internal readonly Action<DragEvent>? _onDragEnd;
internal readonly Action<PanelEvent>? _onDragEnter;
internal readonly Action<PanelEvent>? _onDragLeave;
internal readonly Action<PanelEvent>? _onDrop;
/// <summary>Called when a drag begins on this blob.</summary>
public Action<DragEvent>? OnDragStart { init => _onDragStart = value; }
/// <summary>Called on each frame of an in-flight drag.</summary>
public Action<DragEvent>? OnDrag { init => _onDrag = value; }
/// <summary>Called when a drag started on this blob ends.</summary>
public Action<DragEvent>? OnDragEnd { init => _onDragEnd = value; }
/// <summary>Called when a dragged item enters this blob as a drop target.</summary>
public Action<PanelEvent>? OnDragEnter { init => _onDragEnter = value; }
/// <summary>Called when a dragged item leaves this blob as a drop target.</summary>
public Action<PanelEvent>? OnDragLeave { init => _onDragLeave = value; }
/// <summary>Called when a dragged item is dropped on this blob.</summary>
public Action<PanelEvent>? OnDrop { init => _onDrop = value; }
internal readonly Material? _material;
internal readonly ImmutableArray<UniformValue> _uniforms;
internal readonly Action<CommandList, Rect>? _draw;
/// <summary>A custom material to render this blob with. Pair with <see cref="Uniforms"/> to feed shader parameters.</summary>
public Material? Material { get => _material; init => _material = value; }
/// <summary>Shader uniform values passed to <see cref="Material"/>.</summary>
public ImmutableArray<UniformValue> Uniforms { get => _uniforms; init => _uniforms = value; }
/// <summary>A direct draw callback, given a command list and the blob's rect, for custom rendering.</summary>
public Action<CommandList, Rect>? Draw { get => _draw; init => _draw = value; }
public Container()
{
if (BuildContext._current == null)
throw new InvalidOperationException(
"Container constructed outside Build(). Containers are short-lived: " +
"build them inside your Build() method, or in a helper function called from Build(). " +
"Static Container fields and instances held across rebuilds are not supported.");
_children = BuildContext._current.RentChildren();
_style = StyleList.Empty;
Key = null;
_material = null;
_uniforms = ImmutableArray<UniformValue>.Empty;
_draw = null;
}
/// <summary>The child blobs added to this container during Build().</summary>
public Children Children => _children;
/// <summary>Adds a child blob. Called by collection-initializer syntax inside Build().</summary>
public void Add<T>(in T child) where T : struct, IBlob => _children.Add(in child);
// IEnumerable is required by C# collection-initializer syntax; iteration is not a
// use case for Container, so this returns an empty enumerator.
IEnumerator IEnumerable.GetEnumerator() => Array.Empty<object>().GetEnumerator();
public bool Equals(Container other)
=> Key == other.Key
&& StyleList.ContentsEqual(_style, other._style)
&& ReferenceEquals(_material, other._material)
&& _draw == other._draw
&& UniformValue.SequenceEqual(_uniforms, other._uniforms);
public override int GetHashCode()
=> Key?.GetHashCode() ?? 0;
// _style, _children, event handlers, _material, _uniforms, and _draw intentionally excluded
// from GetHashCode; Equals governs correctness -- see StructIntentEqualityTests.
void IBlob.WriteTo(ref Frame frame)
{
frame.Kind = BlobKind.Container;
frame.Key = Key;
frame.Content = "";
frame.Style = _style;
frame.Children = _children;
frame.Texture = null;
frame.Path = null;
frame.Scene = null;
frame.RenderOnce = false;
frame.Paused = false;
frame.Color = null;
frame.Shape = default;
frame.Points = null;
frame.Material = _material;
frame.Uniforms = _uniforms;
frame.Draw = _draw;
frame.Placeholder = null;
frame.MaxLength = null;
frame.Disabled = false;
frame.Numeric = false;
frame.MinValue = null;
frame.MaxValue = null;
frame.NumberFormat = null;
frame.Multiline = false;
frame.OnChange = null;
frame.OnSubmit = null;
frame.OnFocus = null;
frame.OnBlur = null;
frame.OnCancel = null;
frame.IsControlled = false;
frame.ValueAndInitialTextBothSet = false;
frame.Events.OnClick = _onClick;
frame.Events.OnMouseEnter = _onMouseEnter;
frame.Events.OnMouseLeave = _onMouseLeave;
frame.Events.OnMouseDown = _onMouseDown;
frame.Events.OnMouseUp = _onMouseUp;
frame.Events.OnMouseMove = _onMouseMove;
frame.Events.OnDragStart = _onDragStart;
frame.Events.OnDrag = _onDrag;
frame.Events.OnDragEnd = _onDragEnd;
frame.Events.OnDragEnter = _onDragEnter;
frame.Events.OnDragLeave = _onDragLeave;
frame.Events.OnDrop = _onDrop;
}
}
/// <summary>
/// Displays an image from a live <see cref="T:Sandbox.Texture"/> or an asset path. Set one source, never both.
/// Wrap in a <see cref="T:Goo.Container"/> for any style it does not expose.
/// </summary>
public readonly partial record struct Image : IBlob
{
public static BlobKind Kind => BlobKind.Image;
/// <summary>A live texture to display. Set this or <see cref="Path"/>, never both. Setting both renders nothing and warns.</summary>
public Texture? Texture { get; init; }
/// <summary>Path to an image asset, for example "ui/logo.png". Set this or <see cref="Texture"/>, never both. Setting both renders nothing and warns.</summary>
public string? Path { get; init; }
/// <summary>Stable identity across rebuilds. Set it when this blob can change position among its siblings, so the reconciler matches it to the same panel.</summary>
public string? Key { get; init; }
internal readonly StyleList _style;
internal readonly Action<MousePanelEvent>? _onClick;
internal readonly Action<MousePanelEvent>? _onMouseEnter;
internal readonly Action<MousePanelEvent>? _onMouseLeave;
internal readonly Action<MousePanelEvent>? _onMouseDown;
internal readonly Action<MousePanelEvent>? _onMouseUp;
internal readonly Action<MousePanelEvent>? _onMouseMove;
/// <summary>Called when clicked.</summary>
public Action<MousePanelEvent>? OnClick { init => _onClick = value; }
/// <summary>Called when the pointer enters.</summary>
public Action<MousePanelEvent>? OnMouseEnter { init => _onMouseEnter = value; }
/// <summary>Called when the pointer leaves.</summary>
public Action<MousePanelEvent>? OnMouseLeave { init => _onMouseLeave = value; }
/// <summary>Called when a mouse button is pressed.</summary>
public Action<MousePanelEvent>? OnMouseDown { init => _onMouseDown = value; }
/// <summary>Called when a mouse button is released.</summary>
public Action<MousePanelEvent>? OnMouseUp { init => _onMouseUp = value; }
/// <summary>Called when the pointer moves over it.</summary>
public Action<MousePanelEvent>? OnMouseMove { init => _onMouseMove = value; }
public Image()
{
Texture = null;
Path = null;
Key = null;
_style = StyleList.Empty;
}
public bool Equals(Image other)
=> ReferenceEquals(Texture, other.Texture)
&& Path == other.Path
&& Key == other.Key
&& StyleList.ContentsEqual(_style, other._style);
public override int GetHashCode()
=> HashCode.Combine(Texture, Path, Key);
// _style and event handlers intentionally excluded; mirrors Text/Container per StructIntentEqualityTests.
void IBlob.WriteTo(ref Frame frame)
{
frame.Kind = BlobKind.Image;
frame.Key = Key;
frame.Content = "";
frame.Style = _style;
frame.Children = null;
frame.Texture = Texture;
frame.Path = Path;
frame.Scene = null;
frame.RenderOnce = false;
frame.Paused = false;
frame.Color = null;
frame.Shape = default;
frame.Points = null;
frame.Material = null;
frame.Uniforms = ImmutableArray<UniformValue>.Empty;
frame.Draw = null;
frame.Placeholder = null;
frame.MaxLength = null;
frame.Disabled = false;
frame.Numeric = false;
frame.MinValue = null;
frame.MaxValue = null;
frame.NumberFormat = null;
frame.Multiline = false;
frame.OnChange = null;
frame.OnSubmit = null;
frame.OnFocus = null;
frame.OnBlur = null;
frame.OnCancel = null;
frame.IsControlled = false;
frame.ValueAndInitialTextBothSet = false;
frame.Events.OnClick = _onClick;
frame.Events.OnMouseEnter = _onMouseEnter;
frame.Events.OnMouseLeave = _onMouseLeave;
frame.Events.OnMouseDown = _onMouseDown;
frame.Events.OnMouseUp = _onMouseUp;
frame.Events.OnMouseMove = _onMouseMove;
}
}
/// <summary>Renders a 3D <see cref="T:Sandbox.Scene"/> into the UI. Provide a live scene or a scene path.</summary>
public readonly partial record struct ScenePanel : IBlob
{
public static BlobKind Kind => BlobKind.ScenePanel;
/// <summary>A live scene to render. Set this or <see cref="ScenePath"/>.</summary>
public Scene? Scene { get; init; }
/// <summary>Path to a scene asset to render. Set this or <see cref="Scene"/>.</summary>
public string? ScenePath { get; init; }
/// <summary>When true, renders the scene a single frame instead of every frame. Use for static previews.</summary>
public bool RenderOnce { get; init; }
/// <summary>Stable identity across rebuilds. Set it when this blob can change position among its siblings, so the reconciler matches it to the same panel.</summary>
public string? Key { get; init; }
internal readonly StyleList _style;
internal readonly Action<MousePanelEvent>? _onClick;
internal readonly Action<MousePanelEvent>? _onMouseEnter;
internal readonly Action<MousePanelEvent>? _onMouseLeave;
internal readonly Action<MousePanelEvent>? _onMouseDown;
internal readonly Action<MousePanelEvent>? _onMouseUp;
internal readonly Action<MousePanelEvent>? _onMouseMove;
/// <summary>Called when clicked.</summary>
public Action<MousePanelEvent>? OnClick { init => _onClick = value; }
/// <summary>Called when the pointer enters.</summary>
public Action<MousePanelEvent>? OnMouseEnter { init => _onMouseEnter = value; }
/// <summary>Called when the pointer leaves.</summary>
public Action<MousePanelEvent>? OnMouseLeave { init => _onMouseLeave = value; }
/// <summary>Called when a mouse button is pressed.</summary>
public Action<MousePanelEvent>? OnMouseDown { init => _onMouseDown = value; }
/// <summary>Called when a mouse button is released.</summary>
public Action<MousePanelEvent>? OnMouseUp { init => _onMouseUp = value; }
/// <summary>Called when the pointer moves over it.</summary>
public Action<MousePanelEvent>? OnMouseMove { init => _onMouseMove = value; }
public ScenePanel()
{
Scene = null;
ScenePath = null;
RenderOnce = false;
Key = null;
_style = StyleList.Empty;
}
public bool Equals(ScenePanel other)
=> ReferenceEquals(Scene, other.Scene)
&& ScenePath == other.ScenePath
&& RenderOnce == other.RenderOnce
&& Key == other.Key
&& StyleList.ContentsEqual(_style, other._style);
public override int GetHashCode()
=> HashCode.Combine(Scene, ScenePath, RenderOnce, Key);
// _style and event handlers intentionally excluded; mirrors Image per StructIntentEqualityTests.
void IBlob.WriteTo(ref Frame frame)
{
frame.Kind = BlobKind.ScenePanel;
frame.Key = Key;
frame.Content = "";
frame.Style = _style;
frame.Children = null;
frame.Texture = null;
frame.Scene = Scene;
frame.Path = ScenePath;
frame.RenderOnce = RenderOnce;
frame.Paused = false;
frame.Color = null;
frame.Shape = default;
frame.Points = null;
frame.Material = null;
frame.Uniforms = ImmutableArray<UniformValue>.Empty;
frame.Draw = null;
frame.Placeholder = null;
frame.MaxLength = null;
frame.Disabled = false;
frame.Numeric = false;
frame.MinValue = null;
frame.MaxValue = null;
frame.NumberFormat = null;
frame.Multiline = false;
frame.OnChange = null;
frame.OnSubmit = null;
frame.OnFocus = null;
frame.OnBlur = null;
frame.OnCancel = null;
frame.IsControlled = false;
frame.ValueAndInitialTextBothSet = false;
frame.Events.OnClick = _onClick;
frame.Events.OnMouseEnter = _onMouseEnter;
frame.Events.OnMouseLeave = _onMouseLeave;
frame.Events.OnMouseDown = _onMouseDown;
frame.Events.OnMouseUp = _onMouseUp;
frame.Events.OnMouseMove = _onMouseMove;
}
}
/// <summary>Renders an SVG asset, with an optional color override.</summary>
public readonly partial record struct SvgPanel : IBlob
{
public static BlobKind Kind => BlobKind.SvgPanel;
/// <summary>Path to the SVG asset to render, for example "ui/icon.svg".</summary>
public string? Path { get; init; }
/// <summary>Optional color to tint the SVG, as a CSS color string. Leave null to use the SVG's own colors.</summary>
public string? Color { get; init; }
/// <summary>Stable identity across rebuilds. Set it when this blob can change position among its siblings, so the reconciler matches it to the same panel.</summary>
public string? Key { get; init; }
internal readonly StyleList _style;
internal readonly Action<MousePanelEvent>? _onClick;
internal readonly Action<MousePanelEvent>? _onMouseEnter;
internal readonly Action<MousePanelEvent>? _onMouseLeave;
internal readonly Action<MousePanelEvent>? _onMouseDown;
internal readonly Action<MousePanelEvent>? _onMouseUp;
internal readonly Action<MousePanelEvent>? _onMouseMove;
/// <summary>Called when clicked.</summary>
public Action<MousePanelEvent>? OnClick { init => _onClick = value; }
/// <summary>Called when the pointer enters.</summary>
public Action<MousePanelEvent>? OnMouseEnter { init => _onMouseEnter = value; }
/// <summary>Called when the pointer leaves.</summary>
public Action<MousePanelEvent>? OnMouseLeave { init => _onMouseLeave = value; }
/// <summary>Called when a mouse button is pressed.</summary>
public Action<MousePanelEvent>? OnMouseDown { init => _onMouseDown = value; }
/// <summary>Called when a mouse button is released.</summary>
public Action<MousePanelEvent>? OnMouseUp { init => _onMouseUp = value; }
/// <summary>Called when the pointer moves over it.</summary>
public Action<MousePanelEvent>? OnMouseMove { init => _onMouseMove = value; }
public SvgPanel()
{
Path = null;
Color = null;
Key = null;
_style = StyleList.Empty;
}
public bool Equals(SvgPanel other)
=> Path == other.Path
&& Color == other.Color
&& Key == other.Key
&& StyleList.ContentsEqual(_style, other._style);
public override int GetHashCode()
=> HashCode.Combine(Path, Color, Key);
void IBlob.WriteTo(ref Frame frame)
{
frame.Kind = BlobKind.SvgPanel;
frame.Key = Key;
frame.Content = "";
frame.Style = _style;
frame.Children = null;
frame.Texture = null;
frame.Path = Path;
frame.Color = Color;
frame.Scene = null;
frame.RenderOnce = false;
frame.Paused = false;
frame.Shape = default;
frame.Points = null;
frame.Material = null;
frame.Uniforms = ImmutableArray<UniformValue>.Empty;
frame.Draw = null;
frame.Placeholder = null;
frame.MaxLength = null;
frame.Disabled = false;
frame.Numeric = false;
frame.MinValue = null;
frame.MaxValue = null;
frame.NumberFormat = null;
frame.Multiline = false;
frame.OnChange = null;
frame.OnSubmit = null;
frame.OnFocus = null;
frame.OnBlur = null;
frame.OnCancel = null;
frame.IsControlled = false;
frame.ValueAndInitialTextBothSet = false;
frame.Events.OnClick = _onClick;
frame.Events.OnMouseEnter = _onMouseEnter;
frame.Events.OnMouseLeave = _onMouseLeave;
frame.Events.OnMouseDown = _onMouseDown;
frame.Events.OnMouseUp = _onMouseUp;
frame.Events.OnMouseMove = _onMouseMove;
}
}
/// <summary>A pie wedge or ring segment. Spans from <see cref="StartAngle"/> to <see cref="EndAngle"/>, between <see cref="InnerRadius"/> and <see cref="OuterRadius"/>.</summary>
public readonly partial record struct Sector : IBlob
{
public static BlobKind Kind => BlobKind.Sector;
/// <summary>Angle where the wedge begins, in degrees.</summary>
public float StartAngle { get; init; }
/// <summary>Angle where the wedge ends, in degrees.</summary>
public float EndAngle { get; init; }
/// <summary>Inner edge of the ring, as a fraction (0 to 1) of the frame box. 0 gives a solid wedge.</summary>
public float InnerRadius { get; init; }
/// <summary>Outer edge of the ring, as a fraction (0 to 1) of the frame box.</summary>
public float OuterRadius { get; init; }
/// <summary>Corner rounding, as a fraction (0 to 1) of the outer radius. 0 gives sharp corners.</summary>
public float CornerRadius { get; init; }
/// <summary>Stable identity across rebuilds. Set it when this blob can change position among its siblings, so the reconciler matches it to the same panel.</summary>
public string? Key { get; init; }
internal readonly StyleList _style;
internal readonly Action<MousePanelEvent>? _onClick;
internal readonly Action<MousePanelEvent>? _onMouseEnter;
internal readonly Action<MousePanelEvent>? _onMouseLeave;
internal readonly Action<MousePanelEvent>? _onMouseDown;
internal readonly Action<MousePanelEvent>? _onMouseUp;
internal readonly Action<MousePanelEvent>? _onMouseMove;
/// <summary>Called when clicked.</summary>
public Action<MousePanelEvent>? OnClick { init => _onClick = value; }
/// <summary>Called when the pointer enters.</summary>
public Action<MousePanelEvent>? OnMouseEnter { init => _onMouseEnter = value; }
/// <summary>Called when the pointer leaves.</summary>
public Action<MousePanelEvent>? OnMouseLeave { init => _onMouseLeave = value; }
/// <summary>Called when a mouse button is pressed.</summary>
public Action<MousePanelEvent>? OnMouseDown { init => _onMouseDown = value; }
/// <summary>Called when a mouse button is released.</summary>
public Action<MousePanelEvent>? OnMouseUp { init => _onMouseUp = value; }
/// <summary>Called when the pointer moves over it.</summary>
public Action<MousePanelEvent>? OnMouseMove { init => _onMouseMove = value; }
public Sector()
{
StartAngle = 0f;
EndAngle = 0f;
InnerRadius = 0f;
OuterRadius = 0f;
CornerRadius = 0f;
Key = null;
_style = StyleList.Empty;
}
public bool Equals(Sector other)
=> StartAngle == other.StartAngle
&& EndAngle == other.EndAngle
&& InnerRadius == other.InnerRadius
&& OuterRadius == other.OuterRadius
&& CornerRadius == other.CornerRadius
&& Key == other.Key
&& StyleList.ContentsEqual(_style, other._style);
public override int GetHashCode()
=> HashCode.Combine(StartAngle, EndAngle, InnerRadius, OuterRadius, CornerRadius, Key);
void IBlob.WriteTo(ref Frame frame)
{
frame.Kind = BlobKind.Sector;
frame.Key = Key;
frame.Content = "";
frame.Style = _style;
frame.Children = null;
frame.Texture = null;
frame.Path = null;
frame.Scene = null;
frame.RenderOnce = false;
frame.Paused = false;
frame.Color = null;
frame.Shape = new ShapeParams { A = StartAngle, B = EndAngle, C = InnerRadius, D = OuterRadius, E = CornerRadius };
frame.Points = null;
frame.Material = null;
frame.Uniforms = ImmutableArray<UniformValue>.Empty;
frame.Draw = null;
frame.Placeholder = null;
frame.MaxLength = null;
frame.Disabled = false;
frame.Numeric = false;
frame.MinValue = null;
frame.MaxValue = null;
frame.NumberFormat = null;
frame.Multiline = false;
frame.OnChange = null;
frame.OnSubmit = null;
frame.OnFocus = null;
frame.OnBlur = null;
frame.OnCancel = null;
frame.IsControlled = false;
frame.ValueAndInitialTextBothSet = false;
frame.Events.OnClick = _onClick;
frame.Events.OnMouseEnter = _onMouseEnter;
frame.Events.OnMouseLeave = _onMouseLeave;
frame.Events.OnMouseDown = _onMouseDown;
frame.Events.OnMouseUp = _onMouseUp;
frame.Events.OnMouseMove = _onMouseMove;
}
}
/// <summary>A stroked arc line. Sweeps from <see cref="StartAngle"/> to <see cref="EndAngle"/> at a fixed <see cref="Radius"/>.</summary>
public readonly partial record struct Arc : IBlob
{
public static BlobKind Kind => BlobKind.Arc;
/// <summary>Angle where the arc begins, in degrees.</summary>
public float StartAngle { get; init; }
/// <summary>Angle where the arc ends, in degrees.</summary>
public float EndAngle { get; init; }
/// <summary>Radius of the arc, as a fraction (0 to 1) of the frame box.</summary>
public float Radius { get; init; }
/// <summary>Thickness of the arc stroke.</summary>
public float StrokeWidth { get; init; }
/// <summary>Stable identity across rebuilds. Set it when this blob can change position among its siblings, so the reconciler matches it to the same panel.</summary>
public string? Key { get; init; }
internal readonly StyleList _style;
internal readonly Action<MousePanelEvent>? _onClick;
internal readonly Action<MousePanelEvent>? _onMouseEnter;
internal readonly Action<MousePanelEvent>? _onMouseLeave;
internal readonly Action<MousePanelEvent>? _onMouseDown;
internal readonly Action<MousePanelEvent>? _onMouseUp;
internal readonly Action<MousePanelEvent>? _onMouseMove;
/// <summary>Called when clicked.</summary>
public Action<MousePanelEvent>? OnClick { init => _onClick = value; }
/// <summary>Called when the pointer enters.</summary>
public Action<MousePanelEvent>? OnMouseEnter { init => _onMouseEnter = value; }
/// <summary>Called when the pointer leaves.</summary>
public Action<MousePanelEvent>? OnMouseLeave { init => _onMouseLeave = value; }
/// <summary>Called when a mouse button is pressed.</summary>
public Action<MousePanelEvent>? OnMouseDown { init => _onMouseDown = value; }
/// <summary>Called when a mouse button is released.</summary>
public Action<MousePanelEvent>? OnMouseUp { init => _onMouseUp = value; }
/// <summary>Called when the pointer moves over it.</summary>
public Action<MousePanelEvent>? OnMouseMove { init => _onMouseMove = value; }
public Arc()
{
StartAngle = 0f;
EndAngle = 0f;
Radius = 0f;
StrokeWidth = 0f;
Key = null;
_style = StyleList.Empty;
}
public bool Equals(Arc other)
=> StartAngle == other.StartAngle
&& EndAngle == other.EndAngle
&& Radius == other.Radius
&& StrokeWidth == other.StrokeWidth
&& Key == other.Key
&& StyleList.ContentsEqual(_style, other._style);
public override int GetHashCode()
=> HashCode.Combine(StartAngle, EndAngle, Radius, StrokeWidth, Key);
void IBlob.WriteTo(ref Frame frame)
{
frame.Kind = BlobKind.Arc;
frame.Key = Key;
frame.Content = "";
frame.Style = _style;
frame.Children = null;
frame.Texture = null;
frame.Path = null;
frame.Scene = null;
frame.RenderOnce = false;
frame.Paused = false;
frame.Color = null;
frame.Shape = new ShapeParams { A = StartAngle, B = EndAngle, C = Radius, D = StrokeWidth };
frame.Points = null;
frame.Material = null;
frame.Uniforms = ImmutableArray<UniformValue>.Empty;
frame.Draw = null;
frame.Placeholder = null;
frame.MaxLength = null;
frame.Disabled = false;
frame.Numeric = false;
frame.MinValue = null;
frame.MaxValue = null;
frame.NumberFormat = null;
frame.Multiline = false;
frame.OnChange = null;
frame.OnSubmit = null;
frame.OnFocus = null;
frame.OnBlur = null;
frame.OnCancel = null;
frame.IsControlled = false;
frame.ValueAndInitialTextBothSet = false;
frame.Events.OnClick = _onClick;
frame.Events.OnMouseEnter = _onMouseEnter;
frame.Events.OnMouseLeave = _onMouseLeave;
frame.Events.OnMouseDown = _onMouseDown;
frame.Events.OnMouseUp = _onMouseUp;
frame.Events.OnMouseMove = _onMouseMove;
}
}
// Polygon: arbitrary-vertex filled shape baked into a 512x512 alpha mask. Unit coords in [0,1], treated as immutable (the cache key holds the reference). Click hit-testing uses the bounding rect (engine limitation).
/// <summary>A filled shape defined by arbitrary vertices in unit coordinates.</summary>
public readonly partial record struct Polygon : IBlob
{
public static BlobKind Kind => BlobKind.Polygon;
/// <summary>The vertices, in unit coordinates from 0 to 1 (origin top-left, x right, y down). Treated as immutable: do not mutate the array after assigning it.</summary>
public Vector2[]? Points { get; init; }
/// <summary>Stable identity across rebuilds. Set it when this blob can change position among its siblings, so the reconciler matches it to the same panel.</summary>
public string? Key { get; init; }
internal readonly StyleList _style;
internal readonly Action<MousePanelEvent>? _onClick;
internal readonly Action<MousePanelEvent>? _onMouseEnter;
internal readonly Action<MousePanelEvent>? _onMouseLeave;
internal readonly Action<MousePanelEvent>? _onMouseDown;
internal readonly Action<MousePanelEvent>? _onMouseUp;
internal readonly Action<MousePanelEvent>? _onMouseMove;
/// <summary>Called when clicked.</summary>
public Action<MousePanelEvent>? OnClick { init => _onClick = value; }
/// <summary>Called when the pointer enters.</summary>
public Action<MousePanelEvent>? OnMouseEnter { init => _onMouseEnter = value; }
/// <summary>Called when the pointer leaves.</summary>
public Action<MousePanelEvent>? OnMouseLeave { init => _onMouseLeave = value; }
/// <summary>Called when a mouse button is pressed.</summary>
public Action<MousePanelEvent>? OnMouseDown { init => _onMouseDown = value; }
/// <summary>Called when a mouse button is released.</summary>
public Action<MousePanelEvent>? OnMouseUp { init => _onMouseUp = value; }
/// <summary>Called when the pointer moves over it.</summary>
public Action<MousePanelEvent>? OnMouseMove { init => _onMouseMove = value; }
public Polygon()
{
Points = null;
Key = null;
_style = StyleList.Empty;
}
public bool Equals(Polygon other)
=> ReferenceEquals(Points, other.Points)
&& Key == other.Key
&& StyleList.ContentsEqual(_style, other._style);
public override int GetHashCode()
=> HashCode.Combine(Points, Key);
void IBlob.WriteTo(ref Frame frame)
{
frame.Kind = BlobKind.Polygon;
frame.Key = Key;
frame.Content = "";
frame.Style = _style;
frame.Children = null;
frame.Texture = null;
frame.Path = null;
frame.Scene = null;
frame.RenderOnce = false;
frame.Paused = false;
frame.Color = null;
frame.Shape = default;
frame.Points = Points;
frame.Material = null;
frame.Uniforms = ImmutableArray<UniformValue>.Empty;
frame.Draw = null;
frame.Placeholder = null;
frame.MaxLength = null;
frame.Disabled = false;
frame.Numeric = false;
frame.MinValue = null;
frame.MaxValue = null;
frame.NumberFormat = null;
frame.Multiline = false;
frame.OnChange = null;
frame.OnSubmit = null;
frame.OnFocus = null;
frame.OnBlur = null;
frame.OnCancel = null;
frame.IsControlled = false;
frame.ValueAndInitialTextBothSet = false;
frame.Events.OnClick = _onClick;
frame.Events.OnMouseEnter = _onMouseEnter;
frame.Events.OnMouseLeave = _onMouseLeave;
frame.Events.OnMouseDown = _onMouseDown;
frame.Events.OnMouseUp = _onMouseUp;
frame.Events.OnMouseMove = _onMouseMove;
}
}
// WebPanel: embeds an engine Chromium webview. The Url is the only content surface;
// background-image and cursor are owned by the engine WebPanel itself per frame, so
// the common-only facade keeps both out by construction.
/// <summary>Embeds a web view via the engine's Chromium webview. <see cref="Url"/> is its only content surface.</summary>
public readonly partial record struct WebPanel : IBlob
{
public static BlobKind Kind => BlobKind.WebPanel;
/// <summary>The URL to load in the web view.</summary>
public string? Url { get; init; }
// Maps to engine WebSurface.InBackgroundMode.
/// <summary>When true, throttles the view's script and repaint and pauses its media. Use for offscreen or hidden views.</summary>
public bool Paused { get; init; }
/// <summary>Stable identity across rebuilds. Set it when this blob can change position among its siblings, so the reconciler matches it to the same panel.</summary>
public string? Key { get; init; }
internal readonly StyleList _style;
internal readonly Action<MousePanelEvent>? _onClick;
internal readonly Action<MousePanelEvent>? _onMouseEnter;
internal readonly Action<MousePanelEvent>? _onMouseLeave;
internal readonly Action<MousePanelEvent>? _onMouseDown;
internal readonly Action<MousePanelEvent>? _onMouseUp;
internal readonly Action<MousePanelEvent>? _onMouseMove;
/// <summary>Called when clicked.</summary>
public Action<MousePanelEvent>? OnClick { init => _onClick = value; }
/// <summary>Called when the pointer enters.</summary>
public Action<MousePanelEvent>? OnMouseEnter { init => _onMouseEnter = value; }
/// <summary>Called when the pointer leaves.</summary>
public Action<MousePanelEvent>? OnMouseLeave { init => _onMouseLeave = value; }
/// <summary>Called when a mouse button is pressed.</summary>
public Action<MousePanelEvent>? OnMouseDown { init => _onMouseDown = value; }
/// <summary>Called when a mouse button is released.</summary>
public Action<MousePanelEvent>? OnMouseUp { init => _onMouseUp = value; }
/// <summary>Called when the pointer moves over it.</summary>
public Action<MousePanelEvent>? OnMouseMove { init => _onMouseMove = value; }
public WebPanel()
{
Url = null;
Paused = false;
Key = null;
_style = StyleList.Empty;
}
public bool Equals(WebPanel other)
=> Url == other.Url
&& Paused == other.Paused
&& Key == other.Key
&& StyleList.ContentsEqual(_style, other._style);
public override int GetHashCode()
=> HashCode.Combine(Url, Paused, Key);
void IBlob.WriteTo(ref Frame frame)
{
frame.Kind = BlobKind.WebPanel;
frame.Key = Key;
frame.Content = "";
frame.Style = _style;
frame.Children = null;
frame.Texture = null;
frame.Path = Url;
frame.Scene = null;
frame.RenderOnce = false;
frame.Paused = Paused;
frame.Color = null;
frame.Shape = default;
frame.Points = null;
frame.Material = null;
frame.Uniforms = ImmutableArray<UniformValue>.Empty;
frame.Draw = null;
frame.Placeholder = null;
frame.MaxLength = null;
frame.Disabled = false;
frame.Numeric = false;
frame.MinValue = null;
frame.MaxValue = null;
frame.NumberFormat = null;
frame.Multiline = false;
frame.OnChange = null;
frame.OnSubmit = null;
frame.OnFocus = null;
frame.OnBlur = null;
frame.OnCancel = null;
frame.IsControlled = false;
frame.ValueAndInitialTextBothSet = false;
frame.Events.OnClick = _onClick;
frame.Events.OnMouseEnter = _onMouseEnter;
frame.Events.OnMouseLeave = _onMouseLeave;
frame.Events.OnMouseDown = _onMouseDown;
frame.Events.OnMouseUp = _onMouseUp;
frame.Events.OnMouseMove = _onMouseMove;
}
}
/// <summary>A text input wrapping the engine's TextEntry. Set <see cref="Value"/> for a controlled field driven by your state, or <see cref="InitialText"/> for an uncontrolled one; never both.</summary>
public readonly partial record struct TextEntry : IBlob
{
public static BlobKind Kind => BlobKind.TextEntry;
/// <summary>The field text in controlled mode, re-emitted each render to track your state. Set this or <see cref="InitialText"/>, never both.</summary>
public string? Value { get; init; }
/// <summary>The starting text in uncontrolled mode, set once when the field is created. Set this or <see cref="Value"/>, never both.</summary>
public string? InitialText { get; init; }
/// <summary>Hint text shown when the field is empty.</summary>
public string? Placeholder { get; init; }
/// <summary>Maximum number of characters allowed. Null for no limit.</summary>
public int? MaxLength { get; init; }
/// <summary>When true, the field is read-only and does not accept input.</summary>
public bool Disabled { get; init; }
/// <summary>When true, the field accepts and wraps multiple lines.</summary>
public bool Multiline { get; init; }
/// <summary>When true, the field accepts numeric input only.</summary>
public bool Numeric { get; init; }
/// <summary>Smallest accepted value when <see cref="Numeric"/> is true.</summary>
public float? MinValue { get; init; }
/// <summary>Largest accepted value when <see cref="Numeric"/> is true.</summary>
public float? MaxValue { get; init; }
/// <summary>Format string applied to the value when <see cref="Numeric"/> is true.</summary>
public string? NumberFormat { get; init; }
/// <summary>Stable identity across rebuilds. Set it when this blob can change position among its siblings, so the reconciler matches it to the same panel.</summary>
public string? Key { get; init; }
internal readonly StyleList _style;
internal readonly Action<string>? _onChange;
internal readonly Action<string>? _onSubmit;
internal readonly Action? _onFocus;
internal readonly Action<string>? _onBlur;
internal readonly Action? _onCancel;
internal readonly Action<MousePanelEvent>? _onClick;
internal readonly Action<MousePanelEvent>? _onMouseEnter;
internal readonly Action<MousePanelEvent>? _onMouseLeave;
internal readonly Action<MousePanelEvent>? _onMouseDown;
internal readonly Action<MousePanelEvent>? _onMouseUp;
internal readonly Action<MousePanelEvent>? _onMouseMove;
/// <summary>Called on each edit, with the new text.</summary>
public Action<string>? OnChange { init => _onChange = value; }
/// <summary>Called when the user submits the field, for example by pressing Enter, with the text.</summary>
public Action<string>? OnSubmit { init => _onSubmit = value; }
/// <summary>Called when the field gains focus.</summary>
public Action? OnFocus { init => _onFocus = value; }
/// <summary>Called when the field loses focus, with its final text.</summary>
public Action<string>? OnBlur { init => _onBlur = value; }
/// <summary>Called when the user cancels editing, for example by pressing Escape.</summary>
public Action? OnCancel { init => _onCancel = value; }
/// <summary>Called when clicked.</summary>
public Action<MousePanelEvent>? OnClick { init => _onClick = value; }
/// <summary>Called when the pointer enters.</summary>
public Action<MousePanelEvent>? OnMouseEnter { init => _onMouseEnter = value; }
/// <summary>Called when the pointer leaves.</summary>
public Action<MousePanelEvent>? OnMouseLeave { init => _onMouseLeave = value; }
/// <summary>Called when a mouse button is pressed.</summary>
public Action<MousePanelEvent>? OnMouseDown { init => _onMouseDown = value; }
/// <summary>Called when a mouse button is released.</summary>
public Action<MousePanelEvent>? OnMouseUp { init => _onMouseUp = value; }
/// <summary>Called when the pointer moves over it.</summary>
public Action<MousePanelEvent>? OnMouseMove { init => _onMouseMove = value; }
public TextEntry()
{
Value = null;
InitialText = null;
Placeholder = null;
MaxLength = null;
Disabled = false;
Multiline = false;
Numeric = false;
MinValue = null;
MaxValue = null;
NumberFormat = null;
Key = null;
_style = StyleList.Empty;
}
public bool Equals(TextEntry other)
=> Value == other.Value
&& InitialText == other.InitialText
&& Placeholder == other.Placeholder
&& MaxLength == other.MaxLength
&& Disabled == other.Disabled
&& Multiline == other.Multiline
&& Numeric == other.Numeric
&& MinValue == other.MinValue
&& MaxValue == other.MaxValue
&& NumberFormat == other.NumberFormat
&& Key == other.Key
&& StyleList.ContentsEqual(_style, other._style);
public override int GetHashCode()
=> HashCode.Combine(Value, InitialText, Placeholder, MaxLength, Disabled, Numeric, Multiline, Key);
void IBlob.WriteTo(ref Frame frame)
{
frame.Kind = BlobKind.TextEntry;
frame.Key = Key;
frame.Content = "";
frame.Style = _style;
frame.Children = null;
frame.Texture = null;
// Path slot carries the text value (controlled-or-initial). Reconciler reads
// IsControlled to decide whether to re-emit on Update.
frame.Path = Value ?? InitialText;
frame.IsControlled = Value is not null;
frame.ValueAndInitialTextBothSet = Value is not null && InitialText is not null;
frame.Scene = null;
frame.RenderOnce = false;
frame.Paused = false;
frame.Color = null;
frame.Shape = default;
frame.Points = null;
frame.Material = null;
frame.Uniforms = ImmutableArray<UniformValue>.Empty;
frame.Draw = null;
frame.Placeholder = Placeholder;
frame.MaxLength = MaxLength;
frame.Disabled = Disabled;
frame.Numeric = Numeric;
frame.MinValue = MinValue;
frame.MaxValue = MaxValue;
frame.NumberFormat = NumberFormat;
frame.Multiline = Multiline;
frame.OnChange = _onChange;
frame.OnSubmit = _onSubmit;
frame.OnFocus = _onFocus;
frame.OnBlur = _onBlur;
frame.OnCancel = _onCancel;
frame.Events.OnClick = _onClick;
frame.Events.OnMouseEnter = _onMouseEnter;
frame.Events.OnMouseLeave = _onMouseLeave;
frame.Events.OnMouseDown = _onMouseDown;
frame.Events.OnMouseUp = _onMouseUp;
frame.Events.OnMouseMove = _onMouseMove;
}
}