-
Notifications
You must be signed in to change notification settings - Fork 9
Design Virtualization
Two factories, two controllers. Both are closure-driven: the caller hands the factory a row closure and the renderer wraps it in gpui::list or gpui::uniform_list.
| Factory | Controller | Wraps | Trade-off |
|---|---|---|---|
virtual_list(id, &controller, cx) |
VirtualListController |
gpui::ListState |
Variable-height rows; supports on_visible_range_change. |
uniform_virtual_list(id, count, &controller, cx) |
UniformVirtualListController |
gpui::UniformListScrollHandle |
Same-height rows only; ~10× faster for very large lists. No on_visible_range_change. |
The uniform variant does not own the item count — pass it to uniform_virtual_list(...) each frame.
use yororen_ui::headless::virtual_list::{UniformVirtualListController, VirtualListController};
let controller = VirtualListController::with_default(0);
// or: VirtualListController::new(count, gpui::ListAlignment::Top, px(16.))
let uniform = UniformVirtualListController::new();| Method | Purpose |
|---|---|
reset(element_count) |
Replace the total item count (after bulk add/remove). |
splice(old_range, count) |
Replace a range with count new items. |
append(n) |
Append n items at the tail; preserves scroll position. Right call for infinite loading. |
scroll_to_reveal_item(ix) |
Make item ix visible. (UniformVirtualListController exposes scroll_to_item instead.) |
scroll_to_top() / scroll_to_bottom()
|
Jump to the ends. |
FnMut(usize, &mut Window, &mut App) -> AnyElement + 'static. The closure runs inside a row-scoped element namespace, so child ids collide freely across rows.
virtual_list("rows", &controller, cx)
.row(move |ix, _w, cx| {
let item = &items[ix];
list_item(("task-list", item.id), &item.title, cx).render(cx).into_any_element()
})
.render(cx)virtual_list exposes .on_visible_range_change(F) for lazy load. The renderer hands it to gpui::ListState::set_scroll_handler.
Don't call controller.reset(...) from inside the callback. ListState is Rc<RefCell<…>> and the scroll path already borrows it. Resetting would re-enter the RefCell and panic. Bump a count in your entity instead and call controller.reset(...) at the top of the next render frame.
let entity_for_range = cx.entity().clone();
virtual_list("feed", &controller, cx)
.row(move |ix, _w, cx| /* per-row element */)
.on_visible_range_change(move |range, total, _w, cx| {
entity_for_range.update(cx, |s, _| {
if range.end + 50 >= total && s.feed_count < 10_000 {
s.feed_count += 100;
}
});
})
.render(cx)-
virtual_list— variable-height rows (rows with different content sizes, expand/collapse, async-loaded details). -
uniform_virtual_list— fixed-height rows (chat bubbles, log lines, settings rows). Significantly faster becausegpui::uniform_listmeasures only the first row.
- Keyed state — per-row element namespace isolation.
- Recipe: search + results list.
- VirtualList widget.
Yororen UI v0.3.0 · repository · Apache-2.0 · This wiki documents Yororen UI v0.3.0.
This wiki documents Yororen UI v0.3.0 — the headless-core, swappable-renderer build.