VirtualList¶
Header: winui/ui/collections/VirtualListControl.hpp.
A list that scrolls itself and creates Elements only for the rows in view.
It asks its item factory for a fresh Element per visible index and drops rows as they scroll out, so a 100,000-item list holds about a dozen live controls. A Scroll, by contrast, keeps its whole child and clips it. The visible rows are created again on every arrange and scroll step, so keep per-row state in the caller's data, not in the row. The list scrolls with the wheel and an overlay scroll bar. TableView's body is one. All rows share item_height() unless set_size_items_to_content() lets each row be as tall as it measures. It has no spec. For a list in a declared tree use ui::ListView. Build this one by hand when rows of one height, an index and a factory are all a list needs.
Example:
std::vector<std::string> names(1000, "Row"); // outlives the window
auto list = std::make_unique<ui::VirtualListControl>();
list->set_item_height(36.0f);
list->set_item_factory([&names](std::size_t i) -> std::unique_ptr<ui::Element> {
return std::make_unique<ui::TextControl>(names[i]);
});
list->set_item_count(names.size());
ui::VirtualListControl* observer = window.set_content(std::move(list));
auto top_button = std::make_unique<ui::ButtonControl>("Top");
top_button->on_click([observer] { observer->set_scroll_offset(0.0f); }); // back to the top
ui::VirtualListControl¶
Derives from ui::Element, ui::ScrollProvider.
A list that scrolls itself and creates Elements only for the rows in view.
It asks its item factory for a fresh Element per visible index and drops rows as they scroll out, so a 100,000-item list holds about a dozen live controls. A Scroll, by contrast, keeps its whole child and clips it. The visible rows are created again on every arrange and scroll step, so keep per-row state in the caller's data, not in the row. The list scrolls with the wheel and an overlay scroll bar. TableView's body is one. All rows share item_height() unless set_size_items_to_content() lets each row be as tall as it measures. It has no spec. For a list in a declared tree use ui::ListView. Build this one by hand when rows of one height, an index and a factory are all a list needs.
Example:
std::vector<std::string> names(1000, "Row"); // outlives the window
auto list = std::make_unique<ui::VirtualListControl>();
list->set_item_height(36.0f);
list->set_item_factory([&names](std::size_t i) -> std::unique_ptr<ui::Element> {
return std::make_unique<ui::TextControl>(names[i]);
});
list->set_item_count(names.size());
ui::VirtualListControl* observer = window.set_content(std::move(list));
auto top_button = std::make_unique<ui::ButtonControl>("Top");
top_button->on_click([observer] { observer->set_scroll_offset(0.0f); }); // back to the top
ItemFactory¶
Builds the row for item index. The list takes ownership and destroys the row once it scrolls out of view.
VirtualListControl¶
An empty list: no factory, no items, 32-DIP rows.
set_item_factory¶
Sets the factory that returns the Element for any index in [0, item_count()). It is called for every visible row on each arrange and scroll step, so keep it cheap. Returning nullptr ends the realized rows there, for a source that shrank before set_item_count() caught up. That row and the ones after it stay empty. Replacing the factory rebuilds the rows on the next layout.
item_count¶
How many items the list scrolls over; 0 by default.
set_item_count¶
Changes the item count and lays the list out again. The scroll offset is clamped again on the next arrange. Forgets the heights the rows measured.
item_height¶
Every row's height in DIPs; 32 by default. Each row is arranged to exactly this height, whatever it measures. When the rows size to their content, it is the least height of a row and the estimate for a row not yet seen.
set_item_height¶
Changes the row height, lays the list out again and forgets the heights the rows measured.
size_items_to_content¶
Whether each row is measured with unbounded height and is as tall as it asks for, at least item_height(). Off by default. A row keeps the height it measured while it is out of view, so scrolling back finds it where it was. Rows never seen count as item_height().
set_size_items_to_content¶
Turns content-sized rows on or off and lays the list out again.
forget_item_heights¶
Forgets every height the rows measured and lays the list out again. Call it after a change that reflows the rows' content, such as a new column width. The list tracks its own width changes.
item_top¶
Where item index starts, in DIPs from the top of the content, leading space included.
item_extent¶
Item index's height. That is what it measured while the rows size to their content, and item_height() otherwise.
index_at¶
The item at y DIPs from the top of the content. Returns item_count() past the last item and 0 over the leading space.
leading_space¶
Room before the first item that scrolls with the content, where a ListView puts its header. The items start this far down the content, and the content is this much taller.
set_leading_space¶
Sets the leading space in DIPs, clamped to >= 0. Lays the list out again only on a change.
content_height¶
The content's height: the leading space and every item.
scroll_offset¶
How far the content is scrolled, in DIPs from the top (0 is the top).
set_scroll_offset¶
Sets the offset directly, for example to scroll to the top. Clamps it to the current content range and rebuilds the realized rows at once.
right_gutter¶
The width the vertical bar takes from the rows, decided by measure(). It is the bar's gutter in eras with a classic bar, such as Aero, while the content overflows. It is 0 for the overlay bar, which draws over the rows.
viewport_rect¶
Where the rows are shown: the bounds minus the right gutter.
scroll_info¶
Element::scroll_info(), the same as a Scroll's. It can be pulled a tenth of its viewport past either end.
first_visible_index¶
The item index of the first realized row. The realized items are [first_visible_index(), first_visible_index() + visible_count()). visible_item() takes an offset into that range. Meant for inspection and testing, not for driving the list from outside.
visible_count¶
How many rows are realized right now: the rows at least partly in view.
visible_item¶
The ith realized row; 0 is the row of first_visible_index(). Throws std::out_of_range past visible_count(). The list owns the row and destroys it when it scrolls out of view.
for_each_child¶
The realized rows, top first. Only these can take focus. Focus does not scroll a row into view.
automation_children¶
Reported to screen readers as a scrollable List without children, because only the visible rows exist as Elements.
automation_role¶
What screen readers and other assistive tools see of this node: the role, name, help text and children here, and what a client can do with the node through the providers automation_providers() lists (ui/Automation.hpp).
automation_role() is the role this node reports; Group by default.
measure_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¶
Position children within the rect arrange() settled on (also in bounds(), window DIPs). Default: nothing, for a leaf.
render_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¶
A pointer event for this element: the element under the pointer, one inside it that did not handle the event, or the element holding the capture. The position is in layout coordinates (handle_pointer()). Return true when the event did something here, and it goes no further up; a handled press also captures the pointer until its release. A control that acts on a press (a click, a toggle, a caret placed, a drag started) returns true for the press and its release, so a list row or a card around it does not act on the same click. Default: false.
wheel_override¶
A wheel turn over this element (handle_wheel()). Return true when it scrolled something here. Default: an element with no children hands it to pointer_override() and returns false; one with children returns false.
children_hit_clip¶
Where the children can be hit, in this element's layout coordinates, or nullopt for anywhere (the default): a scrolling control returns its viewport, so content scrolled out of view is not hit.
covers_children_at¶
Whether a part of this element drawn over its children is at p (a scroll bar over the content), so the element is hit there instead of a child. Default: false.
tick_override¶
Fades the scroll bar, then ticks the rows.
Tests¶
tests/test_layout.cpp