Skip to main content

damascene_core/
scroll.rs

1//! App-side scroll request types.
2//!
3//! Apps push [`ScrollRequest`]s via [`crate::App::drain_scroll_requests`];
4//! the layout pass resolves each one against the matching scroll
5//! container's live viewport rect and writes the resulting offset into
6//! the scroll state so the same frame's render reflects the new position.
7//!
8//! Mirrors [`crate::toast`]: the App produces fire-and-forget descriptors
9//! and the runtime resolves them with state that's only fully known
10//! mid-frame.
11
12// Lock in full per-item documentation for this module (issue #73).
13#![warn(missing_docs)]
14
15/// Where in the viewport a row-targeted [`ScrollRequest`] should land
16/// its target row.
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum ScrollAlignment {
19    /// Top of the row aligns with the top of the viewport.
20    Start,
21    /// Centre of the row aligns with the centre of the viewport.
22    Center,
23    /// Bottom of the row aligns with the bottom of the viewport.
24    End,
25    /// No-op if the row already fits entirely inside the viewport;
26    /// otherwise scroll the minimum amount that brings it into view
27    /// (i.e., align to the nearer edge).
28    Visible,
29}
30
31/// What the app produces from [`crate::App::drain_scroll_requests`].
32/// Three shapes today:
33///
34/// - [`ScrollRequest::ToRow`] — "scroll the virtual list keyed
35///   `list_key` so row `row` lands per `align`." Resolved during
36///   layout of the matching `virtual_list` / `virtual_list_dyn` using
37///   the live viewport and the row-height cache.
38/// - [`ScrollRequest::ToRowKey`] — same operation, but targets the
39///   virtual-list row by stable row identity instead of current row
40///   index. Prefer this for `virtual_list_dyn` when the app already
41///   has message/thread/commit ids.
42/// - [`ScrollRequest::EnsureVisible`] — "scroll the nearest scroll
43///   container under the node keyed `container_key` so the content-
44///   space rect `y..y+h` is visible." Resolved during layout of the
45///   matching `scroll(...)` container; minimal-displacement (top edge
46///   if above viewport, bottom edge if below, no-op if already
47///   visible). Used by [`crate::widgets::text_area`] for
48///   caret-into-view on keyboard navigation, and available for any
49///   widget that needs to keep an inner anchor on screen.
50#[derive(Clone, Debug)]
51pub enum ScrollRequest {
52    /// Bring `row` of the virtual list keyed `list_key` into view per
53    /// `align`.
54    ToRow {
55        /// `.key(...)` of the target virtual list.
56        list_key: String,
57        /// Zero-based row index. Out-of-range indices are ignored.
58        row: usize,
59        /// Where in the viewport the row should land.
60        align: ScrollAlignment,
61    },
62    /// Bring the row identified by `row_key` in the virtual list keyed
63    /// `list_key` into view per `align`.
64    ToRowKey {
65        /// `.key(...)` of the target virtual list.
66        list_key: String,
67        /// Stable row identity, as produced by the list's `row_key`
68        /// function. Unknown keys are ignored.
69        row_key: String,
70        /// Where in the viewport the row should land.
71        align: ScrollAlignment,
72    },
73    /// Ensure the content-space rect at `y..y+h` is visible inside
74    /// the scroll container under the node keyed `container_key`.
75    /// `container_key` is the outer widget's key (e.g. the text_area's
76    /// key) — the resolver descends to find the nearest `Kind::Scroll`
77    /// inside that node.
78    EnsureVisible {
79        /// `.key(...)` of the widget containing the scroll container.
80        container_key: String,
81        /// Top of the target rect, in the scroll container's content
82        /// coordinates (logical px, 0 = top of content).
83        y: f32,
84        /// Height of the target rect in logical pixels.
85        h: f32,
86    },
87}
88
89impl ScrollRequest {
90    /// Construct a [`ScrollRequest::ToRow`]. Kept for source-compat
91    /// with callers that predate the enum — `ScrollRequest::new(...)`
92    /// has always meant "scroll a virtual list to a row."
93    pub fn new(list_key: impl Into<String>, row: usize, align: ScrollAlignment) -> Self {
94        ScrollRequest::ToRow {
95            list_key: list_key.into(),
96            row,
97            align,
98        }
99    }
100
101    /// Construct a [`ScrollRequest::ToRowKey`]. Dynamic virtual lists
102    /// resolve this against the same stable row identities passed to
103    /// [`crate::virtual_list_dyn`].
104    pub fn to_row_key(
105        list_key: impl Into<String>,
106        row_key: impl Into<String>,
107        align: ScrollAlignment,
108    ) -> Self {
109        ScrollRequest::ToRowKey {
110            list_key: list_key.into(),
111            row_key: row_key.into(),
112            align,
113        }
114    }
115
116    /// Construct a [`ScrollRequest::EnsureVisible`] for the widget
117    /// keyed `container_key`, asking the resolver to keep
118    /// `y..y+h` (in the scroll container's content coordinates)
119    /// inside the viewport.
120    pub fn ensure_visible(container_key: impl Into<String>, y: f32, h: f32) -> Self {
121        ScrollRequest::EnsureVisible {
122            container_key: container_key.into(),
123            y,
124            h,
125        }
126    }
127}