Skip to content

Commit c66ff8a

Browse files
andreakarashoclaude
andcommitted
Add ClipContent property to clip children to element bounds
Adds LayoutConfig.ClipContent which emits scissor commands to clip overflowing children without requiring a scroll container. Exposed via LayoutStyle for BeginHorizontal/BeginVertical, and applied to docked window containers and the viewport overlay box. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e4d0217 commit c66ff8a

4 files changed

Lines changed: 50 additions & 15 deletions

File tree

src/Clay.GameEditor/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,8 @@ void RenderViewportContent()
620620
{
621621
Padding = Padding.All(20),
622622
BackgroundColor = Color.Rgba(0, 0, 0, 80),
623-
CornerRadius = CornerRadius.All(8)
623+
CornerRadius = CornerRadius.All(8),
624+
ClipContent = true
624625
});
625626

626627
if (isPlaying)

src/Clay/ClayUI.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1679,6 +1679,7 @@ public static void BeginHorizontal(ushort gap = 8, ChildAlignment alignment = de
16791679
ChildGap = gap,
16801680
ChildAlignment = alignment,
16811681
Padding = hasStyle ? s.Padding : default,
1682+
ClipContent = hasStyle && s.ClipContent,
16821683
Sizing = new Sizing
16831684
{
16841685
Width = hasSizingW ? s.Sizing.Width : SizingAxis.Grow(),
@@ -1792,6 +1793,7 @@ public static void BeginVertical(ushort gap = 8, ChildAlignment alignment = defa
17921793
ChildGap = gap,
17931794
ChildAlignment = alignment,
17941795
Padding = hasStyle ? s.Padding : default,
1796+
ClipContent = hasStyle && s.ClipContent,
17951797
Sizing = new Sizing
17961798
{
17971799
Width = hasSizingW ? s.Sizing.Width : SizingAxis.Grow(),
@@ -2139,7 +2141,8 @@ public static bool BeginWindow(string title, ref bool open, WindowStyle? style =
21392141
Layout = new LayoutConfig
21402142
{
21412143
Direction = LayoutDirection.TopToBottom,
2142-
Sizing = Sizing.FixedSize(leafData.BoundingBox.Width, leafData.BoundingBox.Height)
2144+
Sizing = Sizing.FixedSize(leafData.BoundingBox.Width, leafData.BoundingBox.Height),
2145+
ClipContent = true
21432146
},
21442147
Floating = new FloatingConfig
21452148
{
@@ -6371,6 +6374,10 @@ public LayoutStyle() { }
63716374
public BorderConfig Border { get; set; } = default;
63726375
public Padding Padding { get; set; } = default;
63736376
public Sizing Sizing { get; set; } = default;
6377+
/// <summary>
6378+
/// When true, children are clipped to this element's bounding box.
6379+
/// </summary>
6380+
public bool ClipContent { get; set; } = false;
63746381
}
63756382

63766383
public struct ScrollAreaStyle

src/Clay/Core/ClayContext.cs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -327,13 +327,20 @@ public void ConfigureOpenElement(ElementDeclaration declaration)
327327
GenerateIdForAnonymousElement(ref openLayoutElement);
328328
}
329329

330+
// ClipContent adds to clip stack (without scroll behavior)
331+
if (declaration.Layout.ClipContent)
332+
{
333+
OpenClipElementStack.Add((int)openLayoutElement.Id);
334+
}
335+
330336
// Process scroll config
331337
if (declaration.Scroll.IsScrollable)
332338
{
333339
int scrollIndex = ScrollElementConfigs.Add(declaration.Scroll);
334340
ElementConfigs.Add(new ElementConfig { Type = ElementConfigType.Scroll, ConfigIndex = scrollIndex });
335341
openLayoutElement.ElementConfigsLength++;
336-
OpenClipElementStack.Add((int)openLayoutElement.Id);
342+
if (!declaration.Layout.ClipContent) // Avoid double-push
343+
OpenClipElementStack.Add((int)openLayoutElement.Id);
337344

338345
// Find or create scroll container data
339346
bool found = false;
@@ -384,6 +391,12 @@ public void CloseElement()
384391
bool elementHasScrollHorizontal = false;
385392
bool elementHasScrollVertical = false;
386393

394+
// Pop clip stack for ClipContent elements
395+
if (layoutConfig.ClipContent)
396+
{
397+
OpenClipElementStack.Length--;
398+
}
399+
387400
// Check for scroll config
388401
for (int i = 0; i < openLayoutElement.ElementConfigsLength; i++)
389402
{
@@ -393,7 +406,8 @@ public void CloseElement()
393406
ref var scrollConfig = ref ScrollElementConfigs[config.ConfigIndex];
394407
elementHasScrollHorizontal = scrollConfig.Horizontal;
395408
elementHasScrollVertical = scrollConfig.Vertical;
396-
OpenClipElementStack.Length--;
409+
if (!layoutConfig.ClipContent) // Avoid double-pop
410+
OpenClipElementStack.Length--;
397411
break;
398412
}
399413
}
@@ -1134,6 +1148,8 @@ private void GenerateRenderCommandsRecursive(int elementIndex, short zIndex, Bou
11341148
// Find configs
11351149
int sharedIndex = FindConfigIndex(ref element, ElementConfigType.Shared);
11361150
int scrollIndex = FindConfigIndex(ref element, ElementConfigType.Scroll);
1151+
ref var layoutConfig = ref LayoutConfigs[element.LayoutConfigIndex];
1152+
bool clipContent = layoutConfig.ClipContent;
11371153

11381154
SharedElementConfig sharedConfig = sharedIndex >= 0 ? SharedElementConfigs[sharedIndex] : default;
11391155

@@ -1154,28 +1170,34 @@ private void GenerateRenderCommandsRecursive(int elementIndex, short zIndex, Bou
11541170
});
11551171
}
11561172

1157-
// Scissor start
1158-
if (scrollIndex >= 0)
1173+
// Scissor start (for scroll containers or ClipContent elements)
1174+
bool needsScissor = scrollIndex >= 0 || clipContent;
1175+
if (needsScissor)
11591176
{
1160-
ref var scrollConfig = ref ScrollElementConfigs[scrollIndex];
1177+
ScrollRenderData scrollData = default;
1178+
if (scrollIndex >= 0)
1179+
{
1180+
ref var scrollConfig = ref ScrollElementConfigs[scrollIndex];
1181+
scrollData = new ScrollRenderData
1182+
{
1183+
Horizontal = scrollConfig.Horizontal,
1184+
Vertical = scrollConfig.Vertical
1185+
};
1186+
}
11611187
RenderCommands.Add(new RenderCommand
11621188
{
11631189
BoundingBox = boundingBox,
11641190
CommandType = RenderCommandType.ScissorStart,
11651191
Id = element.Id,
11661192
ZIndex = zIndex,
1167-
Scroll = new ScrollRenderData
1168-
{
1169-
Horizontal = scrollConfig.Horizontal,
1170-
Vertical = scrollConfig.Vertical
1171-
}
1193+
Scroll = scrollData
11721194
});
11731195
}
11741196

1175-
// If this element is a scroll container, its bounding box clips children
1197+
// If this element clips children, its bounding box clips children
11761198
var childClip = clipBounds;
11771199
bool childHasClip = hasClip;
1178-
if (scrollIndex >= 0)
1200+
if (needsScissor)
11791201
{
11801202
childClip = boundingBox;
11811203
childHasClip = true;
@@ -1238,7 +1260,7 @@ private void GenerateRenderCommandsRecursive(int elementIndex, short zIndex, Bou
12381260
}
12391261

12401262
// Scissor end
1241-
if (scrollIndex >= 0)
1263+
if (needsScissor)
12421264
{
12431265
RenderCommands.Add(new RenderCommand
12441266
{

src/Clay/Layout/LayoutConfig.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ public struct LayoutConfig
3434
/// </summary>
3535
public LayoutDirection Direction;
3636

37+
/// <summary>
38+
/// When true, children are clipped to this element's bounding box.
39+
/// </summary>
40+
public bool ClipContent;
41+
3742
public static readonly LayoutConfig Default = new()
3843
{
3944
Sizing = Sizing.Default,

0 commit comments

Comments
 (0)