Skip to content

Sections

Header: winui/ui/navigation/Sections.hpp. Spec: winui/ui/navigation/Sections.hpp.

A row of large text headers over the selected item's content.

An optional title sits above the header row, and optional left/right header elements flank it. When the headers do not all fit, they form a carousel by default. The selected header leads the row and the rest follow it, wrapping around. Only then do the keyboard and the navigation buttons shown on hover wrap too. The selected header is underlined with an accent bar. A change of selection flies the old content out and the new one in from the side the selection moved to. The header row is the tab stop. Left/Right select the previous/next item and Home/End the first/last. Ctrl+Tab, Ctrl+Shift+Tab, Ctrl+PageDown and Ctrl+PageUp step the selection from anywhere inside. ui::Sections is the declarative form.

Example:

window.set_content(ui::Sections{
    .title      = std::string{"EMAIL"},
    .min_height = 400.0f,
    .on_selection_changed = [](std::size_t index) { /* e.g. mark items read */ },
    .items = {
        ui::Section{.header = "All",     .content = ui::Text{.text = "all emails go here."}},
        ui::Section{.header = "Unread",  .content = ui::Text{.text = "unread emails go here."}},
        ui::Section{.header = "Flagged", .content = ui::Text{.text = "flagged emails go here."}},
    },
});

// Imperatively:
auto sections = std::make_unique<ui::SectionsControl>();
sections->set_title("EMAIL");
ui::TextControl* all = sections->add_item("All", std::make_unique<ui::TextControl>("all emails"));
sections->add_item("Unread", std::make_unique<ui::TextControl>("unread emails"));
sections->on_selection_changed([](std::size_t index) { /* ... */ });
sections->set_selected_index(1);

ui::Sections

Large text headers over swappable content. Builds a ui::SectionsControl.

Example:

ui::Property<std::size_t> current_folder{0};
window.set_content(ui::Sections{
    .title                = "EMAIL",
    .on_selection_changed = [&](std::size_t i) { current_folder = i; },
    .items                = {
        ui::Section{ .header = "All", .content = ui::Text{ .text = "all emails go here." } },
        ui::Section{ .header = "Unread", .content = ui::Text{ .text = "unread emails go here." } },
        ui::Section{ .header = "Flagged", .content = ui::Text{ .text = "flagged emails go here." } },
    },
});

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

ui::Sections{
    .title, .selected_index, .header_carousel_enabled, .style, .margin, .width, .height,
    .min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
    .vertical_alignment, .visible, .tool_tip, .enabled, .accessibility, .on_selection_changed,
    .on_pointer_entered, .on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down,
    .on_wheel, .control, .items, .left_header_content, .right_header_content
}

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

Field Type Default
title ui::Binding<std::string> The small caption above the headers; empty draws none.
selected_index ui::TwoWayField<std::size_t> {0} The selected item; clamped to the items.
header_carousel_enabled ui::Binding<bool> {true} Headers that do not fit rotate so the selected one leads. False scrolls the row instead.
on_selection_changed ui::Callback<void(std::size_t)> Called with the new index whenever the selection moves.
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::Sections> Out: the handle that receives the built control.
items ui::SectionList The items, in header order.
left_header_content ui::Spec An element left of the headers.
right_header_content ui::Spec An element right of the headers.

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

ui::Section

One section: a header and the content shown while it is selected. Written inside the .items of a ui::Sections.

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

ui::Section{
    .header, .content
}

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

Field Type Default
header std::string The item's header text in the header row.
content ui::Spec What the Sections shows while this item is selected.

ui::SectionList

A built, owned list of Sections items. Callers never name it, since a braced list of ui::Section{...} converts to it.

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

ui::SectionList{
    .entries
}

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

Field Type Default
entries std::vector<Entry> The items, in header order.

operator=

SectionList& operator=(const SectionList&) = delete

operator=

SectionList& operator=(SectionList&&) = default

struct Entry

One item with its content already built.

Field Type Default
header std::string
node std::unique_ptr<Element>

ui::SectionsControl

Derives from ui::Element, ui::ValueProvider.

A row of large text headers over the selected item's content.

An optional title sits above the header row, and optional left/right header elements flank it. When the headers do not all fit, they form a carousel by default. The selected header leads the row and the rest follow it, wrapping around. Only then do the keyboard and the navigation buttons shown on hover wrap too. The selected header is underlined with an accent bar. A change of selection flies the old content out and the new one in from the side the selection moved to. The header row is the tab stop. Left/Right select the previous/next item and Home/End the first/last. Ctrl+Tab, Ctrl+Shift+Tab, Ctrl+PageDown and Ctrl+PageUp step the selection from anywhere inside. ui::Sections is the declarative form.

Example:

window.set_content(ui::Sections{
    .title      = std::string{"EMAIL"},
    .min_height = 400.0f,
    .on_selection_changed = [](std::size_t index) { /* e.g. mark items read */ },
    .items = {
        ui::Section{.header = "All",     .content = ui::Text{.text = "all emails go here."}},
        ui::Section{.header = "Unread",  .content = ui::Text{.text = "unread emails go here."}},
        ui::Section{.header = "Flagged", .content = ui::Text{.text = "flagged emails go here."}},
    },
});

// Imperatively:
auto sections = std::make_unique<ui::SectionsControl>();
sections->set_title("EMAIL");
ui::TextControl* all = sections->add_item("All", std::make_unique<ui::TextControl>("all emails"));
sections->add_item("Unread", std::make_unique<ui::TextControl>("unread emails"));
sections->on_selection_changed([](std::size_t index) { /* ... */ });
sections->set_selected_index(1);

SectionsControl

SectionsControl() = default

