UI Layouts
The built-in layout layer is intentionally small. It focuses on
high-value primitives that compose cleanly with layout-size policies:
Stack for ordered
composition, Centered
and Frame for compact
one-child composition, Viewport
for clipped one-surface content, and
ScrollArea for
classic overflow-style scrolling with optional scroll bars. Use
Sections for titled
vertical regions, and
Buttons for centered
action-button rows.
Important
The UI Framework is currently in beta, and parts of the API may change in future releases.
Usage
Building Columns
Use a horizontal stack to place multiple surfaces next to each other. Each child keeps its own size policy, so fixed-width sidebars and one growing main area are easy to express.
auto columns = ui::Stack::create(Orientation::Horizontal);
auto sidebar = ui::Panel::create();
auto mainView = ui::Panel::create();
sidebar->editLayoutMetrics().setPreferredSize(Size{24, 0});
mainView->editLayoutMetrics().setSizePolicy(ui::SizePolicy{ui::SizePolicy::Grow});
columns->addSurface(sidebar);
columns->addSurface(mainView);
Hiding Layout Children
Layouts only allocate space for visible children. Hidden children keep their last rectangle and are ignored until they are shown again. This lets applications keep optional panels in the tree without constantly removing and re-adding them.
auto stack = ui::Stack::create(Orientation::Vertical);
auto header = ui::TextBox::create("Title");
auto details = ui::Panel::create();
auto body = ui::Panel::create();
header->editLayoutMetrics().setFixedHeight(1);
details->editLayoutMetrics().setFixedHeight(4);
body->editLayoutMetrics().setSizePolicy(ui::SizePolicy{ui::SizePolicy::Grow});
stack->addSurface(header);
stack->addSurface(details);
stack->addSurface(body);
details->flags().setVisible(false); // body now receives the space details would have used
How Layout Margins Work
Margins in LayoutMetrics are recommendations to the parent layout.
They are different from padding: a surface owns its content and its own
padding, while the parent that arranges child surfaces owns the spacing
between those children. Metric sizes always describe the child content
rectangle without margins.
When a layout places adjacent child surfaces, it collapses the two touching margins. The larger value wins and becomes spacing owned by the layouting parent. In a vertical stack, the previous child’s bottom margin collapses with the next child’s top margin. In a horizontal stack, the previous child’s right margin collapses with the next child’s left margin.
Margins around the outside of a layout depend on the role of that layout:
Pure layouts arrange peer surfaces.
StackandButtonsare pure layouts. They consume only the margins between children and propagate surrounding child margins through their ownLayoutMetrics. This is what lets nested layouts collapse margins at the level where siblings actually meet.Enclosing layouts place one selected or contained surface inside an area they own.
Frame,Pages, andViewportare enclosing layouts. They collapse their own padding or inset with the enclosed child’s margins and place the child inside the reduced rectangle. The enclosed child’s margins are not propagated further.
For example, in a horizontal stack of vertical stacks, the vertical stacks propagate their left and right child margins. The horizontal stack then collapses the margins between columns. If the vertical stacks consumed those margins themselves, the column spacing would be added instead of collapsed.
Clipping One Content Surface with Viewport
Viewport manages one
content surface. The clearest API is setContentSurface(), but the
generic child container works too as long as the layout still has at most
one child. Removing the content is valid and leaves the viewport empty.
auto viewport = ui::Viewport::create();
auto map = ui::Panel::create();
map->editLayoutMetrics().setFixedSize(Size{200, 80});
viewport->setContentSurface(map);
viewport->setScrollOffset(Position{20, 10});
viewport->setAlignment(Alignment::Center);
viewport->surfaces().remove(map);
viewport->surfaces().add(map);
If the content is smaller than the viewport on an axis, the alignment
anchors it inside the viewport. If the content is larger, the
scrollOffset moves it relative to that regular placement. The offset
is always clamped to the valid range.
Use the common scroll helpers from actions:
auto upAction = ui::Action::create("up");
upAction->addKey(Key::Up);
upAction->setTriggerFn([viewport] { viewport->scrollUp(); });
page->actions().add(upAction);
auto pageDownAction = ui::Action::create("page down");
pageDownAction->addKey(Key::PageDown);
pageDownAction->setTriggerFn([viewport] { viewport->pageDown(); });
page->actions().add(pageDownAction);
auto topAction = ui::Action::create("top");
topAction->addKey(Key::Home);
topAction->setTriggerFn([viewport] { viewport->scrollToTop(); });
page->actions().add(topAction);
Building Classic Overflow Areas
ScrollArea is the
high-level layout for a scrollable child surface. It expands by default,
owns a viewport, owns persistent scroll bar surfaces, and forwards the
same scroll API as the viewport.
auto scrollArea = ui::ScrollArea::create();
auto document = ui::Panel::create();
document->editLayoutMetrics().setFixedSize(Size{120, 80});
scrollArea->setContentSurface(document);
scrollArea->setAlignment(Alignment::TopLeft);
scrollArea->setScrollBarMode(Orientation::Vertical, ui::ScrollBarMode::Automatic);
scrollArea->setScrollBarMode(Orientation::Horizontal, ui::ScrollBarMode::Hidden);
auto downAction = ui::Action::create("down");
downAction->addKey(Key::Down);
downAction->setTriggerFn([scrollArea] { scrollArea->scrollDown(); });
page->actions().add(downAction);
ScrollBarMode::Hidden hides a bar and reserves no space.
ScrollBarMode::Automatic shows a bar only when content overflows on
that axis. ScrollBarMode::Visible reserves a bar whenever the
scroll area’s size permits it. Automatic mode uses a stable pass because
one axis can force the other; for example, a vertical bar consumes one
column and may make horizontal overflow appear.
The scroll bars and corner are persistent child surfaces. They are shown
and hidden with the surface visibility flag instead of being recreated
when the scroll area resizes. Access them for styling through
horizontalScrollBar(), verticalScrollBar(), and
scrollCorner(). These implementation children are protected from
generic public mutation, so use setContentSurface() for the user
content and leave the scroll area structure intact.
Centering and Framing One Child
Centered manages one
content surface, leaves configurable padding around it, and centers the
child in the remaining area. It is useful for dialogs and empty-state
views.
Frame also manages one
content surface. It paints a themed border and optional title, then lays
the child into the inner rectangle after border and padding have been
removed.
Both layouts inherit the same single-content behavior as Viewport:
they tolerate zero children, accept one child through either
setContentSurface() or surfaces().add(), and reject a second
child with an exception.
auto centered = ui::Centered::create();
auto frame = ui::Frame::create();
auto body = ui::Stack::create(Orientation::Vertical);
centered->setPadding(Margins{4, 2});
centered->setContentSurface(frame);
frame->setTitle(String{"Prompt"});
frame->setPadding(Margins{1, 2});
frame->setContentSurface(body);
// Equivalent when the layout is empty:
// frame->surfaces().add(body);
Building Titled Sections
Use Sections when a
vertical area should read as named blocks. Section metadata belongs to
the layout, not to the child surface, so the same surface can stay simple
and reusable.
auto sections = ui::Sections::create();
sections->addSection(
ui::TextBox::create("Licensed to.........: Example GmbH"),
ui::Sections::SectionOptions{String{"License Info"}, String{"[h]"}});
Section options are stored as layout data on the parent-child relation.
If you add a child through sections->surfaces().add(surface), the
layout creates default empty options for it. You can update them later
with setSectionOptions().
The default theme draws ─⟨ title ⟩────. Customize the separator
through the Sections theme element: Border defines the separator
line and title/right insets, Title defines the title style and
padding, and TitleBracket defines the bracket style and bracket
glyphs. Title padding is painted as part of the title, while title
margins collapse with adjacent one-line text parts. Use theme tags when
only selected section groups should use a different decoration.
Reference
-
class Stack : public erbsland::cterm::ui::Layout
A layout that stacks surfaces vertically or horizontally.
Public Functions
-
explicit Stack(Orientation orientation, ProtectedTag) noexcept
Create a stack layout with the given orientation.
- Parameters:
orientation – The stacking direction for child surfaces.
-
virtual LayoutMetrics onMeasure(MeasureScope &scope, const LayoutProposal &proposal) noexcept override
Measure this stack from its visible children.
- Parameters:
scope – Measurement access.
proposal – The proposed stack size.
-
virtual void onLayout(LayoutScope &scope) noexcept override
Recalculate all child rectangles inside this stack.
- Parameters:
scope – The layout scope.
Public Static Functions
-
static StackPtr create(Orientation orientation)
Create a new stack layout.
- Parameters:
orientation – The stacking direction for child surfaces.
- Returns:
The new stack layout instance.
-
explicit Stack(Orientation orientation, ProtectedTag) noexcept
-
class Centered : public erbsland::cterm::ui::layout::SingleContentLayout
A one-child layout that centers its content inside padded available space.
Public Functions
-
explicit Centered(ProtectedTag) noexcept
Create a centered layout.
-
Margins padding() const noexcept
Access the padding around the centered content.
- Returns:
The padding.
-
void setPadding(Margins padding) noexcept
Replace the padding around the centered content.
- Parameters:
padding – The new padding.
-
virtual LayoutMetrics onMeasure(MeasureScope &scope, const LayoutProposal &proposal) noexcept override
Measure this surface for a proposed size.
The returned layout sizes describe the margin-free content rectangle. The optional margins in the returned metrics are recommendations for the parent layout and are not included in these sizes.
- Parameters:
scope – Measurement access for child surfaces.
proposal – The proposed size.
- Returns:
The measured layout metrics.
-
virtual void onLayout(LayoutScope &scope) noexcept override
Layout direct children within this surface.
- Parameters:
scope – Layout access for measuring and placing child surfaces.
Public Static Functions
-
static CenteredPtr create()
Create a centered layout.
- Returns:
The new centered layout.
-
explicit Centered(ProtectedTag) noexcept
-
class Frame : public erbsland::cterm::ui::layout::SingleContentLayout
A one-child layout that surrounds its content with a themed frame and optional title.
Public Functions
-
explicit Frame(ProtectedTag) noexcept
Create a frame layout.
-
Margins padding() const noexcept
Access the padding between frame and content.
- Returns:
The padding.
-
void setPadding(Margins padding) noexcept
Replace the padding between frame and content.
- Parameters:
padding – The new padding.
-
virtual LayoutMetrics onMeasure(MeasureScope &scope, const LayoutProposal &proposal) noexcept override
Measure this surface for a proposed size.
The returned layout sizes describe the margin-free content rectangle. The optional margins in the returned metrics are recommendations for the parent layout and are not included in these sizes.
- Parameters:
scope – Measurement access for child surfaces.
proposal – The proposed size.
- Returns:
The measured layout metrics.
-
virtual void onLayout(LayoutScope &scope) noexcept override
Layout direct children within this surface.
- Parameters:
scope – Layout access for measuring and placing child surfaces.
-
virtual bool isOpaque() const noexcept override
Test if this surface paints every cell in its rectangle.
- Returns:
trueif the surface is opaque.
-
virtual void onPaint(WritableBuffer &buffer, const PaintContext &context) noexcept override
Paint this surface into the target buffer.
- Parameters:
buffer – The target buffer.
context – The paint context for the current paint pass.
Public Static Functions
-
static FramePtr create()
Create a frame layout.
- Returns:
The new frame layout.
-
explicit Frame(ProtectedTag) noexcept
-
class SingleContentLayout : public erbsland::cterm::ui::Layout
Base class for layouts that manage one optional content surface.
Subclassed by erbsland::cterm::ui::layout::Centered, erbsland::cterm::ui::layout::Frame, erbsland::cterm::ui::layout::Viewport
Public Functions
-
void setContentSurface(SurfacePtr contentSurface)
Replace the content surface.
- Parameters:
contentSurface – The new content surface, or an empty pointer to clear it.
-
const SurfacePtr &contentSurface() const noexcept
Access the current content surface.
- Returns:
The current content surface.
-
void setContentSurface(SurfacePtr contentSurface)
-
class Viewport : public erbsland::cterm::ui::layout::SingleContentLayout
A layout surface that clips, positions, and scrolls one content surface.
Public Functions
-
explicit Viewport(ProtectedTag) noexcept
Create a viewport.
-
Alignment alignment() const noexcept
Get the content alignment for undersized content.
- Returns:
The content alignment.
-
void setAlignment(Alignment alignment) noexcept
Replace the content alignment for undersized content.
- Parameters:
alignment – The new alignment.
-
Position scrollOffset() const noexcept
Get the current scroll offset.
- Returns:
The clamped scroll offset.
-
void setScrollOffset(Position scrollOffset) noexcept
Replace the scroll offset.
- Parameters:
scrollOffset – The new scroll offset.
-
void scrollBy(Position delta) noexcept
Scroll by the given delta.
- Parameters:
delta – The scroll delta.
-
void scrollUp(Coordinate count = 1) noexcept
Scroll one or more rows up.
- Parameters:
count – The number of rows.
-
void scrollDown(Coordinate count = 1) noexcept
Scroll one or more rows down.
- Parameters:
count – The number of rows.
-
void scrollLeft(Coordinate count = 1) noexcept
Scroll one or more columns left.
- Parameters:
count – The number of columns.
-
void scrollRight(Coordinate count = 1) noexcept
Scroll one or more columns right.
- Parameters:
count – The number of columns.
-
void pageUp() noexcept
Scroll one page up.
-
void pageDown() noexcept
Scroll one page down.
-
void pageLeft() noexcept
Scroll one page left.
-
void pageRight() noexcept
Scroll one page right.
-
void scrollToTop() noexcept
Scroll to the top edge.
-
void scrollToBottom() noexcept
Scroll to the bottom edge.
-
void scrollToLeftEdge() noexcept
Scroll to the left edge.
-
void scrollToRightEdge() noexcept
Scroll to the right edge.
-
void scrollIntoView(Rectangle contentRect) noexcept
Scroll by the smallest amount needed to make a content rectangle visible.
- Parameters:
contentRect – The rectangle in content coordinates.
-
Size contentSizeForViewport(Size viewportSize) const noexcept
Resolve the content size for a candidate viewport.
- Parameters:
viewportSize – The candidate viewport size.
- Returns:
The content size.
-
Size contentSizeForViewport(Size viewportSize, LayoutScope &scope) noexcept
Resolve the content size for a candidate viewport using measurement.
- Parameters:
viewportSize – The candidate viewport size.
scope – The measurement scope.
- Returns:
The content size.
-
virtual void onLayout(LayoutScope &scope) noexcept override
Layout direct children within this surface.
- Parameters:
scope – Layout access for measuring and placing child surfaces.
Public Static Functions
-
static ViewportPtr create()
Create a viewport.
- Returns:
The new viewport.
-
explicit Viewport(ProtectedTag) noexcept
-
class ScrollArea : public erbsland::cterm::ui::surface::AbstractScrollArea
A scroll area layout that displays one content surface with optional scroll bars.
Public Functions
-
explicit ScrollArea(ProtectedTag) noexcept
Create a scroll area.
-
void setContentSurface(SurfacePtr contentSurface)
Replace the content surface.
- Parameters:
contentSurface – The new content surface, or an empty pointer to clear it.
-
const SurfacePtr &contentSurface() const noexcept
Access the current content surface.
- Returns:
The current content surface.
-
const ViewportPtr &viewport() const noexcept
Access the viewport.
- Returns:
The viewport.
-
Alignment alignment() const noexcept
Get the content alignment for undersized content.
- Returns:
The content alignment.
-
void setAlignment(Alignment alignment) noexcept
Replace the content alignment for undersized content.
- Parameters:
alignment – The new alignment.
-
virtual void onLayout(LayoutScope &scope) noexcept override
Layout direct children within this surface.
- Parameters:
scope – Layout access for measuring and placing child surfaces.
Public Static Functions
-
static ScrollAreaPtr create()
Create a scroll area.
- Returns:
The new scroll area.
-
explicit ScrollArea(ProtectedTag) noexcept
-
class Sections : public erbsland::cterm::ui::Layout
A vertical layout that separates child surfaces with themed title lines.
Public Types
-
using SectionOptions = layout::SectionOptions
Section options type, kept here for source compatibility.
Public Functions
-
explicit Sections(ProtectedTag) noexcept
Create a sections layout.
-
void addSection(SurfacePtr surface)
Add one surface as a section.
- Parameters:
surface – The section body surface.
-
void addSection(SurfacePtr surface, SectionOptions options)
Add one surface as a section.
- Parameters:
surface – The section body surface.
options – The section metadata.
-
std::size_t sectionCount() const noexcept
Get the number of configured sections.
-
const SectionOptions §ionOptions(std::size_t index) const
Access options for one section.
- Parameters:
index – The section index.
- Returns:
The section options.
-
void setSectionOptions(std::size_t index, SectionOptions options)
Replace options for one section.
- Parameters:
index – The section index.
options – The new section options.
-
bool isTrailingSeparatorVisible() const noexcept
Test if a trailing separator is drawn after the last section.
-
void setTrailingSeparatorVisible(bool visible) noexcept
Change whether a trailing separator is drawn after the last section.
- Parameters:
visible – The new visibility state.
-
virtual void onLayout(LayoutScope &scope) noexcept override
Layout direct children within this surface.
- Parameters:
scope – Layout access for measuring and placing child surfaces.
-
virtual void onPaint(WritableBuffer &buffer, const PaintContext &context) noexcept override
Paint this surface into the target buffer.
- Parameters:
buffer – The target buffer.
context – The paint context for the current paint pass.
Public Static Functions
-
static SectionsPtr create()
Create a sections layout.
- Returns:
The new sections layout.
-
using SectionOptions = layout::SectionOptions
-
class Buttons : public erbsland::cterm::ui::Layout
A horizontal button layout that centers rows and wraps when needed.
Public Functions
-
explicit Buttons(ProtectedTag) noexcept
Create a button layout.
-
surface::ButtonPtr addAction(ButtonActionPtr action)
Add an action-backed button.
- Parameters:
action – The action represented by the new button.
- Returns:
The created button surface.
-
void addButton(surface::ButtonPtr button)
Add an existing button.
- Parameters:
button – The button to add.
-
void insertButton(std::size_t index, surface::ButtonPtr button)
Insert an existing button.
- Parameters:
index – The insertion index.
button – The button to insert.
-
std::size_t buttonCount() const noexcept
Get the number of buttons in this layout.
-
surface::ButtonPtr button(std::size_t index) const
Access one button.
- Parameters:
index – The button index.
-
virtual LayoutMetrics onMeasure(MeasureScope &scope, const LayoutProposal &proposal) noexcept override
Measure this surface for a proposed size.
The returned layout sizes describe the margin-free content rectangle. The optional margins in the returned metrics are recommendations for the parent layout and are not included in these sizes.
- Parameters:
scope – Measurement access for child surfaces.
proposal – The proposed size.
- Returns:
The measured layout metrics.
-
virtual void onLayout(LayoutScope &scope) noexcept override
Layout direct children within this surface.
- Parameters:
scope – Layout access for measuring and placing child surfaces.
-
virtual void onKeyPress(KeyPressEvent &keyPressEvent) noexcept override
Handle a key press event.
This is the low-level event handler for key press events. If a surface handles a key press event, it should mark it as handled.
- Parameters:
keyPressEvent – The key press event to handle.
Public Static Functions
-
static ButtonsPtr create()
Create a button layout.
- Returns:
The new button layout.
-
explicit Buttons(ProtectedTag) noexcept