Expander¶
Header: winui/ui/containers/Expander.hpp. Spec: winui/ui/containers/Expander.hpp.
A header row that shows or hides one piece of content below or above it.
The header is the control's single tab stop. A click on it or Space toggles expanded(). Enter doesn't, since it steps into the Expander when it is a focus scope. While collapsed, the content isn't part of the tree at all. It isn't arranged or drawn, and it is out of the tab order and the automation tree. Opening and closing slide the content out from behind the header, clipped, in 333 ms when opening and 167 ms when closing. The control's own height changes at once. set_content() takes ownership of the content and returns a non-owning observer.
Example:
// Declarative:
ui::Property<bool> open{false};
window.set_content(ui::Expander{
.header = "Advanced options",
.expanded = open,
.vertical_alignment = ui::Alignment::Leading,
.content = ui::VerticalStack{
.spacing = 8,
.content = {
ui::CheckBox{.text = "Verbose logging"},
ui::CheckBox{.text = "Keep temporary files"},
},
},
});
// Imperative:
auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* expander = stack->add(std::make_unique<ui::ExpanderControl>("Details"));
expander->set_content(std::make_unique<ui::TextControl>("Shown while expanded"));
expander->on_expanded_changed([&](bool now) { open = now; });
expander->set_expanded(true); // fires the handler
ui::Expander¶
A collapsible header over one child. Builds a ui::ExpanderControl.
.expanded = prop is two-way, which keeps the state in a model. When the user expands or collapses it, the property is written before on_expanded_changed runs.
Example:
ui::Property<bool> open{false};
window.set_content(ui::Expander{
.header = "Advanced options",
.expanded = open,
.vertical_alignment = ui::Alignment::Leading,
.content = ui::VerticalStack{.spacing = 8, .content = {
ui::CheckBox{.text = "Verbose logging"},
ui::CheckBox{.text = "Experimental renderer"},
}},
});
Every field, in the order a designated initializer names them:
ui::Expander{
.header, .expanded, .expand_direction, .content_padding, .style, .margin, .width, .height,
.min_width, .min_height, .max_width, .max_height, .horizontal_alignment,
.vertical_alignment, .visible, .tool_tip, .enabled, .accessibility, .on_expanded_changed,
.on_pointer_entered, .on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down,
.on_wheel, .control, .header_content, .content
}
Fields, in declaration order (designators must follow it):
| Field | Type | Default | |
|---|---|---|---|
header |
ui::Binding<std::string> |
The header text. See header_content for richer headers. |
|
expanded |
ui::TwoWayField<bool> |
{false} |
Whether the content shows. |
expand_direction |
ui::Binding<ui::ExpandDirection> |
{ui::ExpandDirection::Down} |
Down, the default, puts the content below the header, and Up above it. |
content_padding |
ui::Binding<ui::Thickness> |
{ui::Thickness{16.0f}} |
The inset around the content inside the open content area, 16 DIPs by default (ui::ExpanderControl::set_content_padding()). |
on_expanded_changed |
ui::Callback<void(bool)> |
Fires with the new state whenever it changes, through a header click, Space or a write to the bound property. | |
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::Expander> |
Out: the handle that receives the built control. | |
header_content |
ui::Spec |
Header content in place of the header text (ui::ExpanderControl::set_header_content()). |
|
content |
ui::Spec |
The collapsible content. |
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¶
Consumes the spec and returns the built ui::ExpanderControl; the caller owns it.
ui::ExpanderControl¶
Derives from ui::Element, ui::ExpandCollapseProvider.
A header row that shows or hides one piece of content below or above it.
The header is the control's single tab stop. A click on it or Space toggles expanded(). Enter doesn't, since it steps into the Expander when it is a focus scope. While collapsed, the content isn't part of the tree at all. It isn't arranged or drawn, and it is out of the tab order and the automation tree. Opening and closing slide the content out from behind the header, clipped, in 333 ms when opening and 167 ms when closing. The control's own height changes at once. set_content() takes ownership of the content and returns a non-owning observer.
Example:
// Declarative:
ui::Property<bool> open{false};
window.set_content(ui::Expander{
.header = "Advanced options",
.expanded = open,
.vertical_alignment = ui::Alignment::Leading,
.content = ui::VerticalStack{
.spacing = 8,
.content = {
ui::CheckBox{.text = "Verbose logging"},
ui::CheckBox{.text = "Keep temporary files"},
},
},
});
// Imperative:
auto stack = std::make_unique<ui::StackControl>(ui::Orientation::Vertical);
auto* expander = stack->add(std::make_unique<ui::ExpanderControl>("Details"));
expander->set_content(std::make_unique<ui::TextControl>("Shown while expanded"));
expander->on_expanded_changed([&](bool now) { open = now; });
expander->set_expanded(true); // fires the handler
ExpanderControl¶
A collapsed Expander with an empty text header and no content.
ExpanderControl¶
An Expander with header as its text header, starting expanded or collapsed as expanded says. Doesn't fire on_expanded_changed().
header¶
The header text, drawn in the header row unless header_content() replaces it.
set_header¶
Changes the header text. Updates layout.
set_header_content¶
Sets any element as header content, drawn in the header row in place of the text and sized like it. The row stretches it, so the element's own alignment places it within the row. It is part of the header button, so it gets no input of its own, and a click on it toggles the Expander. nullptr goes back to the text header. Returns a non-owning observer.
header_content¶
The header content set with set_header_content(), or nullptr for a text header. Owned by the Expander.
expand_direction¶
Which side the content opens on. Up puts the content above the header, rounds the header's bottom corners only while open, slides the content up from behind the header and turns the chevron around.
set_expand_direction¶
Changes the direction. Updates layout.
set_content¶
Takes ownership of the collapsible content and returns a non-owning observer, like ScrollControl::set_child(). Replaces and destroys any previous content. nullptr clears it.
content¶
The collapsible content, or nullptr before set_content(). Owned by the Expander.
expanded¶
Whether the content region is open or opening.
set_expanded¶
Opens or closes the content region and starts the slide. Fires on_expanded_changed() when the value changes. Updates layout.
on_expanded_changed¶
Fired whenever expanded changes, through a header click, Space or set_expanded().
content_padding¶
The inset between the edges of the open content area and the content inside it, 16 DIPs by default. A dense list inside an Expander, such as a navigation pane, needs far less room than card-like content does.
set_content_padding¶
Changes the content padding. Updates layout.
header_bounds¶
The header row's own rect from the most recent arrange(), for hit-testing the header.
content_bounds¶
The clipped area the content slides into, from the most recent arrange(). Zero height while fully collapsed.
slide_progress¶
How far the content is through the opening or closing slide, eased. 0 is fully hidden behind the header and 1 fully in place. Read it like FlyoutControl::transition_opacity().
The slide runs only in a host that ticks the control, such as ui::run()'s frame loop. Until the first tick, every set_expanded() jumps straight to the end value, so the content never stays stuck halfway behind the header.
focused¶
Whether the header has focus. The header itself is the focusable element. The focusable elements inside content() join the tab order only while expanded (see collect_focusables()).
focusable¶
The header is focusable while the Expander is enabled.
takes_focus_at¶
Only a press on the header row focuses the Expander. Presses on the content don't.
collect_focusables¶
The header, then the focusable elements inside the content while the content region exists and the Expander isn't a focus scope.
is_pressed¶
Whether the header is drawn pressed, because the pointer is down on it or Space is held.
for_each_child¶
Visits the content, but only while the content region exists. A collapsed Expander's content is skipped by walks over the tree, as it is for layout, rendering and input.
automation_role¶
Exposed to UI Automation as a group.
automation_name_fallback¶
The header text names the Expander when accessibility().name doesn't.
measure_override¶
The header row's height plus, while the content region exists, the content's desired height and the content padding.
arrange_override¶
Splits the bounds into the header row and the content area, as expand_direction() says, and places the content at its current slide offset.
pointer_override¶
A click on the header row toggles expanded(). A press reaches the content only inside the area it is drawn in (content_bounds()). Moves and releases always reach it.
hit_test_children¶
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.
render_override¶
Draws the header row and chevron, the content clipped to its area, then the focus ring.
clear_dirty_override¶
Clears the dirty flags of this control, of the content while it exists and of the header content.
tick_override¶
Advances the open/close slide, then ticks the content while it exists.
collect_scope_focusables¶
As a focus scope (Element::set_focus_scope(), off by default), the content leaves the window's tab order and gets a tab order of its own inside the Expander. Tab reaches the header, Enter steps into the content and Escape steps back out onto the header. Space toggles either way, so the keyboard can always collapse the group from its header.
enabled_changed¶
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.
focus_changed¶
Called by Window as focus reaches or leaves the header. Redraws.
key_override¶
Space toggles expanded() on key up. Enter does nothing here.
focus_ring_rect¶
The focus ring goes around the header only.
focus_ring_corner_radius¶
The ring's corner radius: the theme's control radius by default.
Enumerations¶
ExpandDirection¶
Which way an Expander opens.
| Value | |
|---|---|
Down |
The default. The content opens below the header. |
Up |
The content opens above the header, which stays at the bottom. |
Tests¶
tests/test_expander.cpp