nandi/jolt-nativepublic Fork 0
0ab003e6fe9419234eb25cfd91dcc7b75d283bfa
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

rows.rs · 248 lines · 7.6 KBRust Blame HistoryRaw
Let a list say where it put its rows, and jump there 0ab003e nandi 8d ago1//! A column that remembers where it put its rows.
2//!
3//! iced can scroll a list to an offset and to nothing else: `snap_to`,
4//! `scroll_to` and `scroll_by` all take a number of points, and there is no
5//! "show me this child" anywhere in the toolkit. So a client that wants to be
6//! taken to one row of a hundred has to answer the question itself — how far
7//! down the content is that row? — and the only thing that knows is the layout.
8//!
9//! This is that, in the one place the answer is free. `Rows` wraps the column
10//! inside a scroll area and does nothing to it but read the layout it already
11//! computed, writing down where each row landed. A jump is then a lookup and
12//! the `scroll_to` that already exists.
13//!
14//! It is one widget per scroll area, always, whether or not anything is asking
15//! to be scrolled to. That is the whole of its safety: iced matches widgets by
16//! where they sit, so a wrapper that came and went as rows became interesting
17//! would leave the tree a different shape than the state kept for it. The
18//! `State` below is why it can be told apart from the column it wraps at all —
19//! iced tells two widgets apart by the type of their state, and two stateless
20//! ones are the same widget as far as it knows.
21
22use std::collections::HashMap;
23use std::sync::{Arc, Mutex};
24
25use cosmic::iced::advanced::widget::{Operation, Tree, tree};
26use cosmic::iced::advanced::{Clipboard, Layout, Shell, Widget, layout, mouse, overlay, renderer};
27// `cosmic::Element`, not iced's: the two differ in their theme, and this
28// widget lives in a cosmic tree.
29use cosmic::Element;
30use cosmic::iced::{Event, Length, Rectangle, Size, Vector};
31
32/// Where each row of one scroll area was last laid out, in points from the top
33/// of the content, beside how tall it is.
34///
35/// Shared with whoever wants to scroll: the widget writes during layout, the
36/// app reads when a jump asks for a row. Late answers are the point — a row
37/// only just mounted is measured on the frame it appears, and the jump that
38/// wanted it can ask again on the next one rather than having missed its one
39/// chance.
40#[derive(Clone, Default)]
41pub struct Placements(Arc<Mutex<HashMap<i32, (f32, f32)>>>);
42
43impl Placements {
44 pub fn new() -> Self {
45 Self::default()
46 }
47
48 /// Where row `key` sits, if it has been laid out since the last time the
49 /// list it is in was.
50 pub fn get(&self, key: i32) -> Option<(f32, f32)> {
51 self.0.lock().ok()?.get(&key).copied()
52 }
53
54 fn write(&self, rows: HashMap<i32, (f32, f32)>) {
55 if let Ok(mut held) = self.0.lock() {
56 *held = rows;
57 }
58 }
59}
60
61/// The trivial state that gives this widget a type of its own.
62///
63/// It holds nothing. What it is for is the tag: a wrapper with no state is
64/// indistinguishable from a plain column to iced's diffing, which reuses the
65/// tree it kept for one as the tree for the other — and the mismatch is not
66/// noticed until something deep inside reads a child that was never there.
67struct State;
68
69pub struct Rows<'a, Message> {
70 inner: Element<'a, Message>,
71 keys: Vec<i32>,
72 placements: Placements,
73}
74
75impl<'a, Message> Rows<'a, Message> {
76 /// `keys` are the rows of `inner`, in the order they were given to it.
77 pub fn new(
78 inner: impl Into<Element<'a, Message>>,
79 keys: Vec<i32>,
80 placements: Placements,
81 ) -> Self {
82 Self {
83 inner: inner.into(),
84 keys,
85 placements,
86 }
87 }
88}
89
90impl<Message> Widget<Message, cosmic::Theme, cosmic::Renderer> for Rows<'_, Message> {
91 fn tag(&self) -> tree::Tag {
92 tree::Tag::of::<State>()
93 }
94
95 fn state(&self) -> tree::State {
96 tree::State::new(State)
97 }
98
99 fn children(&self) -> Vec<Tree> {
100 vec![Tree::new(&self.inner)]
101 }
102
103 fn diff(&mut self, tree: &mut Tree) {
104 tree.diff_children(std::slice::from_mut(&mut self.inner));
105 }
106
107 fn size(&self) -> Size<Length> {
108 self.inner.as_widget().size()
109 }
110
111 fn layout(
112 &mut self,
113 tree: &mut Tree,
114 renderer: &cosmic::Renderer,
115 limits: &layout::Limits,
116 ) -> layout::Node {
117 let node = self
118 .inner
119 .as_widget_mut()
120 .layout(&mut tree.children[0], renderer, limits);
121
122 // The column's own children, in the order its keys were given. Their
123 // bounds are already relative to the content's top, which is the
124 // coordinate `scroll_to` is asking for.
125 //
126 // Written whole rather than merged: a row that has left the list has
127 // no place any more, and an old answer for it would send a jump to
128 // wherever it used to be.
129 let mut rows = HashMap::with_capacity(self.keys.len());
130 for (key, child) in self.keys.iter().zip(node.children()) {
131 let bounds = child.bounds();
132 rows.insert(*key, (bounds.y, bounds.height));
133 }
134 self.placements.write(rows);
135
136 let size = node.size();
137 layout::Node::with_children(size, vec![node])
138 }
139
140 fn operate(
141 &mut self,
142 tree: &mut Tree,
143 layout: Layout<'_>,
144 renderer: &cosmic::Renderer,
145 operation: &mut dyn Operation,
146 ) {
147 operation.traverse(&mut |operation| {
148 self.inner.as_widget_mut().operate(
149 &mut tree.children[0],
150 inner_layout(layout),
151 renderer,
152 operation,
153 );
154 });
155 }
156
157 fn update(
158 &mut self,
159 tree: &mut Tree,
160 event: &Event,
161 layout: Layout<'_>,
162 cursor: mouse::Cursor,
163 renderer: &cosmic::Renderer,
164 clipboard: &mut dyn Clipboard,
165 shell: &mut Shell<'_, Message>,
166 viewport: &Rectangle,
167 ) {
168 self.inner.as_widget_mut().update(
169 &mut tree.children[0],
170 event,
171 inner_layout(layout),
172 cursor,
173 renderer,
174 clipboard,
175 shell,
176 viewport,
177 );
178 }
179
180 fn mouse_interaction(
181 &self,
182 tree: &Tree,
183 layout: Layout<'_>,
184 cursor: mouse::Cursor,
185 viewport: &Rectangle,
186 renderer: &cosmic::Renderer,
187 ) -> mouse::Interaction {
188 self.inner.as_widget().mouse_interaction(
189 &tree.children[0],
190 inner_layout(layout),
191 cursor,
192 viewport,
193 renderer,
194 )
195 }
196
197 fn draw(
198 &self,
199 tree: &Tree,
200 renderer: &mut cosmic::Renderer,
201 theme: &cosmic::Theme,
202 style: &renderer::Style,
203 layout: Layout<'_>,
204 cursor: mouse::Cursor,
205 viewport: &Rectangle,
206 ) {
207 self.inner.as_widget().draw(
208 &tree.children[0],
209 renderer,
210 theme,
211 style,
212 inner_layout(layout),
213 cursor,
214 viewport,
215 );
216 }
217
218 fn overlay<'b>(
219 &'b mut self,
220 tree: &'b mut Tree,
221 layout: Layout<'b>,
222 renderer: &cosmic::Renderer,
223 viewport: &Rectangle,
224 translation: Vector,
225 ) -> Option<overlay::Element<'b, Message, cosmic::Theme, cosmic::Renderer>> {
226 self.inner.as_widget_mut().overlay(
227 &mut tree.children[0],
228 inner_layout(layout),
229 renderer,
230 viewport,
231 translation,
232 )
233 }
234}
235
236/// The one child this widget's layout node has.
237fn inner_layout(layout: Layout<'_>) -> Layout<'_> {
238 layout
239 .children()
240 .next()
241 .expect("a Rows layout holds exactly one child")
242}
243
244impl<'a, Message: 'a> From<Rows<'a, Message>> for Element<'a, Message> {
245 fn from(rows: Rows<'a, Message>) -> Self {
246 Element::new(rows)
247 }
248}