nandi/jolt-nativepublic Fork 0
5ba95e0164dfaf9110357b5e041001f98d4f13a9
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 · 253 lines · 7.8 KBRust Blame HistoryRaw
Let a list say where it put its rows, and jump there 0ab003e nandi 7d 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
Keep asking until the row is there, and say so when it is not b49f82a nandi 7d ago54 /// How many rows have a place written down. For the log alone.
55 pub fn len(&self) -> usize {
56 self.0.lock().map(|held| held.len()).unwrap_or(0)
57 }
58
Let a list say where it put its rows, and jump there 0ab003e nandi 7d ago59 fn write(&self, rows: HashMap<i32, (f32, f32)>) {
60 if let Ok(mut held) = self.0.lock() {
61 *held = rows;
62 }
63 }
64}
65
66/// The trivial state that gives this widget a type of its own.
67///
68/// It holds nothing. What it is for is the tag: a wrapper with no state is
69/// indistinguishable from a plain column to iced's diffing, which reuses the
70/// tree it kept for one as the tree for the other — and the mismatch is not
71/// noticed until something deep inside reads a child that was never there.
72struct State;
73
74pub struct Rows<'a, Message> {
75 inner: Element<'a, Message>,
76 keys: Vec<i32>,
77 placements: Placements,
78}
79
80impl<'a, Message> Rows<'a, Message> {
81 /// `keys` are the rows of `inner`, in the order they were given to it.
82 pub fn new(
83 inner: impl Into<Element<'a, Message>>,
84 keys: Vec<i32>,
85 placements: Placements,
86 ) -> Self {
87 Self {
88 inner: inner.into(),
89 keys,
90 placements,
91 }
92 }
93}
94
95impl<Message> Widget<Message, cosmic::Theme, cosmic::Renderer> for Rows<'_, Message> {
96 fn tag(&self) -> tree::Tag {
97 tree::Tag::of::<State>()
98 }
99
100 fn state(&self) -> tree::State {
101 tree::State::new(State)
102 }
103
104 fn children(&self) -> Vec<Tree> {
105 vec![Tree::new(&self.inner)]
106 }
107
108 fn diff(&mut self, tree: &mut Tree) {
109 tree.diff_children(std::slice::from_mut(&mut self.inner));
110 }
111
112 fn size(&self) -> Size<Length> {
113 self.inner.as_widget().size()
114 }
115
116 fn layout(
117 &mut self,
118 tree: &mut Tree,
119 renderer: &cosmic::Renderer,
120 limits: &layout::Limits,
121 ) -> layout::Node {
122 let node = self
123 .inner
124 .as_widget_mut()
125 .layout(&mut tree.children[0], renderer, limits);
126
127 // The column's own children, in the order its keys were given. Their
128 // bounds are already relative to the content's top, which is the
129 // coordinate `scroll_to` is asking for.
130 //
131 // Written whole rather than merged: a row that has left the list has
132 // no place any more, and an old answer for it would send a jump to
133 // wherever it used to be.
134 let mut rows = HashMap::with_capacity(self.keys.len());
135 for (key, child) in self.keys.iter().zip(node.children()) {
136 let bounds = child.bounds();
137 rows.insert(*key, (bounds.y, bounds.height));
138 }
139 self.placements.write(rows);
140
141 let size = node.size();
142 layout::Node::with_children(size, vec![node])
143 }
144
145 fn operate(
146 &mut self,
147 tree: &mut Tree,
148 layout: Layout<'_>,
149 renderer: &cosmic::Renderer,
150 operation: &mut dyn Operation,
151 ) {
152 operation.traverse(&mut |operation| {
153 self.inner.as_widget_mut().operate(
154 &mut tree.children[0],
155 inner_layout(layout),
156 renderer,
157 operation,
158 );
159 });
160 }
161
162 fn update(
163 &mut self,
164 tree: &mut Tree,
165 event: &Event,
166 layout: Layout<'_>,
167 cursor: mouse::Cursor,
168 renderer: &cosmic::Renderer,
169 clipboard: &mut dyn Clipboard,
170 shell: &mut Shell<'_, Message>,
171 viewport: &Rectangle,
172 ) {
173 self.inner.as_widget_mut().update(
174 &mut tree.children[0],
175 event,
176 inner_layout(layout),
177 cursor,
178 renderer,
179 clipboard,
180 shell,
181 viewport,
182 );
183 }
184
185 fn mouse_interaction(
186 &self,
187 tree: &Tree,
188 layout: Layout<'_>,
189 cursor: mouse::Cursor,
190 viewport: &Rectangle,
191 renderer: &cosmic::Renderer,
192 ) -> mouse::Interaction {
193 self.inner.as_widget().mouse_interaction(
194 &tree.children[0],
195 inner_layout(layout),
196 cursor,
197 viewport,
198 renderer,
199 )
200 }
201
202 fn draw(
203 &self,
204 tree: &Tree,
205 renderer: &mut cosmic::Renderer,
206 theme: &cosmic::Theme,
207 style: &renderer::Style,
208 layout: Layout<'_>,
209 cursor: mouse::Cursor,
210 viewport: &Rectangle,
211 ) {
212 self.inner.as_widget().draw(
213 &tree.children[0],
214 renderer,
215 theme,
216 style,
217 inner_layout(layout),
218 cursor,
219 viewport,
220 );
221 }
222
223 fn overlay<'b>(
224 &'b mut self,
225 tree: &'b mut Tree,
226 layout: Layout<'b>,
227 renderer: &cosmic::Renderer,
228 viewport: &Rectangle,
229 translation: Vector,
230 ) -> Option<overlay::Element<'b, Message, cosmic::Theme, cosmic::Renderer>> {
231 self.inner.as_widget_mut().overlay(
232 &mut tree.children[0],
233 inner_layout(layout),
234 renderer,
235 viewport,
236 translation,
237 )
238 }
239}
240
241/// The one child this widget's layout node has.
242fn inner_layout(layout: Layout<'_>) -> Layout<'_> {
243 layout
244 .children()
245 .next()
246 .expect("a Rows layout holds exactly one child")
247}
248
249impl<'a, Message: 'a> From<Rows<'a, Message>> for Element<'a, Message> {
250 fn from(rows: Rows<'a, Message>) -> Self {
251 Element::new(rows)
252 }
253}