Controller

Struct Controller 

Source
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_scroll when UI events occur
  • tick(now_ms) each frame/timer tick (for tween scrolling and is_scrolling debouncing)

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 returns Some(offset), apply it to the real UI.

Implementations§

Source§

impl<K: VirtualizerKey> Controller<K>

Source

pub fn new(options: VirtualizerOptions<K>) -> Self

Examples found in repository?
examples/controller_tween.rs (line 11)
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
Hide additional examples
examples/anchor_prepend.rs (lines 10-14)
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}
Source

pub fn from_virtualizer(v: Virtualizer<K>) -> Self

Source

pub fn virtualizer(&self) -> &Virtualizer<K>

Examples found in repository?
examples/controller_tween.rs (line 31)
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
Hide additional examples
examples/anchor_prepend.rs (line 22)
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}
Source

pub fn virtualizer_mut(&mut self) -> &mut Virtualizer<K>

Examples found in repository?
examples/controller_tween.rs (line 12)
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
Hide additional examples
examples/anchor_prepend.rs (line 15)
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}
Source

pub fn into_virtualizer(self) -> Virtualizer<K>

Source

pub fn is_animating(&self) -> bool

Source

pub fn cancel_animation(&mut self)

Source

pub fn on_viewport_size(&mut self, viewport_main: u32)

Source

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.

Source

pub fn tick(&mut self, now_ms: u64) -> Option<u64>

Advances the controller.

  • If a tween is active, updates scroll_offset and returns the new offset.
  • Otherwise, runs is_scrolling debouncing and returns None.
Examples found in repository?
examples/controller_tween.rs (line 27)
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}
Source

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.

Source

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.

Source

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?
examples/controller_tween.rs (lines 15-21)
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}
Source

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.

Source

pub fn capture_first_visible_anchor(&self) -> Option<ScrollAnchor<K>>

Examples found in repository?
examples/anchor_prepend.rs (line 18)
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}
Source

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.

Source

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?
examples/anchor_prepend.rs (lines 38-46)
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>

Source§

fn clone(&self) -> Controller<K>

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<K: Debug> Debug for Controller<K>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<K> !Freeze for Controller<K>

§

impl<K> !RefUnwindSafe for Controller<K>

§

impl<K> Send for Controller<K>
where K: Send,

§

impl<K> !Sync for Controller<K>

§

impl<K> Unpin for Controller<K>
where K: Unpin,

§

impl<K> !UnwindSafe for Controller<K>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more