Infinite List widget from upstream feature/list-widget-reloaded branch
This commit is contained in:
parent
6829724ac0
commit
0b068e486e
6 changed files with 932 additions and 0 deletions
|
|
@ -10,6 +10,7 @@ use crate::core::window;
|
|||
use crate::core::{Element, Length, Size, Widget};
|
||||
use crate::float::{self, Float};
|
||||
use crate::keyed;
|
||||
use crate::list::{self, List};
|
||||
use crate::overlay;
|
||||
use crate::pane_grid::{self, PaneGrid};
|
||||
use crate::pick_list::{self, PickList};
|
||||
|
|
@ -1054,6 +1055,18 @@ where
|
|||
Scrollable::new(content)
|
||||
}
|
||||
|
||||
/// Creates a new [`List`] with the provided [`Content`] and
|
||||
/// closure to view an item of the [`List`].
|
||||
///
|
||||
/// [`List`]: crate::List
|
||||
/// [`Content`]: crate::list::Content
|
||||
pub fn list<'a, T, Message, Theme, Renderer>(
|
||||
content: &'a list::Content<T>,
|
||||
view_item: impl Fn(usize, &'a T) -> Element<'a, Message, Theme, Renderer> + 'a,
|
||||
) -> List<'a, T, Message, Theme, Renderer> {
|
||||
List::new(content, view_item)
|
||||
}
|
||||
|
||||
/// Creates a new [`Button`] with the provided content.
|
||||
///
|
||||
/// # Example
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ pub mod container;
|
|||
pub mod float;
|
||||
pub mod grid;
|
||||
pub mod keyed;
|
||||
pub mod list;
|
||||
pub mod overlay;
|
||||
pub mod pane_grid;
|
||||
pub mod pick_list;
|
||||
|
|
@ -68,6 +69,8 @@ pub use float::Float;
|
|||
#[doc(no_inline)]
|
||||
pub use grid::Grid;
|
||||
#[doc(no_inline)]
|
||||
pub use list::List;
|
||||
#[doc(no_inline)]
|
||||
pub use mouse_area::MouseArea;
|
||||
#[doc(no_inline)]
|
||||
pub use pane_grid::PaneGrid;
|
||||
|
|
|
|||
792
widget/src/list.rs
Normal file
792
widget/src/list.rs
Normal file
|
|
@ -0,0 +1,792 @@
|
|||
#![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<T>,
|
||||
spacing: f32,
|
||||
view_item:
|
||||
Box<dyn Fn(usize, &'a T) -> Element<'a, Message, Theme, Renderer> + 'a>,
|
||||
visible_elements: Vec<Element<'a, Message, Theme, Renderer>>,
|
||||
}
|
||||
|
||||
impl<'a, T, Message, Theme, Renderer> List<'a, T, Message, Theme, Renderer> {
|
||||
pub fn new(
|
||||
content: &'a Content<T>,
|
||||
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<Pixels>) -> Self {
|
||||
self.spacing = amount.into().0;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
last_limits: layout::Limits,
|
||||
visible_layouts: Vec<(usize, layout::Node, Tree)>,
|
||||
size: Size,
|
||||
offsets: Vec<f32>,
|
||||
widths: Vec<f32>,
|
||||
task: Task,
|
||||
visible_outdated: bool,
|
||||
}
|
||||
|
||||
enum Task {
|
||||
Idle,
|
||||
Computing {
|
||||
current: usize,
|
||||
offsets: Vec<f32>,
|
||||
widths: Vec<f32>,
|
||||
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<Message, Theme, Renderer>
|
||||
for List<'a, T, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
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<Length> {
|
||||
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::<State>();
|
||||
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::<State>();
|
||||
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::<State>();
|
||||
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::<State>();
|
||||
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::<State>();
|
||||
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<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
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::<Vec<_>>();
|
||||
|
||||
(!children.is_empty())
|
||||
.then(|| overlay::Group::with_children(children).overlay())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, Message, Theme, Renderer>
|
||||
From<List<'a, T, Message, Theme, Renderer>>
|
||||
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<T> {
|
||||
items: Vec<T>,
|
||||
is_new: RefCell<bool>,
|
||||
changes: RefCell<VecDeque<Change>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Change {
|
||||
Updated { original: usize, current: usize },
|
||||
Removed { original: usize, current: usize },
|
||||
Pushed { original: usize, current: usize },
|
||||
}
|
||||
|
||||
impl<T> Content<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
items: Vec::new(),
|
||||
is_new: RefCell::new(true),
|
||||
changes: RefCell::new(VecDeque::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_items(items: Vec<T>) -> 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<T> {
|
||||
self.items
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Content<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromIterator<T> for Content<T> {
|
||||
fn from_iter<I: IntoIterator<Item = T>>(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<usize, usize>
|
||||
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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue