Skip to content

Stack

Header: winui/ui/panels/Stack.hpp. Spec: winui/ui/panels/Stack.hpp.

Lays its children out in a single column or row, spacing apart.

The panel owns its children (add() takes a unique_ptr and hands back a non-owning observer). Each child is measured unbounded along the stack and gets the panel's full cross extent, where its own alignment places it. The declarative forms are ui::VerticalStack and ui::HorizontalStack.

Example:

auto  stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
stack->set_spacing(12.0f);
ui::TextControl* label = stack->add(std::make_unique<ui::TextControl>("Clicks: 0"));
ui::ButtonControl*    click = stack->add(std::make_unique<ui::ButtonControl>("Click me"));
click->on_click([label, n = 0]() mutable { label->set_text(std::format("Clicks: {}", ++n)); });
window.set_content(std::move(stack)); // the window takes ownership of the whole subtree

// The same tree, declared:
ui::Property<int> clicks{0};
window.set_content(ui::VerticalStack{
    .spacing  = 12,
    .content  = {
        ui::Text{ .text = ui::bind(clicks, [](int n) { return std::format("Clicks: {}", n); }) },
        ui::Button{ .text = "Click me", .on_click = [&] { ++clicks; } },
    },
});

ui::VerticalStack

A column of children. Builds a ui::StackControl with vertical orientation.

