//! A column that remembers where it put its rows. //! //! iced can scroll a list to an offset and to nothing else: `snap_to`, //! `scroll_to` and `scroll_by` all take a number of points, and there is no //! "show me this child" anywhere in the toolkit. So a client that wants to be //! taken to one row of a hundred has to answer the question itself — how far //! down the content is that row? — and the only thing that knows is the layout. //! //! This is that, in the one place the answer is free. `Rows` wraps the column //! inside a scroll area and does nothing to it but read the layout it already //! computed, writing down where each row landed. A jump is then a lookup and //! the `scroll_to` that already exists. //! //! It is one widget per scroll area, always, whether or not anything is asking //! to be scrolled to. That is the whole of its safety: iced matches widgets by //! where they sit, so a wrapper that came and went as rows became interesting //! would leave the tree a different shape than the state kept for it. The //! `State` below is why it can be told apart from the column it wraps at all — //! iced tells two widgets apart by the type of their state, and two stateless //! ones are the same widget as far as it knows. use std::collections::HashMap; use std::sync::{Arc, Mutex}; use cosmic::iced::advanced::widget::{Operation, Tree, tree}; use cosmic::iced::advanced::{Clipboard, Layout, Shell, Widget, layout, mouse, overlay, renderer}; // `cosmic::Element`, not iced's: the two differ in their theme, and this // widget lives in a cosmic tree. use cosmic::Element; use cosmic::iced::{Event, Length, Rectangle, Size, Vector}; /// Where each row of one scroll area was last laid out, in points from the top /// of the content, beside how tall it is. /// /// Shared with whoever wants to scroll: the widget writes during layout, the /// app reads when a jump asks for a row. Late answers are the point — a row /// only just mounted is measured on the frame it appears, and the jump that /// wanted it can ask again on the next one rather than having missed its one /// chance. #[derive(Clone, Default)] pub struct Placements(Arc>>); impl Placements { pub fn new() -> Self { Self::default() } /// Where row `key` sits, if it has been laid out since the last time the /// list it is in was. pub fn get(&self, key: i32) -> Option<(f32, f32)> { self.0.lock().ok()?.get(&key).copied() } /// How many rows have a place written down. For the log alone. pub fn len(&self) -> usize { self.0.lock().map(|held| held.len()).unwrap_or(0) } fn write(&self, rows: HashMap) { if let Ok(mut held) = self.0.lock() { *held = rows; } } } /// The trivial state that gives this widget a type of its own. /// /// It holds nothing. What it is for is the tag: a wrapper with no state is /// indistinguishable from a plain column to iced's diffing, which reuses the /// tree it kept for one as the tree for the other — and the mismatch is not /// noticed until something deep inside reads a child that was never there. struct State; pub struct Rows<'a, Message> { inner: Element<'a, Message>, keys: Vec, placements: Placements, } impl<'a, Message> Rows<'a, Message> { /// `keys` are the rows of `inner`, in the order they were given to it. pub fn new( inner: impl Into>, keys: Vec, placements: Placements, ) -> Self { Self { inner: inner.into(), keys, placements, } } } impl Widget for Rows<'_, Message> { fn tag(&self) -> tree::Tag { tree::Tag::of::() } fn state(&self) -> tree::State { tree::State::new(State) } fn children(&self) -> Vec { vec![Tree::new(&self.inner)] } fn diff(&mut self, tree: &mut Tree) { tree.diff_children(std::slice::from_mut(&mut self.inner)); } fn size(&self) -> Size { self.inner.as_widget().size() } fn layout( &mut self, tree: &mut Tree, renderer: &cosmic::Renderer, limits: &layout::Limits, ) -> layout::Node { let node = self .inner .as_widget_mut() .layout(&mut tree.children[0], renderer, limits); // The column's own children, in the order its keys were given. Their // bounds are already relative to the content's top, which is the // coordinate `scroll_to` is asking for. // // Written whole rather than merged: a row that has left the list has // no place any more, and an old answer for it would send a jump to // wherever it used to be. let mut rows = HashMap::with_capacity(self.keys.len()); for (key, child) in self.keys.iter().zip(node.children()) { let bounds = child.bounds(); rows.insert(*key, (bounds.y, bounds.height)); } self.placements.write(rows); let size = node.size(); layout::Node::with_children(size, vec![node]) } fn operate( &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &cosmic::Renderer, operation: &mut dyn Operation, ) { operation.traverse(&mut |operation| { self.inner.as_widget_mut().operate( &mut tree.children[0], inner_layout(layout), renderer, operation, ); }); } fn update( &mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &cosmic::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { self.inner.as_widget_mut().update( &mut tree.children[0], event, inner_layout(layout), cursor, renderer, clipboard, shell, viewport, ); } fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &cosmic::Renderer, ) -> mouse::Interaction { self.inner.as_widget().mouse_interaction( &tree.children[0], inner_layout(layout), cursor, viewport, renderer, ) } fn draw( &self, tree: &Tree, renderer: &mut cosmic::Renderer, theme: &cosmic::Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { self.inner.as_widget().draw( &tree.children[0], renderer, theme, style, inner_layout(layout), cursor, viewport, ); } fn overlay<'b>( &'b mut self, tree: &'b mut Tree, layout: Layout<'b>, renderer: &cosmic::Renderer, viewport: &Rectangle, translation: Vector, ) -> Option> { self.inner.as_widget_mut().overlay( &mut tree.children[0], inner_layout(layout), renderer, viewport, translation, ) } } /// The one child this widget's layout node has. fn inner_layout(layout: Layout<'_>) -> Layout<'_> { layout .children() .next() .expect("a Rows layout holds exactly one child") } impl<'a, Message: 'a> From> for Element<'a, Message> { fn from(rows: Rows<'a, Message>) -> Self { Element::new(rows) } }