Tabs¶
Header: winui/ui/navigation/Tabs.hpp. Spec: winui/ui/navigation/Tabs.hpp.
A strip of tabs over the selected tab's content.
The view owns every tab's content but only measures, arranges and renders the selected tab's content. Each tab has a key (Tab::key, else its header) that the events report and that stays with it when it is dragged elsewhere. The close button, a middle click and Ctrl+F4 remove the tab and select its neighbor. With an on_tab_close_requested() handler they raise that instead, and the handler decides, calling remove_tab() to close it. The "+" button shows once there is an on_add_tab_button_click() handler, unless set_add_tab_button_visible() says otherwise. Tabs that do not fit scroll, and dragging a tab reorders the strip. The declarative form is ui::Tabs.
Example:
auto owned = std::make_unique<ui::TabsControl>();
auto* tabs = owned.get();
tabs->add_tab("Home", std::make_unique<ui::TextControl>("Start here"));
tabs->add_tab(ui::Tab{ .header = "Document 1",
.key = "doc-1",
.icon = ui::IconGlyph{0xE8A5},
.content = std::make_unique<ui::TextControl>("Hello") });
tabs->set_closable(0, false);
tabs->on_add_tab_button_click([tabs] {
tabs->add_tab("New document", std::make_unique<ui::TextControl>("Empty"));
tabs->set_selected_index(tabs->tab_count() - 1);
});
tabs->on_selection_changed([](const std::string& key) { std::println("tab {}", key); });
window.set_content(std::move(owned));
ui::Tabs¶
A tab strip over swappable content. Builds a ui::TabsControl. The first tab is selected unless .selected_key names another. A tab's close button removes it, unless .on_tab_close_requested is set. Then the handler decides, and closes the tab by calling remove_tab() through .control. The "+" shows once .on_add_tab_button_click is set.
Example:
ui::Control<ui::Tabs> tabs;
ui::Property<std::string> current;
int documents = 1;
window.set_content(ui::Tabs{
.selected_key = current, // "home", then whichever tab the user picks
.tab_width_mode = ui::TabsWidthMode::SizeToContent,
.on_tab_close_requested = [&](const std::string& key) { tabs->remove_tab(key); },
.on_add_tab_button_click = [&] {
tabs->add_tab(ui::Tab{.header = "New document", .key = std::format("doc-{}", ++documents)});
tabs->set_selected_index(tabs->tab_count() - 1);
},
.control = tabs,
.items = {
ui::Tab{ .header = "Home", .key = "home", .closable = false, .content = ui::Text{ .text = "Start here" } },
ui::Tab{ .header = "Document 1", .key = "doc-1", .content = ui::Text{ .text = "Hello" } },
},
});
Every field, in the order a designated initializer names them:
ui::Tabs{
.selected_key, .tab_width_mode, .close_button_overlay_mode, .add_tab_button_visible,
.can_reorder_tabs, .strip_background, .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_tab_close_requested,
.on_add_tab_button_click, .on_tabs_changed, .on_pointer_entered, .on_pointer_exited,
.on_got_focus, .on_lost_focus, .on_key_down, .on_wheel, .control, .items,
.tab_strip_header_content, .tab_strip_footer_content
}
Fields, in declaration order (designators must follow it):
| Field | Type | Default | |
|---|---|---|---|
selected_key |
ui::TwoWayField<std::string> |
The selected tab's key. Empty, or a key no tab has, leaves the first tab selected. Bound two-way, the property then takes the key of the tab that is selected. | |
tab_width_mode |
ui::Binding<ui::TabsWidthMode> |
{ui::TabsWidthMode::Equal} |
How the tabs share the strip: Equal (clamped to 100..240 DIPs), SizeToContent, or Compact. |
close_button_overlay_mode |
ui::Binding<ui::TabsCloseButtonOverlayMode> |
{ui::TabsCloseButtonOverlayMode::Auto} |
Which closable tabs show their close button. |
add_tab_button_visible |
ui::Binding<std::optional<bool>> |
Shows the "+" after the last tab. Unset shows it when .on_add_tab_button_click is set. |
|
can_reorder_tabs |
ui::Binding<bool> |
{true} |
Dragging a tab moves it within the strip. |
strip_background |
ui::Binding<ui::Paint> |
The strip's fill: a fixed color or a theme resource; unset is transparent. | |
on_selection_changed |
ui::Callback<void(const std::string&)> |
Called with the new tab's key whenever the selection moves. | |
on_tab_close_requested |
ui::Callback<void(const std::string&)> |
Called with the tab's key on its close button, a middle click or Ctrl+F4. With a handler, nothing is removed until it calls ui::TabsControl::remove_tab(). Without one, the tab closes. | |
on_add_tab_button_click |
ui::Callback<void()> |
Called when the "+" is clicked; the handler adds the tab. Setting it shows the "+". | |
on_tabs_changed |
ui::Callback<void()> |
Called when a tab is inserted, removed or moved. | |
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::Tabs> |
Out: the handle that receives the built control. | |
items |
ui::TabList |
The initial tabs, in strip order. | |
tab_strip_header_content |
ui::Spec |
An element left of the tabs. | |
tab_strip_footer_content |
ui::Spec |
An element in the space right of the add button. |
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::TabsControl; the caller owns it.
ui::Tab¶
One tab: a header, an optional icon, and the content shown while it is selected. Written inside the .items of a ui::Tabs, or handed to TabsControl::add_tab(). The content is a spec or an already built std::unique_ptr<Element>.
Example:
auto tabs = std::make_unique<ui::TabsControl>();
tabs->add_tab(ui::Tab{ .header = "Document 1", .key = "doc-1", .icon = ui::IconGlyph{0xE8A5},
.content = ui::Text{ .text = "Hello" } });
tabs->add_tab(ui::Tab{
.header = "Settings",
.key = "settings",
.icon = ui::IconGlyph{0xE713},
.closable = false,
.content = std::make_unique<ui::TextControl>("Settings go here"),
});
Every field, in the order a designated initializer names them:
Fields, in declaration order (designators must follow it):
| Field | Type | Default | |
|---|---|---|---|
header |
ui::Binding<std::string> |
The tab's caption, and its tooltip. A bound header follows its property. A tab without .key keeps the key made from its first header. |
|
key |
std::string |
What identifies the tab in .selected_key and the close and selection events. Empty takes the header, numbered " (2)", " (3)", ... after the first tab with it. It stays with the tab when the tab is dragged elsewhere or renamed. |
|
icon |
ui::IconSource |
The icon before the header in its 16-DIP box: a Symbol, a glyph or a path in the header's color, or a picture in its own colors. Empty shows none. | |
closable |
bool |
true |
Shows the close button and accepts middle click and Ctrl+F4. |
content |
ui::Spec |
What the Tabs shows while this tab is selected; owned by the Tabs once added. |
ui::TabList¶
A built, owned list of tabs. A braced list of ui::Tab{...} converts to it, building each tab's content as it goes.
Example:
ui::TabList tabs = { ui::Tab{ .header = "One", .content = ui::Text{ .text = "1" } },
ui::Tab{ .header = "Two", .content = ui::Text{ .text = "2" } } };
window.set_content(ui::Tabs{ .items = std::move(tabs) });
Every field, in the order a designated initializer names them:
Fields, in declaration order (designators must follow it):
| Field | Type | Default | |
|---|---|---|---|
entries |
std::vector<Entry> |
The tabs, in strip order. |
operator=¶
operator=¶
add¶
Appends one tab, building its content, for a list put together in a loop.
struct Entry¶
One tab with its content already built.
| Field | Type | Default | |
|---|---|---|---|
header |
Binding<std::string> |
||
node |
std::unique_ptr<Element> |
||
icon |
IconSource |
||
closable |
bool |
true |
|
key |
std::string |
ui::TabsControl¶
Derives from ui::Element, ui::ValueProvider, ui::SelectionProvider.
A strip of tabs over the selected tab's content.
The view owns every tab's content but only measures, arranges and renders the selected tab's content. Each tab has a key (Tab::key, else its header) that the events report and that stays with it when it is dragged elsewhere. The close button, a middle click and Ctrl+F4 remove the tab and select its neighbor. With an on_tab_close_requested() handler they raise that instead, and the handler decides, calling remove_tab() to close it. The "+" button shows once there is an on_add_tab_button_click() handler, unless set_add_tab_button_visible() says otherwise. Tabs that do not fit scroll, and dragging a tab reorders the strip. The declarative form is ui::Tabs.
Example:
auto owned = std::make_unique<ui::TabsControl>();
auto* tabs = owned.get();
tabs->add_tab("Home", std::make_unique<ui::TextControl>("Start here"));
tabs->add_tab(ui::Tab{ .header = "Document 1",
.key = "doc-1",
.icon = ui::IconGlyph{0xE8A5},
.content = std::make_unique<ui::TextControl>("Hello") });
tabs->set_closable(0, false);
tabs->on_add_tab_button_click([tabs] {
tabs->add_tab("New document", std::make_unique<ui::TextControl>("Empty"));
tabs->set_selected_index(tabs->tab_count() - 1);
});
tabs->on_selection_changed([](const std::string& key) { std::println("tab {}", key); });
window.set_content(std::move(owned));
TabsControl¶
An empty view: no tabs, and no add button until there is an add handler.
~TabsControl¶
TabsControl¶
operator=¶
add_tab¶
Appends a tab, taking ownership of its content, and returns a non-owning observer to the content. The first tab added becomes selected.
add_tab¶
Appends a tab, building its content; returns the content as an observer (may be null).
insert_tab¶
Inserts a tab at index (clamped), building its content; returns the content as an observer (may be null). The selection stays on the tab it was on; the first tab ever added is selected.
remove_tab¶
Removes the tab at index and destroys its content. Removing the selected tab selects the one now at its index, or the last.
remove_tab¶
Removes the first tab with key, as remove_tab(index_of(key)); no-op when there is none.
move_tab¶
Moves a tab to another index, as dragging it does.
tab_count¶
The number of tabs.
header¶
The header of the tab at index; empty when out of range.
key¶
The key of the tab at index; empty when out of range.
index_of¶
The index of the first tab with key; unset when there is none.
set_header¶
Renames the tab at index (ignored out of range) and updates the strip's layout.
set_icon¶
Sets or clears the icon of the tab at index and updates the strip's layout.
closable¶
Whether the tab at index can be closed.
set_closable¶
Shows or hides the tab's close button (and its middle-click and Ctrl+F4 close).
content¶
The tab's content, or null.
selected_index¶
The selected tab; unset while there are no tabs.
set_selected_index¶
Selects the tab at index, clamped to the tab list. Does nothing, and fires nothing, if the tab is already selected.
selected_key¶
The selected tab's key; empty while there are no tabs.
set_selected_key¶
Selects the first tab with key. Does nothing, and fires nothing, when no tab has it or it is already selected.
on_selection_changed¶
Fired with the new tab's key whenever the selection changes: a click, Enter/Space, Ctrl+Tab, set_selected_index(), set_selected_key(), or a removal that moves it. The key is empty once the last tab is gone.
tab_width_mode¶
How the tabs share the strip; Equal by default.
set_tab_width_mode¶
Changes the width mode and updates the layout.
close_button_overlay_mode¶
Which closable tabs show their close button; Auto by default.
set_close_button_overlay_mode¶
Changes the overlay mode; redraws.
is_add_tab_button_visible¶
Whether the "+" after the last tab shows: the value set, else whether there is an on_add_tab_button_click() handler.
set_add_tab_button_visible¶
Shows or hides the add button; std::nullopt (the default) shows it while there is an add handler. Updates the layout.
can_reorder_tabs¶
Whether dragging a tab moves it within the strip; true by default.
set_can_reorder_tabs¶
Allows or forbids reordering by drag.
strip_background¶
The strip's fill: a fixed color or a theme resource, transparent by default. The content below it is not filled.
set_strip_background¶
Sets or clears the strip's fill; redraws.
set_tab_strip_header_content¶
The strip header, an element left of the tabs. Takes ownership and returns a non-owning observer.
set_tab_strip_footer_content¶
The strip footer, an element in the space right of the add button; as set_tab_strip_header_content().
on_tab_close_requested¶
Fired with the tab's key when its close button, a middle click or Ctrl+F4 asks to close it. With a handler nothing is removed until it calls remove_tab(); without one the tab is removed and its neighbor selected.
on_add_tab_button_click¶
Fired when the "+" is clicked. Adding a tab is the handler's job. Setting one shows the "+" unless set_add_tab_button_visible() set it explicitly.
on_tabs_changed¶
Fired when a tab is inserted, removed or moved.
strip_bounds¶
The tab strip's rect.
content_bounds¶
The selected tab's content area, below the strip.
tab_bounds¶
A tab's slot in the strip (scrolled), or nullopt when it is scrolled out of view.
close_button_bounds¶
A tab's close button, or nullopt when it shows none.
scroll_decrease_bounds¶
The scroll buttons, or nullopt while the tabs fit.
scroll_increase_bounds¶
scroll_offset¶
How far the strip is scrolled, in DIPs (0 is the first tab at the left).
add_button¶
The "+" button (owned by the view).
handle_shortcut¶
Ctrl+Tab, Ctrl+Shift+Tab and Ctrl+F4 anywhere inside the view, then any shortcuts added with add_shortcut().
focused¶
Whether the strip has keyboard focus.
focusable¶
The strip is one tab stop while enabled and not empty.
takes_focus_at¶
Only a press on a tab focuses the strip. The selected content takes focus on its own.
collect_focusables¶
The tabs (this), the strip header, the add button, the strip footer, then the selected tab's content.
for_each_child¶
Lists the tooltips, the strip header and footer, and the selected tab's content. An unselected tab's content is set aside, and nothing reaches it until it is selected again.
sync_native¶
Passes native-control hosting on to the strip parts and the selected tab's content.
automation_children¶
The strip header, one tab item per tab, the add button, the strip footer, then the selected tab's content. The control also exposes the selected header as a read-only value.
automation_focus¶
The tab the keyboard is on, so a screen reader reads it as the arrow keys move.
automation_role¶
A Tab control.
measure_override¶
Fills available on whichever axes are bounded.
arrange_override¶
Lays out the strip (tab widths, scroll buttons) and the selected tab's content below it.
render_override¶
Draws the strip and its tabs, then the selected tab's content.
pointer_override¶
Clicks select and close tabs, a middle click asks to close, and a drag reorders. Other pointer events reach the strip header/footer and the selected content.
wheel_override¶
A turn over an overflowing strip scrolls the strip. Otherwise the turn reaches the tab's content through handle_wheel(), so a scrollable inside the tab wins over one wrapping the whole Tabs.
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.
tick_override¶
Repeats a held scroll button.
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.
key_override¶
Left/Right move the keyboard cursor between tabs; Enter/Space select it.
focus_changed¶
Called by the Window as focus arrives or leaves; redraws the focused tab.
focus_ring_rect¶
Inside the focused tab, cut to the part of it the strip shows.
focus_ring_corner_radius¶
The ring's corner radius: the theme's control radius by default.
focus_ring_inside¶
Whether the ring goes inside focus_ring_rect() in every era, its outer edge on the rect. False by default, which leaves it to the theme. An item inside a viewport (a list row, a tab, a menu item) returns true, as does a control in a tight spot, so a neighbor or the viewport edge never cuts its ring.
Enumerations¶
TabsWidthMode¶
How a Tabs control sizes its tabs.
| Value | |
|---|---|
Equal |
The strip divided evenly, each tab clamped to 100..240 DIPs (the default). |
SizeToContent |
Each tab as wide as its icon, header and close button. |
Compact |
Unselected tabs shrink to their icon. |
TabsCloseButtonOverlayMode¶
When a closable tab shows its close button.
| Value | |
|---|---|
Auto |
the same as Always |
OnPointerOver |
on the selected tab and the hovered one |
Always |
on every closable tab |
Tests¶
tests/test_tabs.cpp