Skip to content

Commit 2a9ef97

Browse files
andreakarashoclaude
andcommitted
Auto-add vertical scrollbar in BeginScrollArea/EndScrollArea
Change BeginScrollArea from ElementScope/using pattern to Begin/End pattern that automatically includes a vertical scrollbar, matching how BeginVertical(scroll: true) and ListBox already work. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f8e0598 commit 2a9ef97

3 files changed

Lines changed: 66 additions & 36 deletions

File tree

src/Clay.Example/Program.cs

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -655,27 +655,15 @@ void PageListBoxAndCombo()
655655

656656
void PageScrollAreas()
657657
{
658-
ClayUI.Label("BeginScrollArea creates a scrollable region with a max height.");
659-
ClayUI.Label("Add a VerticalScrollbar sibling for a visible scroll indicator:");
658+
ClayUI.Label("BeginScrollArea creates a scrollable region with an automatic scrollbar:");
660659
ClayUI.Space(8);
661660

662-
var scrollId = ClayUI.StableId($"ScrollArea_DemoScroll");
661+
ClayUI.BeginScrollArea("DemoScroll", maxHeight: 150);
663662

664-
// Horizontal wrapper: scroll area + scrollbar
665-
ClayUI.BeginHorizontal();
663+
for (int i = 0; i < 20; i++)
664+
ClayUI.Label($" Scrollable item {i + 1}");
666665

667-
using (ClayUI.BeginScrollArea("DemoScroll", maxHeight: 150))
668-
{
669-
for (int i = 0; i < 20; i++)
670-
ClayUI.Label($" Scrollable item {i + 1}");
671-
}
672-
673-
ClayUI.VerticalScrollbar(scrollId);
674-
675-
ClayUI.EndHorizontal();
676-
677-
ClayUI.Space(16);
678-
ClayUI.Label("The main content area also uses this pattern (ScrollConfig + VerticalScrollbar).");
666+
ClayUI.EndScrollArea();
679667
}
680668

681669
void PageWindows()

src/Clay.Test/ClayUIScrollbarTests.cs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,10 @@ public void BeginScrollArea_Renders_WithoutCrash()
223223
{
224224
_fixture.RunFrame(() =>
225225
{
226-
using (ClayUI.BeginScrollArea("scroll1", maxHeight: 100))
227-
{
228-
for (int i = 0; i < 10; i++)
229-
ClayUI.Label($"Item {i}");
230-
}
226+
ClayUI.BeginScrollArea("scroll1", maxHeight: 100);
227+
for (int i = 0; i < 10; i++)
228+
ClayUI.Label($"Item {i}");
229+
ClayUI.EndScrollArea();
231230
});
232231
}
233232

@@ -236,11 +235,10 @@ public void BeginScrollArea_GeneratesScissorCommands()
236235
{
237236
var commands = _fixture.RunFrame(() =>
238237
{
239-
using (ClayUI.BeginScrollArea("scroll2", maxHeight: 100))
240-
{
241-
for (int i = 0; i < 20; i++)
242-
ClayUI.Label($"Item {i}");
243-
}
238+
ClayUI.BeginScrollArea("scroll2", maxHeight: 100);
239+
for (int i = 0; i < 20; i++)
240+
ClayUI.Label($"Item {i}");
241+
ClayUI.EndScrollArea();
244242
});
245243

246244
bool hasScissor = false;

src/Clay/ClayUI.cs

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3010,32 +3010,76 @@ public static void HorizontalScrollbar(ElementId scrollContainerId, ScrollbarSty
30103010
}
30113011

30123012
/// <summary>
3013-
/// Begins a scrollable area.
3013+
/// Begins a scrollable area with an automatic vertical scrollbar.
3014+
/// Call <see cref="EndScrollArea"/> when done adding children.
30143015
/// </summary>
3015-
public static ElementScope BeginScrollArea(string id, float? maxHeight = null, ScrollAreaStyle? style = null)
3016+
public static void BeginScrollArea(string id, float? maxHeight = null, ScrollAreaStyle? style = null)
30163017
{
30173018
var s = style ?? Style.ScrollArea;
3018-
var elementId = StableId($"ScrollArea_{id}");
3019+
var scrollId = StableId($"ScrollArea_{id}");
30193020

3020-
return Clay.Element(new ElementDeclaration
3021+
_context.LayoutScrollInfo.Push(new ClayUIContext.ScrollWrapperInfo(scrollId, IsVertical: true, HasWrapper: true));
3022+
3023+
// Wrapper container (horizontal: scroll content + scrollbar)
3024+
Clay.Element(new ElementDeclaration
30213025
{
3022-
Id = elementId,
30233026
Layout = new LayoutConfig
30243027
{
3025-
Direction = LayoutDirection.TopToBottom,
3028+
Direction = LayoutDirection.LeftToRight,
30263029
Sizing = new Sizing
30273030
{
30283031
Width = SizingAxis.Grow(),
30293032
Height = maxHeight.HasValue
30303033
? SizingAxis.Fit(0, maxHeight.Value)
30313034
: SizingAxis.Grow()
3032-
},
3033-
Padding = s.Padding
3035+
}
30343036
},
3035-
Scroll = ScrollConfig.VerticalScroll,
30363037
BackgroundColor = s.BackgroundColor,
30373038
CornerRadius = s.CornerRadius
30383039
});
3040+
_context.LayoutDepth++;
3041+
3042+
// Scroll container inside wrapper
3043+
Clay.Element(new ElementDeclaration
3044+
{
3045+
Id = scrollId,
3046+
Layout = new LayoutConfig
3047+
{
3048+
Direction = LayoutDirection.TopToBottom,
3049+
Sizing = Sizing.Fill(),
3050+
Padding = s.Padding
3051+
},
3052+
Scroll = ScrollConfig.VerticalScroll
3053+
});
3054+
_context.LayoutDepth++;
3055+
}
3056+
3057+
/// <summary>
3058+
/// Ends a scrollable area started with <see cref="BeginScrollArea"/>.
3059+
/// Automatically adds a vertical scrollbar.
3060+
/// </summary>
3061+
public static void EndScrollArea()
3062+
{
3063+
// Close the scroll container
3064+
if (_context.LayoutDepth > 0)
3065+
{
3066+
Clay.CloseElement();
3067+
_context.LayoutDepth--;
3068+
}
3069+
3070+
// Close the wrapper + add scrollbar
3071+
if (_context.LayoutDepth > 0)
3072+
{
3073+
ClayUIContext.ScrollWrapperInfo? scrollInfo = null;
3074+
if (_context.LayoutScrollInfo.Count > 0)
3075+
scrollInfo = _context.LayoutScrollInfo.Pop();
3076+
3077+
if (scrollInfo.HasValue && scrollInfo.Value.HasWrapper)
3078+
VerticalScrollbar(scrollInfo.Value.ScrollId);
3079+
3080+
Clay.CloseElement();
3081+
_context.LayoutDepth--;
3082+
}
30393083
}
30403084

30413085
// ============ ListBox ============

0 commit comments

Comments
 (0)