Children are measured with unbounded height and stacked top to bottom; child_alignment decides their horizontal placement (a child's own horizontal_alignment still applies inside that).

Example:

ui::Property<int> clicks{0};

window.set_content(ui::VerticalStack{
    .spacing         = 12,
    .child_alignment = ui::Alignment::Leading,
    .margin          = ui::Thickness{24.0f},
    .content         = {
        ui::Text{.text = "Appearance", .role = ui::TextRole::Subtitle},
        ui::Text{.text = ui::bind(clicks, [](int n) { return std::format("Clicks: {}", n); })},
        ui::HorizontalStack{.spacing = 8, .content = {
            ui::Button{.text = "Click me", .on_click = [&] { ++clicks; }},
            ui::Button{.text = "Reset",    .on_click = [&] { clicks = 0; }},
        }},
    },
});

Every field, in the order a designated initializer names them:

ui::VerticalStack{
    .spacing, .child_alignment, .children_transitions, .background, .background_transition,
    .border_brush, .border_thickness, .corner_radius, .padding, .style, .margin, .width,
    .height, .min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
    .vertical_alignment, .visible, .tool_tip, .enabled, .accessibility, .on_pointer_entered,
    .on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down, .on_wheel, .control,
    .content
}

Fields, in declaration order (designators must follow it):

Field Type Default
spacing ui::Binding<float> {0.0f} Gap between adjacent children, DIPs.
child_alignment ui::Binding<ui::Alignment> {ui::Alignment::Stretch} Where children sit across the stack axis; Stretch (the default) sizes them to the panel.
children_transitions ui::Binding<ui::TransitionList> Transitions every child runs: an EntranceTransition as each is first laid out, a RepositionTransition as layout moves it.
background ui::Binding<ui::Paint> Background behind the children: a Color, or a ThemeColor read from the theme; empty (the default) draws nothing.
background_transition ui::Binding<std::optional<ui::BrushTransition>> Animates a background change (Animation.hpp's BrushTransition).
border_brush ui::Binding<ui::Paint> Border color, a Color or a ThemeColor; empty (the default) draws no border.
border_thickness ui::Binding<ui::Thickness> {ui::Thickness{}} Border width per edge, DIPs; it insets the children like padding.
corner_radius ui::Binding<float> {0.0f} Corner radius of the background and the border, DIPs.
padding ui::Binding<ui::Thickness> {ui::Thickness{}} Room between the border and the children, DIPs.
on_pointer_entered ui::Action The pointer moved onto the control.
on_pointer_exited ui::Action The pointer left the control.
on_got_focus ui::Action Keyboard focus arrived on the control, or on anything inside it, from outside it. A control with nothing focusable in it never gets focus, and never raises this.
on_lost_focus ui::Action Keyboard focus left the control and everything inside it.
on_key_down ui::Callback<ui::Handling(const ui::KeyEvent&)> A key pressed while focus is on the control or inside it, before the control's own handling; ui::Handled stops it there (Element::on_key_down()).
on_wheel ui::Callback<ui::Handling(const ui::PointerEvent&)> A wheel turn over the control, before the control scrolls; ui::Handled stops it there (Element::on_wheel()).
control ui::ControlOut<ui::VerticalStack> Out: the handle that receives the built control.
content ui::SpecList The stacked children, top to bottom.

Plus the common fields style, margin, width, height, min_width, min_height, max_width, max_height, horizontal_alignment, vertical_alignment, visible, tool_tip, enabled, accessibility every spec carries (conventions).

build

std::unique_ptr<Element> build()&&

Consumes the spec and returns the built ui::StackControl; the caller owns it.

ui::HorizontalStack

A row of children. Builds a ui::StackControl with horizontal orientation.

The mirror of ui::VerticalStack: children stack left to right and child_alignment places them vertically.

Example:

window.set_content(ui::HorizontalStack{
    .spacing         = 8,
    .child_alignment = ui::Alignment::Center,
    .content         = {
        ui::Icon{.source = ui::Symbol::Save},
        ui::Text{.text = "Unsaved changes"},
        ui::Button{.text = "Save", .on_click = [&] { save(); }},
    },
});

Every field, in the order a designated initializer names them:

ui::HorizontalStack{
    .spacing, .child_alignment, .children_transitions, .background, .background_transition,
    .border_brush, .border_thickness, .corner_radius, .padding, .style, .margin, .width,
    .height, .min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
    .vertical_alignment, .visible, .tool_tip, .enabled, .accessibility, .on_pointer_entered,
    .on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down, .on_wheel, .control,
    .content
}

Fields, in declaration order (designators must follow it):

Field Type Default
spacing ui::Binding<float> {0.0f} Gap between adjacent children, DIPs.
child_alignment ui::Binding<ui::Alignment> {ui::Alignment::Stretch} Where children sit across the stack axis; Stretch (the default) sizes them to the panel.
children_transitions ui::Binding<ui::TransitionList> Transitions every child runs: an EntranceTransition as each is first laid out, a RepositionTransition as layout moves it.
background ui::Binding<ui::Paint> Background behind the children: a Color, or a ThemeColor read from the theme; empty (the default) draws nothing.
background_transition ui::Binding<std::optional<ui::BrushTransition>> Animates a background change (Animation.hpp's BrushTransition).
border_brush ui::Binding<ui::Paint> Border color, a Color or a ThemeColor; empty (the default) draws no border.
border_thickness ui::Binding<ui::Thickness> {ui::Thickness{}} Border width per edge, DIPs; it insets the children like padding.
corner_radius ui::Binding<float> {0.0f} Corner radius of the background and the border, DIPs.
padding ui::Binding<ui::Thickness> {ui::Thickness{}} Room between the border and the children, DIPs.
on_pointer_entered ui::Action The pointer moved onto the control.
on_pointer_exited ui::Action The pointer left the control.
on_got_focus ui::Action Keyboard focus arrived on the control, or on anything inside it, from outside it. A control with nothing focusable in it never gets focus, and never raises this.
on_lost_focus ui::Action Keyboard focus left the control and everything inside it.
on_key_down ui::Callback<ui::Handling(const ui::KeyEvent&)> A key pressed while focus is on the control or inside it, before the control's own handling; ui::Handled stops it there (Element::on_key_down()).
on_wheel ui::Callback<ui::Handling(const ui::PointerEvent&)> A wheel turn over the control, before the control scrolls; ui::Handled stops it there (Element::on_wheel()).
control ui::ControlOut<ui::HorizontalStack> Out: the handle that receives the built control.
content ui::SpecList The stacked children, left to right.

Plus the common fields style, margin, width, height, min_width, min_height, max_width, max_height, horizontal_alignment, vertical_alignment, visible, tool_tip, enabled, accessibility every spec carries (conventions).

build

std::unique_ptr<Element> build()&&

Consumes the spec and returns the built ui::StackControl; the caller owns it.

ui::StackControl

Derives from ui::Element.

Lays its children out in a single column or row, spacing apart.

The panel owns its children (add() takes a unique_ptr and hands back a non-owning observer). Each child is measured unbounded along the stack and gets the panel's full cross extent, where its own alignment places it. The declarative forms are ui::VerticalStack and ui::HorizontalStack.

Example:

auto  stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
stack->set_spacing(12.0f);
ui::TextControl* label = stack->add(std::make_unique<ui::TextControl>("Clicks: 0"));
ui::ButtonControl*    click = stack->add(std::make_unique<ui::ButtonControl>("Click me"));
click->on_click([label, n = 0]() mutable { label->set_text(std::format("Clicks: {}", ++n)); });
window.set_content(std::move(stack)); // the window takes ownership of the whole subtree

// The same tree, declared:
ui::Property<int> clicks{0};
window.set_content(ui::VerticalStack{
    .spacing  = 12,
    .content  = {
        ui::Text{ .text = ui::bind(clicks, [](int n) { return std::format("Clicks: {}", n); }) },
        ui::Button{ .text = "Click me", .on_click = [&] { ++clicks; } },
    },
});

StackControl

StackControl() = default

An empty vertical panel, no spacing, no background.

StackControl

StackControl(Orientation orientation)

An empty panel stacking in orientation.

add

template <typename T>
T* add(std::unique_ptr<T> child)

Takes ownership of a child, appends it after the last one, and returns a non-owning observer to it. The panel is the sole owner, so the pointer is valid only while the child is in the panel. Invalidates layout.

add

template <SpecLike S>
Element* add(S&& spec)

The declarative overload: builds spec (or takes the node a ui::Spec holds) and adds it as add() does a control. Returns the built node as an Element, or nullptr for an empty ui::Spec, which adds nothing; a spec's .control field gives a typed observer.

insert

template <typename T>
T* insert(std::size_t index, std::unique_ptr<T> child)

add() at index (clamped to the end) instead of after the last child.

children

std::span<const std::unique_ptr<Element>> children() const

The children in stacking order (owned by the panel).

remove

bool remove(const Element* child)

Destroys child if it is one of this panel's. False when it is not. Any observer to it dangles afterwards. With an AddRemoveTransition in children_transitions(), the child leaves visibly. It drops out of layout, input and the tab ring at once, fades out where it was, and is destroyed when the fade has played.

orphan

std::unique_ptr<Element> orphan(const Element* child)

Takes child out of the panel alive and hands it back, for adding somewhere else. Focus inside it is cleared and it plays no exit transition. Null when it is not one of this panel's children.

clear

void clear()

Destroys every child, each one leaving as it does with remove().

orientation

Orientation orientation() const

The stacking direction; Vertical by default.

set_orientation

void set_orientation(Orientation o)

Changes the direction; invalidates layout.

spacing

float spacing() const

The gap between neighboring children, in DIPs; 0 by default.

set_spacing

void set_spacing(float spacing)

Changes the gap; invalidates layout.

background

const std::optional<Color>& background() const

The background, filled behind the children; nullopt (the default) draws nothing. A ThemeColor is a theme resource, re-read whenever it is drawn (ui/BackgroundBrush.hpp).

background_resource

ThemeColor background_resource() const

The theme resource the background follows, when set_background(ThemeColor) set one.

set_background

void set_background(std::optional<Color> color)

A fixed background color (nullopt clears it); redraws.

set_background

void set_background(ThemeColor resource)

A theme-resource background, re-read on every theme change; redraws.

background_transition

const std::optional<BrushTransition>& background_transition() const

With a transition set, a later change of what the background draws (a new color, or a resource under a new theme) blends from the color on screen (Animation.hpp's BrushTransition). nullopt (the default) changes it at once.

set_background_transition

void set_background_transition(std::optional<BrushTransition> transition)

Sets or clears the transition used by later background changes.

border_brush

std::optional<Color> border_brush() const

The outline color; nullopt (the default) draws no border even with a non-zero border_thickness(). Nullopt as well while a ThemeColor is set.

border_brush_resource

ThemeColor border_brush_resource() const

The theme resource the border is drawn from, or null.

set_border_brush

void set_border_brush(std::optional<Color> color)

A fixed outline color; replaces any resource. Redraws.

set_border_brush

void set_border_brush(ThemeColor resource)

An outline color read from the theme each time it is drawn; replaces any fixed color. Redraws.

border_thickness

const Thickness& border_thickness() const

The border width in DIPs per side (default zero); it insets the children like padding.

set_border_thickness

void set_border_thickness(const Thickness& thickness)

Sets the border width; updates the layout and redraws.

corner_radius

float corner_radius() const

The corner radius in DIPs, uniform for all four corners (default 0, square). It rounds the background and the border; the children are not clipped.

set_corner_radius

void set_corner_radius(float radius)

Sets the corner radius; redraws.

padding

const Thickness& padding() const

The room in DIPs between the border and the children (default zero).

set_padding

void set_padding(const Thickness& padding)

Sets the padding and updates the layout.

child_alignment

Alignment child_alignment() const

How children are placed across the stack. With Stretch, the default, every child gets the panel's full cross extent and its own horizontal_alignment() / vertical_alignment() positions it within that. Leading, Center and Trailing place every child at its desired cross size, and a child's own alignment then has no effect.

set_child_alignment

void set_child_alignment(Alignment a)

Changes child_alignment(); invalidates layout.

parallel_measure_safe

bool parallel_measure_safe() const override

True: measuring touches only the panel and its children.

for_each_child

void for_each_child(ChildVisitor visit) const override

Lists the children in stacking order.

measure_override

Size measure_override(Size available, const MeasureContext& context) const override

Measures each child unbounded along the stack and bounded across it, caching the sizes; returns their sum along the stack (plus spacing) by the largest across.

arrange_override

void arrange_override(const Rect& final_rect, const ArrangeContext& context) override

Places the children one after another from the cached sizes, aligned per child_alignment().

render_override

void render_override(Renderer& renderer, const RenderContext& context) const override

Fills the background, then renders every child in order.

tick_override

void tick_override(Duration delta) override

Advances the background and exit transitions, then ticks every child.

Aliases

VerticalStackControl

using ui::VerticalStackControl = StackControl

The control a ui::VerticalStack builds: a StackControl whose orientation is Vertical.

HorizontalStackControl

using ui::HorizontalStackControl = StackControl

The control a ui::HorizontalStack builds: a StackControl whose orientation is Horizontal.

Tests

  • tests/test_stack.cpp