AUI Framework
develop
Cross-platform base for C++ UI apps
|
Customizable lists display. More...
#include <AUI/View/AForEachUI.h>
Used to lazily present possibly large or infinite linear non-hierarchical sequences of data.
AForEachUI is created by using AUI_DECLARATIVE_FOR macro.
AUI_DECLARATIVE_FOR mimics ranged for loop semantically.
AUI_DECLARATIVE_FOR consists of single entry variable name, a potentially reactive expression evaluating to range, layout name (acceptable are AVerticalLayout
and AHorizontalLayout
) and a lambda that creates a new view based on data entry. In terms of C++ syntax, the lambda is partially defined by AUI_DECLARATIVE_FOR macro; the lambda's body (including curly braces) is left up to developer. The final declaration of AUI_DECLARATIVE_FOR returns an instance of AForEachUI.
range models one-dimensional list.
AForEachUI works on iterator level by design. In fact, any kind of range (C++20 ranges/range-v3) can be used, starting from bidirectional containers such as std::vector
and std::list
, lazy non-owning dummies like ranges::views::ints
and even fancy range views from std::ranges::views
or ranges::views
are acceptable. One-directional containers like std::queue
or std::stack
can't be used because they don't implement begin()
and end()
. As such, requirements to a range are dictated by ranges::range
concept.
.begin()
method or ranges::begin()
overload defined auto it = ranges::begin(rng)
.end()
method or ranges::end()
overload defined auto it = ranges::end(rng)
begin()
and end()
return an iteratorauto& value = *it
++it
Alternatively, these requirements can be described by a ranged for loop: for (const auto& value : rng) { ... }
.
The range's type is erased with runtime-based range layer aui::any_view.
AUI_DECLARATIVE_FOR can be nested with no restrictions in both directions.
See examples of AUI_DECLARATIVE_FOR.
AForEachUI presents all data available. If placed somewhere inside AScrollArea (implies AScrollAreaViewport), lazy semantics take place. This means that AForEachUI knows scroll position and sliding window size in pixels, making it possible to present a limited set of data that is actually visible, and present data further as soon as the user scrolls down the scroll area.
Under the hood, AForEachUI stores a pair of iterators of the passed range of presented entries, forming a sliding window subrange. When the user scrolls down the list, both iterators are incremented; when the user scrolls upwards, both iterators are decremented.
In this scenario, AForEachUI adds an extra requirement to range's iterator:
--it
If this requirement is not satisfied (case of some ranges::views
), AForEachUI would not unload old items, unless a data update event occurred.
The amount of displayed data is governed by range size, Render to texture tile size, AScrollArea's viewport size and individual entry size. Optimal frequency of sliding during scroll and window size are determined by AForEachUI. In particular, the sliding is performed once per Render to texture tile is passed.
From perspective of layout, lazy semantics is implemented by careful layout updates driven by scroll area events. If possible, the items that appear far from sliding window are unloaded (views are removed). The new items are loaded (new views are instantiated). To avoid content jittering, scroll position is synced with layout updates within AForEachUI. As such, these hijacking operations may confuse scroll bar.
In modern software, especially when it comes to infinite lists in web/mobile applications (i.e., news feed), scrollbar might be completely hidden or significantly transparentized.
This optimization gives a severe performance benefit. Despite the fact that there's a complete mess "under the hood" (scrollbar is the only visual confirmation), the scrolled contents appear normal and natural.
A static range like in the example above will not blow up the machine because AForEachUI is wrapped with a AScrollArea, thus it is not required to evaluate the whole range, which is infinite in our case.
An attempt to update AForEachUI with a large range still can lead to high resource consumption, due to need of recomposition.
This section explains how to initialize AUI_DECLARATIVE_FOR, manage lifetime of containers and how to make them reactive.
In AUI_DECLARATIVE_FOR, a potentially reactive expression evaluating to range and the lambda that creates a new views are both lambdas with capture default by value [=]
. This means that:
Both lambdas can be evaluated at any point during lifetime of a AForEachUI, so the by-value capture makes it's hard to introduce dangling references, by either copying locals or referencing class members.
Most modern compilers are capable to optimize out copying and initialize *copied* locals just in place.
An attempt to go out of the scenarios listed below will likely lead to a static_assert
with a link to this section.
The most straightforward way is using constant global data:
It's a good idea to wrap AForEachUI with an AScrollArea.
Most generators, ranges and views are expected to work.
When using locals, their immediate values are copied during initialization of AUI_DECLARATIVE_FOR.
As such, an attempt to modify items
will not reflect on presentation, because it has own copy of items
.
If your container lives inside your class, its value is not copied but referenced. To avoid unobserved iterator invalidation and content changes, AUI_DECLARATIVE_FOR requires borrowed containers to be constant. There's a compile-time check to verify this requirement that does work in most cases, like this one.
Marking the borrowed container as const effectively saves you from unintended borrowed data changes. If you'd like to change the container or view options and AUI_DECLARATIVE_FOR to respond to the changes, read the section below.
The reason why AUI_DECLARATIVE_FOR is so restrictive about using borrowed non-const data is because it stores range's iterators under the hood. Various containers have different rules on iterator invalidation, but, since it accepts any type of range, we consider using its iterators after a modifying access to the container or a view as unsafe:
To address this issue, we provide a convenient way to make iterator invalidation observable:
AProperty
,mColors
in AUI_DECLARATIVE_FOR clause.Upon clicking "Add A new color" button, the "A new color" label will appear in the list.
> All members, including inherited
|
inlineoverrideprotectedvirtual |
Called by setModelImpl
. The implementation might then use the shared cache to return views from List
range.
The implementation is responsible to clean up the cache.
Implements AForEachUIBase.
|
inlineoverridevirtual |
Implements aui::react::DependencyObserver.