#![allow(missing_docs)] use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ self, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; use std::cell::RefCell; use std::cmp::Ordering; use std::collections::VecDeque; #[allow(missing_debug_implementations)] pub struct List<'a, T, Message, Theme, Renderer> { content: &'a Content, spacing: f32, view_item: Box Element<'a, Message, Theme, Renderer> + 'a>, visible_elements: Vec>, } impl<'a, T, Message, Theme, Renderer> List<'a, T, Message, Theme, Renderer> { pub fn new( content: &'a Content, view_item: impl Fn(usize, &'a T) -> Element<'a, Message, Theme, Renderer> + 'a, ) -> Self { Self { content, spacing: 0.0, view_item: Box::new(view_item), visible_elements: Vec::new(), } } /// Sets the vertical spacing _between_ elements. /// /// Custom margins per element do not exist in iced. You should use this /// method instead! While less flexible, it helps you keep spacing between /// elements consistent. pub fn spacing(mut self, amount: impl Into) -> Self { self.spacing = amount.into().0; self } } struct State { last_limits: layout::Limits, visible_layouts: Vec<(usize, layout::Node, Tree)>, size: Size, offsets: Vec, widths: Vec, task: Task, visible_outdated: bool, } enum Task { Idle, Computing { current: usize, offsets: Vec, widths: Vec, size: Size, }, } impl State { fn recompute(&mut self, size: usize) { let mut offsets = Vec::with_capacity(size + 1); offsets.push(0.0); self.task = Task::Computing { current: 0, offsets, widths: Vec::with_capacity(size), size: Size::ZERO, }; self.visible_layouts.clear(); } } impl<'a, T, Message, Theme, Renderer> Widget for List<'a, T, Message, Theme, Renderer> where Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { tree::Tag::of::() } fn state(&self) -> tree::State { tree::State::new(State { last_limits: layout::Limits::NONE, visible_layouts: Vec::new(), size: Size::ZERO, offsets: vec![0.0], widths: Vec::new(), task: Task::Idle, visible_outdated: false, }) } fn size(&self) -> Size { Size { width: Length::Shrink, height: Length::Shrink, } } fn layout( &mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { let state = tree.state.downcast_mut::(); let loose_limits = limits.loose(); if state.last_limits != loose_limits { state.last_limits = loose_limits; state.recompute(self.content.len()); } let mut changes = self.content.changes.borrow_mut(); match state.task { Task::Idle => { while let Some(change) = changes.pop_front() { match change { Change::Updated { original, current } => { let mut new_element = (self.view_item)( current, &self.content.items[current], ); let visible_index = state .visible_layouts .iter_mut() .position(|(i, _, _)| *i == original); let mut new_tree; // Update if visible let tree = if let Some(visible_index) = visible_index { let (_i, _layout, tree) = &mut state .visible_layouts[visible_index]; tree.diff(&mut new_element); state.visible_outdated = true; tree } else { new_tree = Tree::new(&new_element); &mut new_tree }; let new_layout = new_element .as_widget_mut() .layout(tree, renderer, &state.last_limits); let new_size = new_layout.size(); let height_difference = new_size.height - (state.offsets[original + 1] - state.offsets[original]); for offset in &mut state.offsets[original + 1..] { *offset += height_difference; } let original_width = state.widths[original]; state.widths[original] = new_size.width; if let Some(visible_index) = visible_index { state.visible_layouts[visible_index].1 = new_layout; for (i, layout, _) in &mut state.visible_layouts[visible_index..] { layout .move_to_mut((0.0, state.offsets[*i])); } } else if let Some(first_visible) = state.visible_layouts.first() { let first_visible_index = first_visible.0; if original < first_visible_index { for (i, layout, _) in &mut state.visible_layouts[..] { layout.move_to_mut(( 0.0, state.offsets[*i], )); } } } state.size.height += height_difference; if original_width == state.size.width { state.size.width = state.widths.iter().fold( 0.0, |current, candidate| { current.max(*candidate) }, ); } } Change::Removed { original, .. } => { let height = state.offsets[original + 1] - state.offsets[original]; let original_width = state.widths.remove(original); let _ = state.offsets.remove(original + 1); for offset in &mut state.offsets[original + 1..] { *offset -= height; } // TODO: Smarter visible layout partial updates state.visible_layouts.clear(); state.size.height -= height; if original_width == state.size.width { state.size.width = state.widths.iter().fold( 0.0, |current, candidate| { current.max(*candidate) }, ); } } Change::Pushed { current, .. } => { let mut new_element = (self.view_item)( current, &self.content.items[current], ); let mut tree = Tree::new(&new_element); let layout = new_element.as_widget_mut().layout( &mut tree, renderer, &state.last_limits, ); let size = layout.size(); state.widths.push(size.width); state.offsets.push( state.offsets.last().unwrap() + size.height, ); state.size.width = state.size.width.max(size.width); state.size.height += size.height; } } } } Task::Computing { .. } => { if !changes.is_empty() { // If changes happen during layout computation, // we simply restart the computation changes.clear(); state.recompute(self.content.len()); } } } // Recompute if new { let mut is_new = self.content.is_new.borrow_mut(); if *is_new { state.recompute(self.content.len()); *is_new = false; } } match &mut state.task { Task::Idle => {} Task::Computing { current, size, widths, offsets, } => { const MAX_BATCH_SIZE: usize = 50; let end = (*current + MAX_BATCH_SIZE).min(self.content.len()); let batch = &self.content.items[*current..end]; let mut max_width = size.width; let mut accumulated_height = offsets.last().copied().unwrap_or(0.0); for (i, item) in batch.iter().enumerate() { let mut element = (self.view_item)(*current + i, item); let mut tree = Tree::new(&element); let layout = element .as_widget_mut() .layout(&mut tree, renderer, &state.last_limits) .move_to((0.0, accumulated_height)); let bounds = layout.bounds(); max_width = max_width.max(bounds.width); accumulated_height += bounds.height; offsets.push(accumulated_height); widths.push(bounds.width); } *size = Size::new(max_width, accumulated_height); if end < self.content.len() { *current = end; } else { state.offsets = std::mem::take(offsets); state.widths = std::mem::take(widths); state.size = std::mem::take(size); state.task = Task::Idle; } } } let intrinsic_size = Size::new( state.size.width, state.size.height + self.content.len().saturating_sub(1) as f32 * self.spacing, ); let size = limits.resolve(Length::Shrink, Length::Shrink, intrinsic_size); layout::Node::new(size) } fn update( &mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { let state = tree.state.downcast_mut::(); let offset = layout.position() - Point::ORIGIN; self.visible_elements .iter_mut() .zip(&mut state.visible_layouts) .map(|(element, (index, layout, tree))| { element.as_widget_mut().update( tree, event, Layout::with_offset( offset + Vector::new(0.0, self.spacing * *index as f32), layout, ), cursor, renderer, clipboard, shell, viewport, ) }); if let Event::Window(window::Event::RedrawRequested(_)) = event { match &mut state.task { Task::Idle => {} Task::Computing { .. } => { shell.invalidate_layout(); shell.request_redraw(); } } let offsets = &state.offsets; let start = match binary_search_with_index_by(offsets, |i, height| { (*height + i.saturating_sub(1) as f32 * self.spacing) .partial_cmp(&(viewport.y - offset.y)) .unwrap_or(Ordering::Equal) }) { Ok(i) => i, Err(i) => i.saturating_sub(1), } .min(self.content.len()); let end = match binary_search_with_index_by(offsets, |i, height| { (*height + i.saturating_sub(1) as f32 * self.spacing) .partial_cmp(&(viewport.y + viewport.height - offset.y)) .unwrap_or(Ordering::Equal) }) { Ok(i) => i, Err(i) => i, } .min(self.content.len()); if state.visible_outdated || state.visible_layouts.len() != self.visible_elements.len() { self.visible_elements.clear(); state.visible_outdated = false; } // If view was recreated, we repopulate the visible elements // out of the internal visible layouts if self.visible_elements.is_empty() { self.visible_elements = state .visible_layouts .iter() .map(|(i, _, _)| { (self.view_item)(*i, &self.content.items[*i]) }) .collect(); } // Clear no longer visible elements let top = state .visible_layouts .iter() .take_while(|(i, _, _)| *i < start) .count(); let bottom = state .visible_layouts .iter() .rev() .take_while(|(i, _, _)| *i >= end) .count(); let _ = self.visible_elements.splice(..top, []); let _ = state.visible_layouts.splice(..top, []); let _ = self .visible_elements .splice(self.visible_elements.len() - bottom.., []); let _ = state .visible_layouts .splice(state.visible_layouts.len() - bottom.., []); // Prepend new visible elements if let Some(first_visible) = state.visible_layouts.first().map(|(i, _, _)| *i) { if start < first_visible { for (i, item) in self.content.items[start..first_visible] .iter() .enumerate() { let mut element = (self.view_item)(start + i, item); let mut tree = Tree::new(&element); let layout = element .as_widget_mut() .layout(&mut tree, renderer, &state.last_limits) .move_to(( 0.0, offsets[start + i] + (start + i) as f32 * self.spacing, )); state .visible_layouts .insert(i, (start + i, layout, tree)); self.visible_elements.insert(i, element); } } } // Append new visible elements let last_visible = state .visible_layouts .last() .map(|(i, _, _)| *i + 1) .unwrap_or(start); if last_visible < end { for (i, item) in self.content.items[last_visible..end].iter().enumerate() { let mut element = (self.view_item)(last_visible + i, item); let mut tree = Tree::new(&element); let layout = element .as_widget_mut() .layout(&mut tree, renderer, &state.last_limits) .move_to(( 0.0, offsets[last_visible + i] + (last_visible + i) as f32 * self.spacing, )); state.visible_layouts.push(( last_visible + i, layout, tree, )); self.visible_elements.push(element); } } } } fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { let state = tree.state.downcast_ref::(); let offset = layout.position() - Point::ORIGIN; for (element, (_item, layout, tree)) in self.visible_elements.iter().zip(&state.visible_layouts) { element.as_widget().draw( tree, renderer, theme, style, Layout::with_offset(offset, layout), cursor, viewport, ); } } fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { let state = tree.state.downcast_ref::(); let offset = layout.position() - Point::ORIGIN; self.visible_elements .iter() .zip(&state.visible_layouts) .map(|(element, (_item, layout, tree))| { element.as_widget().mouse_interaction( tree, Layout::with_offset(offset, layout), cursor, viewport, renderer, ) }) .max() .unwrap_or_default() } fn operate( &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn widget::Operation, ) { let state = tree.state.downcast_mut::(); let offset = layout.position() - Point::ORIGIN; for (element, (_item, layout, tree)) in self .visible_elements .iter_mut() .zip(&mut state.visible_layouts) { element.as_widget_mut().operate( tree, Layout::with_offset(offset, layout), renderer, operation, ); } } fn overlay<'b>( &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, viewport: &Rectangle, translation: Vector, ) -> Option> { let state = tree.state.downcast_mut::(); let offset = layout.position() - Point::ORIGIN; let children = self .visible_elements .iter_mut() .zip(&mut state.visible_layouts) .filter_map(|(child, (_item, layout, tree))| { child.as_widget_mut().overlay( tree, Layout::with_offset(offset, layout), renderer, viewport, translation, ) }) .collect::>(); (!children.is_empty()) .then(|| overlay::Group::with_children(children).overlay()) } } impl<'a, T, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, Theme: 'a, Renderer: core::Renderer + 'a, { fn from(list: List<'a, T, Message, Theme, Renderer>) -> Self { Self::new(list) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Content { items: Vec, is_new: RefCell, changes: RefCell>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Change { Updated { original: usize, current: usize }, Removed { original: usize, current: usize }, Pushed { original: usize, current: usize }, } impl Content { pub fn new() -> Self { Self { items: Vec::new(), is_new: RefCell::new(true), changes: RefCell::new(VecDeque::new()), } } pub fn with_items(items: Vec) -> Self { Self { items, is_new: RefCell::new(true), changes: RefCell::new(VecDeque::new()), } } pub fn get(&self, index: usize) -> Option<&T> { self.items.get(index) } pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { self.changes.borrow_mut().push_back(Change::Updated { original: index, current: index, }); self.items.get_mut(index) } pub fn push(&mut self, item: T) { let index = self.items.len(); self.changes.borrow_mut().push_back(Change::Pushed { original: index, current: index, }); self.items.push(item); } pub fn remove(&mut self, index: usize) -> T { let mut changes = self.changes.borrow_mut(); // Update pending changes after removal changes.retain_mut(|change| match change { Change::Updated { current, .. } | Change::Removed { current, .. } | Change::Pushed { current, .. } if *current > index => { // Decrement index of later changes *current -= 1; true } _ => true, }); changes.push_back(Change::Removed { original: index, current: index, }); self.items.remove(index) } pub fn is_empty(&self) -> bool { self.items.is_empty() } pub fn len(&self) -> usize { self.items.len() } pub fn into_vec(self) -> Vec { self.items } } impl Default for Content { fn default() -> Self { Self::new() } } impl FromIterator for Content { fn from_iter>(iter: I) -> Self { Self::with_items(iter.into_iter().collect()) } } /// SAFETY: Copied from the `std` library. #[allow(unsafe_code)] fn binary_search_with_index_by<'a, T, F>( slice: &'a [T], mut f: F, ) -> Result where F: FnMut(usize, &'a T) -> Ordering, { use std::cmp::Ordering::*; // INVARIANTS: // - 0 <= left <= left + size = right <= self.len() // - f returns Less for everything in self[..left] // - f returns Greater for everything in self[right..] let mut size = slice.len(); let mut left = 0; let mut right = size; while left < right { let mid = left + size / 2; // SAFETY: the while condition means `size` is strictly positive, so // `size/2 < size`. Thus `left + size/2 < left + size`, which // coupled with the `left + size <= self.len()` invariant means // we have `left + size/2 < self.len()`, and this is in-bounds. let cmp = f(mid, unsafe { slice.get_unchecked(mid) }); // This control flow produces conditional moves, which results in // fewer branches and instructions than if/else or matching on // cmp::Ordering. // This is x86 asm for u8: https://rust.godbolt.org/z/698eYffTx. left = if cmp == Less { mid + 1 } else { left }; right = if cmp == Greater { mid } else { right }; if cmp == Equal { return Ok(mid); } size = right - left; } Err(left) }