Skip to content

RelativePanel

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

Lays out children relative to each other and to the panel's edges.

Each child carries a ui::RelativePanelConstraints (sixteen constraints). A child is measured in the room its constraints leave it, then pinned: stretched between two anchored edges, placed against one, centered, or in the top-left corner when nothing is set. On the same side, a panel alignment wins over a sibling alignment, which wins over left_of/right_of. Siblings are resolved before the children that refer to them, whatever order they were added in. A constraint that would make a cycle, or that names a non-child, is ignored. The panel asks for the widest and tallest dependency chain plus its border and padding. ui::RelativePanel is the declarative form, referring to siblings by name.

Example:

auto panel = std::make_unique<ui::RelativePanelControl>();
panel->set_padding(ui::Thickness{12.0f});
auto* title  = panel->add(std::make_unique<ui::TextControl>("Title"));
auto* ok     = panel->add(std::make_unique<ui::ButtonControl>("OK"), {.align_right_with_panel = true});
auto* cancel = panel->add(std::make_unique<ui::ButtonControl>("Cancel"), {.left_of = ok});
panel->add(std::make_unique<ui::TextControl>("Body"), {.below = title});
panel->set_constraints(ok, {.below = title, .align_right_with_panel = true}); // re-pin later
cancel->on_click([] { /* ... */ });
window.set_content(std::move(panel));

// Declaratively:
auto node = ui::build(ui::RelativePanel{.content = {
    ui::RelativeItem{.name = "a", .content = ui::Text{.text = "A"}},
    ui::RelativeItem{.right_of = "a", .content = ui::Text{.text = "B"}},
}});

ui::RelativePanel

Children placed relative to each other and to the panel. Builds a ui::RelativePanelControl.

Example:

window.set_content(ui::RelativePanel{
    .padding  = ui::Thickness{12.0f},
    .width    = 280.0f,
    .content  = {
        ui::RelativeItem{.name                  = "image",
                         .align_left_with_panel = true,
                         .align_top_with_panel  = true,
                         .content = ui::Image{.source = ui::Bitmap::read("cover.jpg"), .width = 80.0f}},
        ui::RelativeItem{.name = "title", .right_of = "image", .align_top_with = "image",
                         .content = ui::Text{.text = "Title", .role = ui::TextRole::BodyStrong}},
        ui::RelativeItem{.right_of = "image", .below = "title", .content = ui::Text{.text = "Subtitle"}},
    },
});

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

