pub struct Controller<K> { /* private fields */ }Expand description
A framework-neutral controller that wraps a virtualizer::Virtualizer and provides common
adapter workflows (anchoring, tween-driven scrolling).
This type does not hold any UI objects. Adapters drive it by calling:
on_viewport_size/on_scrollwhen UI events occurtick(now_ms)each frame/timer tick (for tween scrolling andis_scrollingdebouncing)
For UI scroll containers (e.g. DOM), you can use the returned offset from tick() to set the
real scroll position, while keeping the virtualizer state in sync.
§Typical integration loop
- On UI scroll events: call
on_scroll(offset, now_ms)(cancels any active tween). - On UI resize/layout: call
on_viewport_size(main). - Each frame/timer: call
tick(now_ms); if it returnsSome(offset), apply it to the real UI.
Implementations§
Source§impl<K: VirtualizerKey> Controller<K>
impl<K: VirtualizerKey> Controller<K>
Sourcepub fn new(options: VirtualizerOptions<K>) -> Self
pub fn new(options: VirtualizerOptions<K>) -> Self
Examples found in repository?
3fn main() {
4 // Example: TanStack-like controller driving tween scrolling without holding any UI objects.
5 //
6 // An adapter would:
7 // - start a tween (e.g. in response to "scroll to index" command)
8 // - call tick(now_ms) in a frame loop / timer
9 // - apply the returned offset to the real scroll container (if any)
10 // - render using the virtualizer state
11 let mut c = Controller::new(virtualizer::VirtualizerOptions::new(10_000, |_| 1));
12 c.virtualizer_mut().set_viewport_size(20);
13 c.virtualizer_mut().set_scroll_offset(0);
14
15 let target = c.start_tween_to_index(
16 2_000,
17 virtualizer::Align::Center,
18 0,
19 240,
20 Easing::SmoothStep,
21 );
22 println!("target_offset={target}");
23
24 let mut now_ms = 0u64;
25 loop {
26 now_ms += 16;
27 if let Some(off) = c.tick(now_ms) {
28 if now_ms.is_multiple_of(80) {
29 println!(
30 "t={now_ms} off={off} visible={:?}",
31 c.virtualizer().visible_range()
32 );
33 }
34 } else {
35 break;
36 }
37 }
38
39 println!(
40 "done: off={} range={:?}",
41 c.virtualizer().scroll_offset(),
42 c.virtualizer().virtual_range()
43 );
44}More examples
3fn main() {
4 // Example: preserve visual scroll position across "prepend" (chat/timeline load older messages).
5 //
6 // The adapter flow is typically:
7 // 1) capture an anchor (key + offset_in_viewport) before data changes
8 // 2) apply data changes (count/key mapping)
9 // 3) apply the anchor to adjust scroll_offset so the same item stays in the same place
10 let mut c = Controller::new(virtualizer::VirtualizerOptions::new_with_key(
11 100,
12 |_| 1,
13 |i| 1000u64 + i as u64,
14 ));
15 c.virtualizer_mut().set_viewport_and_scroll_clamped(10, 50);
16
17 let anchor = c
18 .capture_first_visible_anchor()
19 .expect("visible range must not be empty");
20 println!(
21 "before prepend: off={} anchor={anchor:?}",
22 c.virtualizer().scroll_offset()
23 );
24
25 // Prepend 10 items; old items shift by +10 indexes.
26 c.virtualizer_mut().set_count(110);
27 c.virtualizer_mut().set_get_item_key(|i| {
28 if i < 10 {
29 2000u64 + i as u64
30 } else {
31 1000u64 + (i - 10) as u64
32 }
33 });
34 // Note: `set_count` and `set_get_item_key` rebuild per-index sizes. In real apps, call
35 // `sync_item_keys()` when your underlying dataset is reordered while `count` stays the same.
36
37 // Provide a key -> index mapping for the current dataset (owned by your adapter).
38 let ok = c.apply_anchor(&anchor, |k| {
39 if (1000..1100).contains(k) {
40 Some((*k as usize - 1000) + 10)
41 } else if (2000..2010).contains(k) {
42 Some(*k as usize - 2000)
43 } else {
44 None
45 }
46 });
47
48 println!(
49 "after prepend: ok={ok} off={}",
50 c.virtualizer().scroll_offset()
51 );
52}pub fn from_virtualizer(v: Virtualizer<K>) -> Self
Sourcepub fn virtualizer(&self) -> &Virtualizer<K>
pub fn virtualizer(&self) -> &Virtualizer<K>
Examples found in repository?
3fn main() {
4 // Example: TanStack-like controller driving tween scrolling without holding any UI objects.
5 //
6 // An adapter would:
7 // - start a tween (e.g. in response to "scroll to index" command)
8 // - call tick(now_ms) in a frame loop / timer
9 // - apply the returned offset to the real scroll container (if any)
10 // - render using the virtualizer state
11 let mut c = Controller::new(virtualizer::VirtualizerOptions::new(10_000, |_| 1));
12 c.virtualizer_mut().set_viewport_size(20);
13 c.virtualizer_mut().set_scroll_offset(0);
14
15 let target = c.start_tween_to_index(
16 2_000,
17 virtualizer::Align::Center,
18 0,
19 240,
20 Easing::SmoothStep,
21 );
22 println!("target_offset={target}");
23
24 let mut now_ms = 0u64;
25 loop {
26 now_ms += 16;
27 if let Some(off) = c.tick(now_ms) {
28 if now_ms.is_multiple_of(80) {
29 println!(
30 "t={now_ms} off={off} visible={:?}",
31 c.virtualizer().visible_range()
32 );
33 }
34 } else {
35 break;
36 }
37 }
38
39 println!(
40 "done: off={} range={:?}",
41 c.virtualizer().scroll_offset(),
42 c.virtualizer().virtual_range()
43 );
44}More examples
3fn main() {
4 // Example: preserve visual scroll position across "prepend" (chat/timeline load older messages).
5 //
6 // The adapter flow is typically:
7 // 1) capture an anchor (key + offset_in_viewport) before data changes
8 // 2) apply data changes (count/key mapping)
9 // 3) apply the anchor to adjust scroll_offset so the same item stays in the same place
10 let mut c = Controller::new(virtualizer::VirtualizerOptions::new_with_key(
11 100,
12 |_| 1,
13 |i| 1000u64 + i as u64,
14 ));
15 c.virtualizer_mut().set_viewport_and_scroll_clamped(10, 50);
16
17 let anchor = c
18 .capture_first_visible_anchor()
19 .expect("visible range must not be empty");
20 println!(
21 "before prepend: off={} anchor={anchor:?}",
22 c.virtualizer().scroll_offset()
23 );
24
25 // Prepend 10 items; old items shift by +10 indexes.
26 c.virtualizer_mut().set_count(110);
27 c.virtualizer_mut().set_get_item_key(|i| {
28 if i < 10 {
29 2000u64 + i as u64
30 } else {
31 1000u64 + (i - 10) as u64
32 }
33 });
34 // Note: `set_count` and `set_get_item_key` rebuild per-index sizes. In real apps, call
35 // `sync_item_keys()` when your underlying dataset is reordered while `count` stays the same.
36
37 // Provide a key -> index mapping for the current dataset (owned by your adapter).
38 let ok = c.apply_anchor(&anchor, |k| {
39 if (1000..1100).contains(k) {
40 Some((*k as usize - 1000) + 10)
41 } else if (2000..2010).contains(k) {
42 Some(*k as usize - 2000)
43 } else {
44 None
45 }
46 });
47
48 println!(
49 "after prepend: ok={ok} off={}",
50 c.virtualizer().scroll_offset()
51 );
52}Sourcepub fn virtualizer_mut(&mut self) -> &mut Virtualizer<K>
pub fn virtualizer_mut(&mut self) -> &mut Virtualizer<K>
Examples found in repository?
3fn main() {
4 // Example: TanStack-like controller driving tween scrolling without holding any UI objects.
5 //
6 // An adapter would:
7 // - start a tween (e.g. in response to "scroll to index" command)
8 // - call tick(now_ms) in a frame loop / timer
9 // - apply the returned offset to the real scroll container (if any)
10 // - render using the virtualizer state
11 let mut c = Controller::new(virtualizer::VirtualizerOptions::new(10_000, |_| 1));
12 c.virtualizer_mut().set_viewport_size(20);
13 c.virtualizer_mut().set_scroll_offset(0);
14
15 let target = c.start_tween_to_index(
16 2_000,
17 virtualizer::Align::Center,
18 0,
19 240,
20 Easing::SmoothStep,
21 );
22 println!("target_offset={target}");
23
24 let mut now_ms = 0u64;
25 loop {
26 now_ms += 16;
27 if let Some(off) = c.tick(now_ms) {
28 if now_ms.is_multiple_of(80) {
29 println!(
30 "t={now_ms} off={off} visible={:?}",
31 c.virtualizer().visible_range()
32 );
33 }
34 } else {
35 break;
36 }
37 }
38
39 println!(
40 "done: off={} range={:?}",
41 c.virtualizer().scroll_offset(),
42 c.virtualizer().virtual_range()
43 );
44}More examples
3fn main() {
4 // Example: preserve visual scroll position across "prepend" (chat/timeline load older messages).
5 //
6 // The adapter flow is typically:
7 // 1) capture an anchor (key + offset_in_viewport) before data changes
8 // 2) apply data changes (count/key mapping)
9 // 3) apply the anchor to adjust scroll_offset so the same item stays in the same place
10 let mut c = Controller::new(virtualizer::VirtualizerOptions::new_with_key(
11 100,
12 |_| 1,
13 |i| 1000u64 + i as u64,
14 ));
15 c.virtualizer_mut().set_viewport_and_scroll_clamped(10, 50);
16
17 let anchor = c
18 .capture_first_visible_anchor()
19 .expect("visible range must not be empty");
20 println!(
21 "before prepend: off={} anchor={anchor:?}",
22 c.virtualizer().scroll_offset()
23 );
24
25 // Prepend 10 items; old items shift by +10 indexes.
26 c.virtualizer_mut().set_count(110);
27 c.virtualizer_mut().set_get_item_key(|i| {
28 if i < 10 {
29 2000u64 + i as u64
30 } else {
31 1000u64 + (i - 10) as u64
32 }
33 });
34 // Note: `set_count` and `set_get_item_key` rebuild per-index sizes. In real apps, call
35 // `sync_item_keys()` when your underlying dataset is reordered while `count` stays the same.
36
37 // Provide a key -> index mapping for the current dataset (owned by your adapter).
38 let ok = c.apply_anchor(&anchor, |k| {
39 if (1000..1100).contains(k) {
40 Some((*k as usize - 1000) + 10)
41 } else if (2000..2010).contains(k) {
42 Some(*k as usize - 2000)
43 } else {
44 None
45 }
46 });
47
48 println!(
49 "after prepend: ok={ok} off={}",
50 c.virtualizer().scroll_offset()
51 );
52}pub fn into_virtualizer(self) -> Virtualizer<K>
pub fn is_animating(&self) -> bool
pub fn cancel_animation(&mut self)
pub fn on_viewport_size(&mut self, viewport_main: u32)
Sourcepub fn on_scroll(&mut self, scroll_offset: u64, now_ms: u64)
pub fn on_scroll(&mut self, scroll_offset: u64, now_ms: u64)
Call this when the UI reports a scroll offset change (e.g. user wheel/drag).
This cancels any active tween.
Sourcepub fn tick(&mut self, now_ms: u64) -> Option<u64>
pub fn tick(&mut self, now_ms: u64) -> Option<u64>
Advances the controller.
- If a tween is active, updates
scroll_offsetand returns the new offset. - Otherwise, runs
is_scrollingdebouncing and returnsNone.
Examples found in repository?
3fn main() {
4 // Example: TanStack-like controller driving tween scrolling without holding any UI objects.
5 //
6 // An adapter would:
7 // - start a tween (e.g. in response to "scroll to index" command)
8 // - call tick(now_ms) in a frame loop / timer
9 // - apply the returned offset to the real scroll container (if any)
10 // - render using the virtualizer state
11 let mut c = Controller::new(virtualizer::VirtualizerOptions::new(10_000, |_| 1));
12 c.virtualizer_mut().set_viewport_size(20);
13 c.virtualizer_mut().set_scroll_offset(0);
14
15 let target = c.start_tween_to_index(
16 2_000,
17 virtualizer::Align::Center,
18 0,
19 240,
20 Easing::SmoothStep,
21 );
22 println!("target_offset={target}");
23
24 let mut now_ms = 0u64;
25 loop {
26 now_ms += 16;
27 if let Some(off) = c.tick(now_ms) {
28 if now_ms.is_multiple_of(80) {
29 println!(
30 "t={now_ms} off={off} visible={:?}",
31 c.virtualizer().visible_range()
32 );
33 }
34 } else {
35 break;
36 }
37 }
38
39 println!(
40 "done: off={} range={:?}",
41 c.virtualizer().scroll_offset(),
42 c.virtualizer().virtual_range()
43 );
44}Sourcepub fn scroll_to_index(
&mut self,
index: usize,
align: Align,
now_ms: u64,
) -> u64
pub fn scroll_to_index( &mut self, index: usize, align: Align, now_ms: u64, ) -> u64
Computes and applies a scroll-to-index immediately (no animation).
Returns the applied (clamped) offset.
Sourcepub fn scroll_to_offset(&mut self, offset: u64, now_ms: u64) -> u64
pub fn scroll_to_offset(&mut self, offset: u64, now_ms: u64) -> u64
Applies a scroll-to-offset immediately (no animation).
Returns the applied (clamped) offset.
Sourcepub fn start_tween_to_index(
&mut self,
index: usize,
align: Align,
now_ms: u64,
duration_ms: u64,
easing: Easing,
) -> u64
pub fn start_tween_to_index( &mut self, index: usize, align: Align, now_ms: u64, duration_ms: u64, easing: Easing, ) -> u64
Starts a tween to an index (adapter-driven).
Returns the clamped target offset.
Examples found in repository?
3fn main() {
4 // Example: TanStack-like controller driving tween scrolling without holding any UI objects.
5 //
6 // An adapter would:
7 // - start a tween (e.g. in response to "scroll to index" command)
8 // - call tick(now_ms) in a frame loop / timer
9 // - apply the returned offset to the real scroll container (if any)
10 // - render using the virtualizer state
11 let mut c = Controller::new(virtualizer::VirtualizerOptions::new(10_000, |_| 1));
12 c.virtualizer_mut().set_viewport_size(20);
13 c.virtualizer_mut().set_scroll_offset(0);
14
15 let target = c.start_tween_to_index(
16 2_000,
17 virtualizer::Align::Center,
18 0,
19 240,
20 Easing::SmoothStep,
21 );
22 println!("target_offset={target}");
23
24 let mut now_ms = 0u64;
25 loop {
26 now_ms += 16;
27 if let Some(off) = c.tick(now_ms) {
28 if now_ms.is_multiple_of(80) {
29 println!(
30 "t={now_ms} off={off} visible={:?}",
31 c.virtualizer().visible_range()
32 );
33 }
34 } else {
35 break;
36 }
37 }
38
39 println!(
40 "done: off={} range={:?}",
41 c.virtualizer().scroll_offset(),
42 c.virtualizer().virtual_range()
43 );
44}Sourcepub fn start_tween_to_offset(
&mut self,
offset: u64,
now_ms: u64,
duration_ms: u64,
easing: Easing,
) -> u64
pub fn start_tween_to_offset( &mut self, offset: u64, now_ms: u64, duration_ms: u64, easing: Easing, ) -> u64
Starts a tween to an offset (adapter-driven).
Returns the clamped target offset.
Sourcepub fn capture_first_visible_anchor(&self) -> Option<ScrollAnchor<K>>
pub fn capture_first_visible_anchor(&self) -> Option<ScrollAnchor<K>>
Examples found in repository?
3fn main() {
4 // Example: preserve visual scroll position across "prepend" (chat/timeline load older messages).
5 //
6 // The adapter flow is typically:
7 // 1) capture an anchor (key + offset_in_viewport) before data changes
8 // 2) apply data changes (count/key mapping)
9 // 3) apply the anchor to adjust scroll_offset so the same item stays in the same place
10 let mut c = Controller::new(virtualizer::VirtualizerOptions::new_with_key(
11 100,
12 |_| 1,
13 |i| 1000u64 + i as u64,
14 ));
15 c.virtualizer_mut().set_viewport_and_scroll_clamped(10, 50);
16
17 let anchor = c
18 .capture_first_visible_anchor()
19 .expect("visible range must not be empty");
20 println!(
21 "before prepend: off={} anchor={anchor:?}",
22 c.virtualizer().scroll_offset()
23 );
24
25 // Prepend 10 items; old items shift by +10 indexes.
26 c.virtualizer_mut().set_count(110);
27 c.virtualizer_mut().set_get_item_key(|i| {
28 if i < 10 {
29 2000u64 + i as u64
30 } else {
31 1000u64 + (i - 10) as u64
32 }
33 });
34 // Note: `set_count` and `set_get_item_key` rebuild per-index sizes. In real apps, call
35 // `sync_item_keys()` when your underlying dataset is reordered while `count` stays the same.
36
37 // Provide a key -> index mapping for the current dataset (owned by your adapter).
38 let ok = c.apply_anchor(&anchor, |k| {
39 if (1000..1100).contains(k) {
40 Some((*k as usize - 1000) + 10)
41 } else if (2000..2010).contains(k) {
42 Some(*k as usize - 2000)
43 } else {
44 None
45 }
46 });
47
48 println!(
49 "after prepend: ok={ok} off={}",
50 c.virtualizer().scroll_offset()
51 );
52}Sourcepub fn capture_anchor_at_offset_in_viewport(
&self,
offset_in_viewport: u64,
) -> Option<ScrollAnchor<K>>
pub fn capture_anchor_at_offset_in_viewport( &self, offset_in_viewport: u64, ) -> Option<ScrollAnchor<K>>
Captures an anchor for the item at a given offset in the viewport.
For example, offset_in_viewport = 0 anchors the item at the top of the viewport.
Sourcepub fn apply_anchor(
&mut self,
anchor: &ScrollAnchor<K>,
key_to_index: impl FnMut(&K) -> Option<usize>,
) -> bool
pub fn apply_anchor( &mut self, anchor: &ScrollAnchor<K>, key_to_index: impl FnMut(&K) -> Option<usize>, ) -> bool
Applies a previously captured anchor by adjusting the scroll offset.
This cancels any active tween.
Examples found in repository?
3fn main() {
4 // Example: preserve visual scroll position across "prepend" (chat/timeline load older messages).
5 //
6 // The adapter flow is typically:
7 // 1) capture an anchor (key + offset_in_viewport) before data changes
8 // 2) apply data changes (count/key mapping)
9 // 3) apply the anchor to adjust scroll_offset so the same item stays in the same place
10 let mut c = Controller::new(virtualizer::VirtualizerOptions::new_with_key(
11 100,
12 |_| 1,
13 |i| 1000u64 + i as u64,
14 ));
15 c.virtualizer_mut().set_viewport_and_scroll_clamped(10, 50);
16
17 let anchor = c
18 .capture_first_visible_anchor()
19 .expect("visible range must not be empty");
20 println!(
21 "before prepend: off={} anchor={anchor:?}",
22 c.virtualizer().scroll_offset()
23 );
24
25 // Prepend 10 items; old items shift by +10 indexes.
26 c.virtualizer_mut().set_count(110);
27 c.virtualizer_mut().set_get_item_key(|i| {
28 if i < 10 {
29 2000u64 + i as u64
30 } else {
31 1000u64 + (i - 10) as u64
32 }
33 });
34 // Note: `set_count` and `set_get_item_key` rebuild per-index sizes. In real apps, call
35 // `sync_item_keys()` when your underlying dataset is reordered while `count` stays the same.
36
37 // Provide a key -> index mapping for the current dataset (owned by your adapter).
38 let ok = c.apply_anchor(&anchor, |k| {
39 if (1000..1100).contains(k) {
40 Some((*k as usize - 1000) + 10)
41 } else if (2000..2010).contains(k) {
42 Some(*k as usize - 2000)
43 } else {
44 None
45 }
46 });
47
48 println!(
49 "after prepend: ok={ok} off={}",
50 c.virtualizer().scroll_offset()
51 );
52}Trait Implementations§
Source§impl<K: Clone> Clone for Controller<K>
impl<K: Clone> Clone for Controller<K>
Source§fn clone(&self) -> Controller<K>
fn clone(&self) -> Controller<K>
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more