An empty Sections: no title, no items, carousel enabled.

add_item

template <typename T>
T* add_item(std::string header, std::unique_ptr<T> content)

Appends an item, taking ownership of its content, and returns a non-owning observer to the content. The first item added becomes selected.

add_item

void add_item(SectionsControlItem item)

Appends item, taking ownership of its content (which may be null for a header-only item). The first item added becomes selected.

item_count

std::size_t item_count() const

The number of items added.

const std::string& header(std::size_t index) const

The header text of item index; an empty string when out of range.

title

const std::string& title() const

The title above the header row; empty collapses the title row.

set_title

void set_title(std::string title)

Sets the title, updates the layout as the title row appears or collapses, and redraws.

selected_index

std::optional<std::size_t> selected_index() const

The selected item; unset while there are no items.

set_selected_index

void set_selected_index(std::size_t index)

Selects item index, clamped to the items. Does nothing, and fires nothing, if the item is already selected.

on_selection_changed

void on_selection_changed(Callback<void(std::size_t)> handler)

Fired with the new index whenever the selection changes, by click, keyboard, the navigation buttons or set_selected_index(). Replaces any previous handler.

bool header_carousel_enabled() const

Whether the headers form a carousel, true by default. Headers that do not fit then rotate so the selected one leads. False keeps them in order and scrolls the row to the selection.

void set_header_carousel_enabled(bool enabled)

Updates the layout and redraws.

set_left_header_content

template <typename T>
T* set_left_header_content(std::unique_ptr<T> element)

An element left of the headers. Takes ownership (replacing any previous one) and returns a non-owning observer.

set_right_header_content

template <typename T>
T* set_right_header_content(std::unique_ptr<T> element)

Like set_left_header_content(), for the right end of the header row.

static_headers

bool static_headers() const

Whether the headers all fit, so they stay in place instead of rotating.

header_bounds

std::optional<Rect> header_bounds(std::size_t index) const

The header of item index as drawn, or nullopt if it is clipped out of the row.

header_row

Rect header_row() const

The header row's rect.

content_bounds

Rect content_bounds() const

The area the selected item's content is arranged in, below the header row.

previous_button_bounds

std::optional<Rect> previous_button_bounds() const

The navigation buttons, while they show.

next_button_bounds

std::optional<Rect> next_button_bounds() const

Like previous_button_bounds(), for the next button.

handle_shortcut

bool handle_shortcut(const KeyEvent& e) override

Ctrl+Tab / Ctrl+Shift+Tab and Ctrl+PageDown / Ctrl+PageUp, from anywhere inside the Sections.

focused

bool focused() const

Whether the header row has keyboard focus.

focusable

bool focusable() const override

The header row is a tab stop while enabled and non-empty.

takes_focus_at

bool takes_focus_at(Point p) const override

Only a press on the header row focuses the Sections. The selected item's content takes focus on its own.

collect_focusables

void collect_focusables(std::vector<Element*>& out) override

Tab order: the left header, the header row, the right header, then the selected content.

for_each_child

void for_each_child(ChildVisitor visit) const override

Lists the left header, the right header and the selected item's content. An unselected item's content is set aside, and nothing reaches it until it is selected again.

automation_role

AutomationRole automation_role() const override

Exposed to accessibility tools as a tab control whose value is the selected header.

measure_override

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

Implement these instead of measure() and arrange(). The base class has already applied margin, size limits and alignment by the time these run. measure_override() is pure (every concrete Element must size itself); arrange_override() defaults to a no-op since most leaves need nothing beyond having bounds() set, which the base already did.

arrange_override

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

Position children within the rect arrange() settled on (also in bounds(), window DIPs). Default: nothing, for a leaf.

render_override

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

What the control draws, in layout coordinates; render() has already applied the visual layer. A container draws its children through their render(), never their render_override().

pointer_override

bool pointer_override(const PointerEvent& e) override

A click on a header selects it, and a click on a navigation button moves by one. Other input goes to the selected content.

hit_test_children

void hit_test_children(Point p, std::vector<Element*>& out) const override

The children p (in this element's layout coordinates) is tested against, topmost first. Default: what for_each_child() lists, last first. A container that draws in another order (Panel's z-index), a control with parts for_each_child() does not list, or one whose parts cover others in places (a pane drawn over the content) lists them here.

clear_dirty_override

void clear_dirty_override(DirtyFlags flags) override

Also clears the content still flying out after a selection change.

tick_override

void tick_override(Duration delta) override

Advances the selection-change fly-in/fade animation.

enabled_changed

void enabled_changed() override

is_enabled() flipped, through set_enabled() here or on an ancestor, or a move to another parent. Drop any press or hover state. The element is redrawn either way. No-op by default.

key_override

bool key_override(const KeyEvent& e) override

Left/Right select the previous/next item, Home/End the first/last.

focus_changed

void focus_changed(bool focused) override

Keyboard focus arrived (true) or left (false); is_focused() already says so. The element is redrawn either way. No-op by default.

focus_ring_rect

Rect focus_ring_rect(const Theme&) const override

Around the header row, square.

focus_ring_corner_radius

float focus_ring_corner_radius(const Theme&) const override

The ring's corner radius: the theme's control radius by default.

ui::Section

One section: a header and the content shown while it is selected. Written inside the .items of a ui::Sections.

Field Type Default
header std::string The item's header text in the header row.
content Spec What the Sections shows while this item is selected.

struct SectionsControlItem

One section: a header and the content shown while it is selected. Passed to SectionsControl::add_item(), which takes ownership of the content.

Field Type Default
header std::string The header text in the header row.
content std::unique_ptr<Element> Shown below the headers while the item is selected.

Tests

  • tests/test_sections.cpp