ui::RelativePanel{
    .background, .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
background ui::Binding<ui::Paint> Background: a Color, or a ThemeColor read from the theme; empty (the default) draws none.
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> Border width per edge, DIPs.
corner_radius ui::Binding<float> {0.0f} Corner radius, DIPs.
padding ui::Binding<ui::Thickness> Padding 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::RelativePanel> Out: the handle that receives the built control.
content ui::RelativeItemList The constrained children; see ui::RelativeItem.

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::RelativePanelControl; the caller owns it.

ui::RelativeItem

One child of a RelativePanel. name is what a sibling's constraints refer to it by. A constraint must name a sibling in the same panel. A name that matches none is a bug, caught by an assert in a debug build and otherwise left unset. Each string field is the ui::RelativePanelConstraints member of the same name; see ui::RelativePanel for an example.

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

ui::RelativeItem{
    .name, .left_of, .above, .right_of, .below, .align_horizontal_center_with,
    .align_vertical_center_with, .align_left_with, .align_top_with, .align_right_with,
    .align_bottom_with, .align_left_with_panel, .align_top_with_panel, .align_right_with_panel,
    .align_bottom_with_panel, .align_horizontal_center_with_panel,
    .align_vertical_center_with_panel, .content
}

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

Field Type Default
name std::string This child's name, for siblings to refer to.
left_of std::string Sit left of the named sibling.
above std::string Sit above the named sibling.
right_of std::string Sit right of the named sibling.
below std::string Sit below the named sibling.
align_horizontal_center_with std::string Share the named sibling's horizontal center.
align_vertical_center_with std::string Share the named sibling's vertical center.
align_left_with std::string Align left edges with the named sibling.
align_top_with std::string Align top edges with the named sibling.
align_right_with std::string Align right edges with the named sibling.
align_bottom_with std::string Align bottom edges with the named sibling.
align_left_with_panel bool false Pin to the panel's left edge.
align_top_with_panel bool false Pin to the panel's top edge.
align_right_with_panel bool false Pin to the panel's right edge.
align_bottom_with_panel bool false Pin to the panel's bottom edge.
align_horizontal_center_with_panel bool false Center horizontally in the panel.
align_vertical_center_with_panel bool false Center vertically in the panel.
content ui::Spec The placed child.

ui::RelativePanelControl

Derives from ui::Panel.

Lays out children relative to each other and to the panel's edges.

Each child carries a ui::RelativePanelConstraints (sixteen constraints). A child is measured in the room its constraints leave it, then pinned: stretched between two anchored edges, placed against one, centered, or in the top-left corner when nothing is set. On the same side, a panel alignment wins over a sibling alignment, which wins over left_of/right_of. Siblings are resolved before the children that refer to them, whatever order they were added in. A constraint that would make a cycle, or that names a non-child, is ignored. The panel asks for the widest and tallest dependency chain plus its border and padding. ui::RelativePanel is the declarative form, referring to siblings by name.

Example:

auto panel = std::make_unique<ui::RelativePanelControl>();
panel->set_padding(ui::Thickness{12.0f});
auto* title  = panel->add(std::make_unique<ui::TextControl>("Title"));
auto* ok     = panel->add(std::make_unique<ui::ButtonControl>("OK"), {.align_right_with_panel = true});
auto* cancel = panel->add(std::make_unique<ui::ButtonControl>("Cancel"), {.left_of = ok});
panel->add(std::make_unique<ui::TextControl>("Body"), {.below = title});
panel->set_constraints(ok, {.below = title, .align_right_with_panel = true}); // re-pin later
cancel->on_click([] { /* ... */ });
window.set_content(std::move(panel));

// Declaratively:
auto node = ui::build(ui::RelativePanel{.content = {
    ui::RelativeItem{.name = "a", .content = ui::Text{.text = "A"}},
    ui::RelativeItem{.right_of = "a", .content = ui::Text{.text = "B"}},
}});

RelativePanelControl

RelativePanelControl() = default

An empty panel with no border or padding.

add

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

Takes ownership of child with its constraints and returns a non-owning observer. Constraints may name siblings added later (set them with set_constraints()).

add

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

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.

add

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

add() with no constraints set.

add

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

The declarative add() with no constraints set.

set_constraints

void set_constraints(const Element* child, const RelativePanelConstraints& constraints)

Replaces a child's constraints; an element that is not a child is ignored.

constraints

RelativePanelConstraints constraints(const Element* child) const

A child's constraints; all unset for an element that is not a child.

parallel_measure_safe

bool parallel_measure_safe() const override

True: measuring touches only the panel and its children. The children are measured in dependency order on one thread.

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.

border_thickness

const Thickness& border_thickness() const

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

corner_radius

float corner_radius() const

The corner radius in DIPs, uniform for all four corners (default 0, square).

padding

const Thickness& padding() const

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

set_border_brush

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

A fixed outline color; replaces any resource. Marks the panel for redraw.

set_border_brush

void set_border_brush(ThemeColor resource)

An outline color read from the theme each time the panel is drawn; replaces any fixed color. Marks the panel for redraw.

set_border_thickness

void set_border_thickness(const Thickness& thickness)

Sets the border width; marks the panel for layout and redraw.

set_corner_radius

void set_corner_radius(float radius)

Sets the corner radius; marks the panel for redraw.

set_padding

void set_padding(const Thickness& padding)

Sets the padding; marks the panel for layout.

measure_override

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

Resolves the dependency graph, measures each child in the room its constraints leave, and returns the widest and tallest chain plus border and padding.

arrange_override

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

Pins every child per its constraints inside the border and padding.

struct RelativePanelConstraints

The placement constraints of one child of a ui::RelativePanelControl. A sibling reference is an observer of another child of the same panel; nullptr leaves that constraint unset.

Field Type Default
left_of const Element* nullptr Right edge against this sibling's left edge.
above const Element* nullptr Bottom edge against this sibling's top edge.
right_of const Element* nullptr Left edge against this sibling's right edge.
below const Element* nullptr Top edge against this sibling's bottom edge.
align_horizontal_center_with const Element* nullptr Horizontal center on this sibling's.
align_vertical_center_with const Element* nullptr Vertical center on this sibling's.
align_left_with const Element* nullptr Left edge in line with this sibling's.
align_top_with const Element* nullptr Top edge in line with this sibling's.
align_right_with const Element* nullptr Right edge in line with this sibling's.
align_bottom_with const Element* nullptr Bottom edge in line with this sibling's.
align_left_with_panel bool false Left edge on the panel's content edge.
align_top_with_panel bool false Top edge on the panel's content edge.
align_right_with_panel bool false Right edge on the panel's content edge.
align_bottom_with_panel bool false Bottom edge on the panel's content edge.
align_horizontal_center_with_panel bool false Centered horizontally in the panel.
align_vertical_center_with_panel bool false Centered vertically in the panel.

class RelativeItemList

A RelativePanel's children, built; the names are resolved when the panel is. Callers write it as a braced list of ui::RelativeItem{...}.

items_

std::vector<Placed> items_

The placed children, in declaration order.

RelativeItemList

RelativeItemList() = default

An empty list.

RelativeItemList

template <typename... S>
RelativeItemList(S&& ... items)
    requires (sizeof...(S) > 0 && (std::same_as<S, RelativeItem> && ...))

Builds every item's content and takes ownership of it, keeping the constraints.

RelativeItemList

RelativeItemList(const RelativeItemList&) = delete

operator=

RelativeItemList& operator=(const RelativeItemList&) = delete

RelativeItemList

RelativeItemList(RelativeItemList&&) = default

operator=

RelativeItemList& operator=(RelativeItemList&&) = default

add

void add(RelativeItem item)

Builds item's content and appends it, as one more entry of the braced list would.

struct Placed

One built child with its constraints; the node is already owned by the list.

Field Type Default
names RelativeItem The constraints; its content is already taken.
node std::unique_ptr<Element> The built child.

Tests

  • tests/test_layout_panels.cpp