Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

virtual/sparse scrolling / models #51

Open
dhardy opened this issue Mar 13, 2023 · 0 comments
Open

virtual/sparse scrolling / models #51

dhardy opened this issue Mar 13, 2023 · 0 comments

Comments

@dhardy
Copy link
Contributor

dhardy commented Mar 13, 2023

From the blog post:

An especially difficult challenge in UI toolkits is sparse scrolling, where there is the illusion of a very large number of child widgets in the scroll area, but in reality only a small subset of the widgets outside the visible viewport are materialized. I am hopeful that the tight coupling between view and associated widget, as well as a lazy callback-driven creation of widgets, will help with this, but again, we won’t know for sure until it’s built.

This is something I've implemented, and with great performance, but it's complicated and has several challenges. A few things which occur to me...

Data list, key

Input data must be some type of list indexed by some type of key. It might be tempting just to use &mut [Item], but:

  • This does not work well with filters (API would require creating a Vec of filtered items).
  • This doesn't support generating items on demand from the key, or look-ups from an external database.

It is useful to be able to filter a view without affecting keys. The current solution in KAS is essentially that the data model may create an iterator over keys, and supports retrieving a single element via key.

Stable Id as a path

It can be desirable to give children a stable Id even when scrolled out-of-view and back. It may also be useful to support resolving an Id even when the target is not currently in-view. This is possible...

  • The parent (container) has an Id.
  • As noted above, each child item should have a Key (stable even when using only a sub-set of items aka a filter). The Key type may be known only to this container, thus the container may need to map it to an index (or sequence of indices).

Putting these together, we have a path which is stable (reproducible for items when removed from the view). This path can be resolved to a child data item even when its widget is not realised.

So, use the path as an Id? This isn't as crazy as it sounds: so long as each "child index" is small (not some random or globally unique integer) the path will compress well. This implementation stores up to 14 indices in a single u64 representation, reverting to a ref-counted heap-allocated Box<[usize]> when necessary (almost never for most UIs). The main catch is that it is not Copy.

Edit: rewrote this section

Maintaining focus when scrolling out-of-view

Druid and Xilem track navigation focus internally (within widget/pod data). Problem: if scrolling something out-of-view such that the widget is dropped, the focus is dropped. Workarounds:

  • Maintain many more widgets than required by the visible display to allow scrolling a limited distance without losing focus
  • Maintain widget repr if it has focus (may not be practical)
  • Track focus externally. For focus alone this is easy (it's just a path), but this is not a complete solution (e.g. is text selection also tracked externally?).

Edit: rewrote this section

Item views

A method is required to build a view from a data item; ideally something like FnMut(&Key, &mut Item) -> V.

Scrolling

Not just a draw offset

Scrolling and resizing may require new data be loaded. Since data is provided by the view not the widget the easiest solution is probably to request a repaint (construct a new view tree). The good news is that this won't be necessary on every small motion.

Tab navigation

Issue: pressing Tab (or Down etc.) may cause focus to move to a new child which has not been loaded yet. Hacky solutions:

  • Load new widgets in the key handling logic (but this conflicts with Xilem's clean separation of View and Widget)
  • Ensure the next/previous child is always loaded (even where this may be at the other end of the list). Difficult because there are quite a few possible "next" targets; over a 2D model this may wrap to the next line and keys like Tab/Down/Right/PageDown/End may all resolve different targets.
  • Scroll, construct a new view tree, and try again

This particular issue is a pain in KAS (where the first solution is possible); I suspect it will be difficult to get right in Xilem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant