diff --git a/src/Clay.Test/ConfigTests.cs b/src/Clay.Test/ConfigTests.cs
index 30eb3e2..1663fb3 100644
--- a/src/Clay.Test/ConfigTests.cs
+++ b/src/Clay.Test/ConfigTests.cs
@@ -206,21 +206,21 @@ public void Container_SetsLayout()
{
var layout = LayoutConfig.FillRow(gap: 8);
var decl = ElementDeclaration.Container(layout);
- Assert.Equal(8, decl.Layout.ChildGap);
+ Assert.Equal(8, decl.Layout!.Value.ChildGap);
}
[Fact]
public void Box_SetsLayoutAndColor()
{
var decl = ElementDeclaration.Box(LayoutConfig.FillRow(), Color.Red);
- Assert.Equal(Color.Red.R, decl.BackgroundColor.R);
+ Assert.Equal(Color.Red.R, decl.BackgroundColor!.Value.R);
}
[Fact]
public void RoundedBox_SetsCornerRadius()
{
var decl = ElementDeclaration.RoundedBox(LayoutConfig.FillRow(), Color.Red, 8f);
- Assert.True(decl.CornerRadius.HasRadius);
+ Assert.True(decl.CornerRadius!.Value.HasRadius);
}
}
diff --git a/src/Clay.Test/OptionalElementDeclarationTests.cs b/src/Clay.Test/OptionalElementDeclarationTests.cs
new file mode 100644
index 0000000..2ea9709
--- /dev/null
+++ b/src/Clay.Test/OptionalElementDeclarationTests.cs
@@ -0,0 +1,401 @@
+using Clay;
+
+namespace Clay.Test;
+
+///
+/// Tests for the nullable ElementDeclaration field contract.
+/// Declaring a config field (even default) opts the element into that config.
+/// Leaving the field null means the config is not emitted.
+/// Id remains required.
+///
+public class OptionalElementDeclarationTests : IDisposable
+{
+ private readonly ClayFixture _fixture;
+
+ public OptionalElementDeclarationTests() => _fixture = new ClayFixture();
+
+ public void Dispose() => _fixture.Dispose();
+
+ private static int CountCommands(ReadOnlySpan commands, RenderCommandType type)
+ {
+ int n = 0;
+ foreach (var c in commands)
+ if (c.CommandType == type) n++;
+ return n;
+ }
+
+ // ============ Null-by-default contract ============
+
+ [Fact]
+ public void OnlyId_NoLayout_DoesNotCrash()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration { Id = ClayApi.Id("only-id") })) { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(0, CountCommands(commands, RenderCommandType.Rectangle));
+ Assert.Equal(0, CountCommands(commands, RenderCommandType.Border));
+ Assert.Equal(0, CountCommands(commands, RenderCommandType.Shadow));
+ Assert.Equal(0, CountCommands(commands, RenderCommandType.Custom));
+ Assert.Equal(0, CountCommands(commands, RenderCommandType.Image));
+ }
+
+ [Fact]
+ public void NullBackgroundColor_DoesNotEmitRectangle()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("no-bg"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) }
+ }))
+ { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(0, CountCommands(commands, RenderCommandType.Rectangle));
+ }
+
+ [Fact]
+ public void NullScroll_DoesNotRegisterScrollContainer()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("no-scroll"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(100, 100) }
+ }))
+ { }
+ ClayApi.EndLayout();
+ Assert.Equal(0, ClayApi.GetScrollContainerCount());
+ }
+
+ // ============ Declaration-is-enough contract ============
+
+ [Fact]
+ public void EmptyCustomConfig_EmitsCustomCommand()
+ {
+ // Regression for sentinel removal: assigning a default-constructed CustomConfig
+ // (CustomData == null) MUST still emit a Custom render command.
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("custom-empty"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ Custom = new CustomConfig()
+ }))
+ { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(1, CountCommands(commands, RenderCommandType.Custom));
+ }
+
+ [Fact]
+ public void EmptyShadowConfig_EmitsShadowCommand()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("shadow-empty"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ Shadow = new ShadowConfig()
+ }))
+ { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(1, CountCommands(commands, RenderCommandType.Shadow));
+ }
+
+ [Fact]
+ public void EmptyBorderConfig_DoesNotCrash()
+ {
+ // BorderConfig with zero width is registered as a config (sentinel removed)
+ // but the visual border command is skipped downstream when Width.HasBorder is false.
+ // Confirms the declaration is accepted without throwing.
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("border-empty"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ Border = new BorderConfig()
+ }))
+ { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(0, CountCommands(commands, RenderCommandType.Border));
+ }
+
+ [Fact]
+ public void EmptyScrollConfig_RegistersScrollContainer()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("scroll-empty"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(100, 100) },
+ Scroll = new ScrollConfig()
+ }))
+ { }
+ ClayApi.EndLayout();
+ Assert.True(ClayApi.GetScrollContainerCount() > 0);
+ }
+
+ [Fact]
+ public void AssignedBackgroundColor_EmitsRectangle()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("bg"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ BackgroundColor = Color.Red
+ }))
+ { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(1, CountCommands(commands, RenderCommandType.Rectangle));
+ }
+
+ [Fact]
+ public void TransparentBackgroundColor_DoesNotCrash()
+ {
+ // Declaration accepts a transparent color; downstream rectangle emission
+ // still skips fully-invisible fills as an optimization.
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("bg-transparent"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ BackgroundColor = new Color(0, 0, 0, 0)
+ }))
+ { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(0, CountCommands(commands, RenderCommandType.Rectangle));
+ }
+
+ // ============ Regression: existing behavior preserved ============
+
+ [Fact]
+ public void Regression_BackgroundColor_StillEmitsRectangle()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("reg-bg"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(100, 100) },
+ BackgroundColor = Color.Blue
+ }))
+ { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(1, CountCommands(commands, RenderCommandType.Rectangle));
+ }
+
+ [Fact]
+ public void Regression_ImageConfig_WithData_EmitsImage()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("reg-image"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(64, 64) },
+ Image = ImageConfig.Create(new object(), 64, 64)
+ }))
+ { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(1, CountCommands(commands, RenderCommandType.Image));
+ }
+
+ [Fact]
+ public void Regression_ShadowDrop_EmitsShadow()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("reg-shadow"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ Shadow = ShadowConfig.Drop(2, 2, 4, Color.Black)
+ }))
+ { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(1, CountCommands(commands, RenderCommandType.Shadow));
+ }
+
+ [Fact]
+ public void Regression_BorderUniform_EmitsBorder()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("reg-border"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ Border = BorderConfig.Uniform(2, Color.White)
+ }))
+ { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(1, CountCommands(commands, RenderCommandType.Border));
+ }
+
+ [Fact]
+ public void Regression_ScrollVertical_RegistersScrollContainer()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("reg-scroll"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(100, 100) },
+ Scroll = ScrollConfig.VerticalScroll
+ }))
+ {
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(100, 500) },
+ BackgroundColor = Color.Red
+ }))
+ { }
+ }
+ ClayApi.EndLayout();
+ Assert.True(ClayApi.GetScrollContainerCount() > 0);
+ }
+
+ [Fact]
+ public void Regression_ContainerFactory_StillBuilds()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(ElementDeclaration.Container(LayoutConfig.FillRow(gap: 4)))) { }
+ ClayApi.EndLayout();
+ Assert.True(ClayApi.GetElementCount() > 0);
+ }
+
+ [Fact]
+ public void Regression_BoxFactory_EmitsRectangle()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(ElementDeclaration.Box(
+ new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ Color.Green))) { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(1, CountCommands(commands, RenderCommandType.Rectangle));
+ }
+
+ [Fact]
+ public void Regression_RoundedBoxFactory_BuildsAndRenders()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(ElementDeclaration.RoundedBox(
+ new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ Color.Green,
+ 8f))) { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(1, CountCommands(commands, RenderCommandType.Rectangle));
+ }
+
+ [Fact]
+ public void Regression_FloatingConfig_RegistersTreeRoot()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("float-parent"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(200, 200) }
+ }))
+ {
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("float-child"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ Floating = new FloatingConfig { AttachTo = FloatingAttachTo.Parent }
+ }))
+ { }
+ }
+ ClayApi.EndLayout();
+ // Root container + the floating element each get a tree root entry.
+ Assert.True(ClayApi.GetTreeRootCount() >= 2);
+ }
+
+ [Fact]
+ public void NullFloating_DoesNotAddTreeRoot()
+ {
+ ClayApi.BeginLayout();
+ int before = ClayApi.GetTreeRootCount();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("no-float"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) }
+ }))
+ { }
+ ClayApi.EndLayout();
+ // Only the root container's tree root should exist (added during BeginLayout).
+ Assert.Equal(1, ClayApi.GetTreeRootCount() - before + 1);
+ }
+
+ [Fact]
+ public void Regression_CornerRadius_AppliesToRectangle()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("rad"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ BackgroundColor = Color.Red,
+ CornerRadius = CornerRadius.All(6f)
+ }))
+ { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(1, CountCommands(commands, RenderCommandType.Rectangle));
+ }
+
+ [Fact]
+ public void UserData_AssignedOrNull_DoesNotCrash()
+ {
+ // UserData is now nint? — both null and an assigned value must be accepted by the layout pipeline.
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("ud-set"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ BackgroundColor = Color.Red,
+ UserData = (nint)0xABCD
+ }))
+ { }
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("ud-null"),
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ BackgroundColor = Color.Red
+ }))
+ { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(2, CountCommands(commands, RenderCommandType.Rectangle));
+ }
+
+ // ============ ClipContent / Scroll clip stack ============
+
+ [Fact]
+ public void NullLayout_DoesNotPushClipStack()
+ {
+ // Was previously checking layout.ClipContent — must tolerate null Layout.
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration { Id = ClayApi.Id("no-layout-clip") })) { }
+ var commands = ClayApi.EndLayout();
+ Assert.Equal(0, CountCommands(commands, RenderCommandType.ScissorStart));
+ }
+
+ [Fact]
+ public void Regression_ClipContent_EmitsScissor()
+ {
+ ClayApi.BeginLayout();
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Id = ClayApi.Id("clip"),
+ Layout = new LayoutConfig
+ {
+ Sizing = Sizing.FixedSize(100, 100),
+ ClipContent = true
+ }
+ }))
+ {
+ using (ClayApi.Element(new ElementDeclaration
+ {
+ Layout = new LayoutConfig { Sizing = Sizing.FixedSize(50, 50) },
+ BackgroundColor = Color.Red
+ }))
+ { }
+ }
+ var commands = ClayApi.EndLayout();
+ Assert.True(CountCommands(commands, RenderCommandType.ScissorStart) >= 1);
+ Assert.True(CountCommands(commands, RenderCommandType.ScissorEnd) >= 1);
+ }
+}
diff --git a/src/Clay/ClayUI.cs b/src/Clay/ClayUI.cs
index 0cbf3e7..61afcc3 100644
--- a/src/Clay/ClayUI.cs
+++ b/src/Clay/ClayUI.cs
@@ -1039,7 +1039,7 @@ public static void Image(object imageData, float width, float height, ImageStyle
},
Image = ImageConfig.Create(imageData, width, height),
CornerRadius = s.CornerRadius,
- Border = s.Border
+ Border = s.Border.HasBorder ? s.Border : null
})) { }
}
@@ -1711,8 +1711,8 @@ public static void BeginHorizontal(ushort gap = 8, ChildAlignment alignment = de
},
BackgroundColor = hasStyle ? s.BackgroundColor : default,
CornerRadius = hasStyle ? s.CornerRadius : default,
- Border = hasStyle ? s.Border : default,
- Shadow = hasStyle ? s.Shadow : default
+ Border = hasStyle && s.Border.HasBorder ? s.Border : null,
+ Shadow = hasStyle && s.Shadow.HasShadow ? s.Shadow : null
});
_context.LayoutDepth++;
@@ -1756,8 +1756,8 @@ public static void BeginHorizontal(ushort gap = 8, ChildAlignment alignment = de
},
BackgroundColor = hasStyle ? s.BackgroundColor : default,
CornerRadius = hasStyle ? s.CornerRadius : default,
- Border = hasStyle ? s.Border : default,
- Shadow = hasStyle ? s.Shadow : default
+ Border = hasStyle && s.Border.HasBorder ? s.Border : null,
+ Shadow = hasStyle && s.Shadow.HasShadow ? s.Shadow : null
});
_context.LayoutDepth++;
}
@@ -1827,8 +1827,8 @@ public static void BeginVertical(ushort gap = 8, ChildAlignment alignment = defa
},
BackgroundColor = hasStyle ? s.BackgroundColor : default,
CornerRadius = hasStyle ? s.CornerRadius : default,
- Border = hasStyle ? s.Border : default,
- Shadow = hasStyle ? s.Shadow : default
+ Border = hasStyle && s.Border.HasBorder ? s.Border : null,
+ Shadow = hasStyle && s.Shadow.HasShadow ? s.Shadow : null
});
_context.LayoutDepth++;
@@ -1872,8 +1872,8 @@ public static void BeginVertical(ushort gap = 8, ChildAlignment alignment = defa
},
BackgroundColor = hasStyle ? s.BackgroundColor : default,
CornerRadius = hasStyle ? s.CornerRadius : default,
- Border = hasStyle ? s.Border : default,
- Shadow = hasStyle ? s.Shadow : default
+ Border = hasStyle && s.Border.HasBorder ? s.Border : null,
+ Shadow = hasStyle && s.Shadow.HasShadow ? s.Shadow : null
});
_context.LayoutDepth++;
}
@@ -1948,7 +1948,7 @@ public static void BeginPanel(string title, PanelStyle? style = null, bool scrol
CornerRadius = sk.Background.HasImage ? CornerRadius.Zero : s.CornerRadius,
Border = sk.Background.HasImage ? default : s.Border,
Image = sk.Background.HasImage ? SkinToImageConfig(sk.Background) : default,
- Shadow = s.Shadow
+ Shadow = s.Shadow.HasShadow ? s.Shadow : null
});
_context.PanelDepth++;
@@ -2042,7 +2042,7 @@ public static void BeginPanel(string title, PanelStyle? style = null, bool scrol
CornerRadius = sk.Background.HasImage ? CornerRadius.Zero : s.CornerRadius,
Border = sk.Background.HasImage ? default : s.Border,
Image = sk.Background.HasImage ? SkinToImageConfig(sk.Background) : default,
- Shadow = s.Shadow
+ Shadow = s.Shadow.HasShadow ? s.Shadow : null
});
_context.PanelDepth++;
@@ -2447,7 +2447,7 @@ public static bool BeginWindow(string title, ref bool open, WindowStyle? style =
CornerRadius = sk.Body.HasImage ? CornerRadius.Zero : s.CornerRadius,
Border = sk.Body.HasImage ? default : s.Border,
Image = sk.Body.HasImage ? SkinToImageConfig(sk.Body) : default,
- Shadow = s.Shadow
+ Shadow = s.Shadow.HasShadow ? s.Shadow : null
});
// Title bar
@@ -3638,7 +3638,7 @@ public static void Tooltip(string text, TooltipStyle? style = null)
},
BackgroundColor = s.BackgroundColor,
CornerRadius = s.CornerRadius,
- Border = s.Border,
+ Border = s.Border.HasBorder ? s.Border : null,
Floating = new FloatingConfig
{
AttachTo = FloatingAttachTo.Root,
@@ -3729,7 +3729,7 @@ public static bool BeginTooltip(TooltipStyle? style = null)
},
BackgroundColor = s.BackgroundColor,
CornerRadius = s.CornerRadius,
- Border = s.Border,
+ Border = s.Border.HasBorder ? s.Border : null,
Floating = new FloatingConfig
{
AttachTo = FloatingAttachTo.Root,
@@ -3909,7 +3909,7 @@ public static bool BeginPopup(string id, PopupStyle? style = null)
},
BackgroundColor = s.BackgroundColor,
CornerRadius = s.CornerRadius,
- Border = s.Border,
+ Border = s.Border.HasBorder ? s.Border : null,
Floating = new FloatingConfig
{
AttachTo = FloatingAttachTo.Root,
@@ -4473,7 +4473,7 @@ public static bool BeginPopupModal(string id, ModalStyle? style = null)
},
BackgroundColor = s.BackgroundColor,
CornerRadius = s.CornerRadius,
- Border = s.Border,
+ Border = s.Border.HasBorder ? s.Border : null,
Floating = new FloatingConfig
{
AttachTo = FloatingAttachTo.Root,
@@ -5034,7 +5034,7 @@ public static void BeginScrollArea(string id, float? maxHeight = null, bool hori
},
BackgroundColor = s.BackgroundColor,
CornerRadius = s.CornerRadius,
- Shadow = s.Shadow
+ Shadow = s.Shadow.HasShadow ? s.Shadow : null
});
_context.LayoutDepth++;
@@ -5083,7 +5083,7 @@ public static void BeginScrollArea(string id, float? maxHeight = null, bool hori
},
BackgroundColor = s.BackgroundColor,
CornerRadius = s.CornerRadius,
- Shadow = s.Shadow
+ Shadow = s.Shadow.HasShadow ? s.Shadow : null
});
_context.LayoutDepth++;
@@ -5210,7 +5210,7 @@ public static void BeginListBox(string label, float maxHeight = 150, ListBoxStyl
},
BackgroundColor = s.BackgroundColor,
CornerRadius = s.CornerRadius,
- Border = s.Border
+ Border = s.Border.HasBorder ? s.Border : null
});
_context.LayoutDepth++;
_context.LayoutScrollInfo.Push(new ClayUIContext.ScrollWrapperInfo(scrollId, IsVertical: true, HasWrapper: true));
@@ -5365,7 +5365,7 @@ public static bool Combo(string label, ref int selectedIndex, string[] options,
},
BackgroundColor = isHovered ? s.HoverColor : s.BackgroundColor,
CornerRadius = s.CornerRadius,
- Border = s.Border
+ Border = s.Border.HasBorder ? s.Border : null
}))
{
// Selected text
diff --git a/src/Clay/Core/ClayContext.cs b/src/Clay/Core/ClayContext.cs
index 27d1a7e..ffe1d63 100644
--- a/src/Clay/Core/ClayContext.cs
+++ b/src/Clay/Core/ClayContext.cs
@@ -251,13 +251,14 @@ public void ConfigureOpenElement(ElementDeclaration declaration)
ref var openLayoutElement = ref GetOpenLayoutElement();
- // Store layout config
- int layoutConfigIndex = LayoutConfigs.Add(declaration.Layout);
+ // Store layout config (default when not provided)
+ var layout = declaration.Layout ?? default;
+ int layoutConfigIndex = LayoutConfigs.Add(layout);
openLayoutElement.LayoutConfigIndex = layoutConfigIndex;
// Validate percent sizing
- if ((declaration.Layout.Sizing.Width.Type == SizingType.Percent && declaration.Layout.Sizing.Width.Percent > 1) ||
- (declaration.Layout.Sizing.Height.Type == SizingType.Percent && declaration.Layout.Sizing.Height.Percent > 1))
+ if ((layout.Sizing.Width.Type == SizingType.Percent && layout.Sizing.Width.Percent > 1) ||
+ (layout.Sizing.Height.Type == SizingType.Percent && layout.Sizing.Height.Percent > 1))
{
OnError?.Invoke(new ErrorData
{
@@ -270,30 +271,30 @@ public void ConfigureOpenElement(ElementDeclaration declaration)
openLayoutElement.ElementConfigsStart = ElementConfigs.Length;
// Process shared config (background color, corner radius, user data)
- if (declaration.BackgroundColor.A > 0 || declaration.CornerRadius.HasRadius || declaration.UserData != 0)
+ if (declaration.BackgroundColor is not null || declaration.CornerRadius is not null || declaration.UserData is not null)
{
int sharedIndex = SharedElementConfigs.Add(new SharedElementConfig
{
- BackgroundColor = declaration.BackgroundColor,
- CornerRadius = declaration.CornerRadius,
- UserData = declaration.UserData
+ BackgroundColor = declaration.BackgroundColor ?? default,
+ CornerRadius = declaration.CornerRadius ?? default,
+ UserData = declaration.UserData ?? 0
});
ElementConfigs.Add(new ElementConfig { Type = ElementConfigType.Shared, ConfigIndex = sharedIndex });
openLayoutElement.ElementConfigsLength++;
}
// Process image config
- if (declaration.Image.HasImage)
+ if (declaration.Image is not null)
{
- int imageIndex = ImageElementConfigs.Add(declaration.Image);
+ int imageIndex = ImageElementConfigs.Add(declaration.Image.Value);
ElementConfigs.Add(new ElementConfig { Type = ElementConfigType.Image, ConfigIndex = imageIndex });
openLayoutElement.ElementConfigsLength++;
}
// Process floating config
- if (declaration.Floating.IsFloating)
+ if (declaration.Floating is not null)
{
- var floatingConfig = declaration.Floating;
+ var floatingConfig = declaration.Floating.Value;
if (OpenLayoutElementStack.Length >= 2)
{
@@ -335,9 +336,9 @@ public void ConfigureOpenElement(ElementDeclaration declaration)
}
// Process custom config
- if (declaration.Custom.HasCustomData)
+ if (declaration.Custom is not null)
{
- int customIndex = CustomElementConfigs.Add(declaration.Custom);
+ int customIndex = CustomElementConfigs.Add(declaration.Custom.Value);
ElementConfigs.Add(new ElementConfig { Type = ElementConfigType.Custom, ConfigIndex = customIndex });
openLayoutElement.ElementConfigsLength++;
}
@@ -353,15 +354,15 @@ public void ConfigureOpenElement(ElementDeclaration declaration)
}
// Push to clip stack if this element clips children (one entry per element)
- if (declaration.Layout.ClipContent || declaration.Scroll.IsScrollable)
+ if (layout.ClipContent || declaration.Scroll is not null)
{
OpenClipElementStack.Add((int)openLayoutElement.Id);
}
// Process scroll config
- if (declaration.Scroll.IsScrollable)
+ if (declaration.Scroll is not null)
{
- int scrollIndex = ScrollElementConfigs.Add(declaration.Scroll);
+ int scrollIndex = ScrollElementConfigs.Add(declaration.Scroll.Value);
ElementConfigs.Add(new ElementConfig { Type = ElementConfigType.Scroll, ConfigIndex = scrollIndex });
openLayoutElement.ElementConfigsLength++;
@@ -392,17 +393,17 @@ public void ConfigureOpenElement(ElementDeclaration declaration)
}
// Process border config
- if (declaration.Border.HasBorder)
+ if (declaration.Border is not null)
{
- int borderIndex = BorderElementConfigs.Add(declaration.Border);
+ int borderIndex = BorderElementConfigs.Add(declaration.Border.Value);
ElementConfigs.Add(new ElementConfig { Type = ElementConfigType.Border, ConfigIndex = borderIndex });
openLayoutElement.ElementConfigsLength++;
}
// Process shadow config
- if (declaration.Shadow.HasShadow)
+ if (declaration.Shadow is not null)
{
- int shadowIndex = ShadowElementConfigs.Add(declaration.Shadow);
+ int shadowIndex = ShadowElementConfigs.Add(declaration.Shadow.Value);
ElementConfigs.Add(new ElementConfig { Type = ElementConfigType.Shadow, ConfigIndex = shadowIndex });
openLayoutElement.ElementConfigsLength++;
}
diff --git a/src/Clay/Elements/ElementDeclaration.cs b/src/Clay/Elements/ElementDeclaration.cs
index 33997fc..7aceb2b 100644
--- a/src/Clay/Elements/ElementDeclaration.cs
+++ b/src/Clay/Elements/ElementDeclaration.cs
@@ -9,59 +9,59 @@ namespace Clay;
public struct ElementDeclaration
{
///
- /// Unique identifier for this element.
+ /// Unique identifier for this element. Required.
///
public ElementId Id;
///
/// Layout configuration (sizing, padding, direction, etc.).
///
- public LayoutConfig Layout;
+ public LayoutConfig? Layout;
///
/// Background color of the element.
///
- public Color BackgroundColor;
+ public Color? BackgroundColor;
///
/// Corner radius for rounded corners.
///
- public CornerRadius CornerRadius;
+ public CornerRadius? CornerRadius;
///
/// Image configuration (if this is an image element).
///
- public ImageConfig Image;
+ public ImageConfig? Image;
///
/// Floating configuration (for overlay/popup elements).
///
- public FloatingConfig Floating;
+ public FloatingConfig? Floating;
///
/// Custom element configuration.
///
- public CustomConfig Custom;
+ public CustomConfig? Custom;
///
/// Scroll container configuration.
///
- public ScrollConfig Scroll;
+ public ScrollConfig? Scroll;
///
/// Border configuration.
///
- public BorderConfig Border;
+ public BorderConfig? Border;
///
/// Shadow configuration.
///
- public ShadowConfig Shadow;
+ public ShadowConfig? Shadow;
///
/// User-defined data (passed through to render commands).
///
- public nint UserData;
+ public nint? UserData;
///
/// Creates a basic container element.
@@ -83,7 +83,7 @@ public static ElementDeclaration RoundedBox(LayoutConfig layout, Color backgroun
{
Layout = layout,
BackgroundColor = backgroundColor,
- CornerRadius = CornerRadius.All(cornerRadius)
+ CornerRadius = global::Clay.CornerRadius.All(cornerRadius)
};
}