diff options
author | 2024-10-24 15:04:23 +0200 | |
---|---|---|
committer | 2024-10-24 15:04:23 +0200 | |
commit | 17b35df160d38b4d41917bdc00cdecda4816baa9 (patch) | |
tree | e151b781cb424b50caed6f4eceb6d28131a96cc6 /widget | |
parent | f4d03870dd1365e8931c6f8e203eda940de735d6 (diff) | |
parent | d08bc6e45d12c7a5e4037c20673ff832eb7802ec (diff) | |
download | iced-17b35df160d38b4d41917bdc00cdecda4816baa9.tar.gz iced-17b35df160d38b4d41917bdc00cdecda4816baa9.tar.bz2 iced-17b35df160d38b4d41917bdc00cdecda4816baa9.zip |
Merge pull request #2628 from tarkah/fix/pane-grid-continuity
Fix/pane grid continuity
Diffstat (limited to '')
-rw-r--r-- | widget/src/lazy/responsive.rs | 5 | ||||
-rw-r--r-- | widget/src/pane_grid.rs | 257 | ||||
-rw-r--r-- | widget/src/pane_grid/state.rs | 83 |
3 files changed, 194 insertions, 151 deletions
diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index a7a99f56..a6c40ab0 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -83,7 +83,10 @@ where new_size: Size, view: &dyn Fn(Size) -> Element<'a, Message, Theme, Renderer>, ) { - if self.size == new_size { + let is_tree_empty = + tree.tag == tree::Tag::stateless() && tree.children.is_empty(); + + if !is_tree_empty && self.size == new_size { return; } diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index e6fda660..b4ed4b64 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -157,7 +157,9 @@ pub struct PaneGrid< Theme: Catalog, Renderer: core::Renderer, { - contents: Contents<'a, Content<'a, Message, Theme, Renderer>>, + internal: &'a state::Internal, + panes: Vec<Pane>, + contents: Vec<Content<'a, Message, Theme, Renderer>>, width: Length, height: Length, spacing: f32, @@ -180,29 +182,19 @@ where state: &'a State<T>, view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>, ) -> Self { - let contents = if let Some((pane, pane_state)) = - state.maximized.and_then(|pane| { - state.panes.get(&pane).map(|pane_state| (pane, pane_state)) - }) { - Contents::Maximized( - pane, - view(pane, pane_state, true), - Node::Pane(pane), - ) - } else { - Contents::All( - state - .panes - .iter() - .map(|(pane, pane_state)| { - (*pane, view(*pane, pane_state, false)) - }) - .collect(), - &state.internal, - ) - }; + let panes = state.panes.keys().copied().collect(); + let contents = state + .panes + .iter() + .map(|(pane, pane_state)| match state.maximized() { + Some(p) if *pane == p => view(*pane, pane_state, true), + _ => view(*pane, pane_state, false), + }) + .collect(); Self { + internal: &state.internal, + panes, contents, width: Length::Fill, height: Length::Fill, @@ -248,7 +240,9 @@ where where F: 'a + Fn(DragEvent) -> Message, { - self.on_drag = Some(Box::new(f)); + if self.internal.maximized().is_none() { + self.on_drag = Some(Box::new(f)); + } self } @@ -265,7 +259,9 @@ where where F: 'a + Fn(ResizeEvent) -> Message, { - self.on_resize = Some((leeway.into().0, Box::new(f))); + if self.internal.maximized().is_none() { + self.on_resize = Some((leeway.into().0, Box::new(f))); + } self } @@ -291,12 +287,20 @@ where } fn drag_enabled(&self) -> bool { - (!self.contents.is_maximized()) + self.internal + .maximized() + .is_none() .then(|| self.on_drag.is_some()) .unwrap_or_default() } } +#[derive(Default)] +struct Memory { + action: state::Action, + order: Vec<Pane>, +} + impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> for PaneGrid<'a, Message, Theme, Renderer> where @@ -304,33 +308,47 @@ where Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { - tree::Tag::of::<state::Action>() + tree::Tag::of::<Memory>() } fn state(&self) -> tree::State { - tree::State::new(state::Action::Idle) + tree::State::new(Memory::default()) } fn children(&self) -> Vec<Tree> { - self.contents - .iter() - .map(|(_, content)| content.state()) - .collect() + self.contents.iter().map(Content::state).collect() } fn diff(&self, tree: &mut Tree) { - match &self.contents { - Contents::All(contents, _) => tree.diff_children_custom( - contents, - |state, (_, content)| content.diff(state), - |(_, content)| content.state(), - ), - Contents::Maximized(_, content, _) => tree.diff_children_custom( - &[content], - |state, content| content.diff(state), - |content| content.state(), - ), - } + let Memory { order, .. } = tree.state.downcast_ref(); + + // `Pane` always increments and is iterated by Ord so new + // states are always added at the end. We can simply remove + // states which no longer exist and `diff_children` will + // diff the remaining values in the correct order and + // add new states at the end + + let mut i = 0; + let mut j = 0; + tree.children.retain(|_| { + let retain = self.panes.get(i) == order.get(j); + + if retain { + i += 1; + } + j += 1; + + retain + }); + + tree.diff_children_custom( + &self.contents, + |state, content| content.diff(state), + Content::state, + ); + + let Memory { order, .. } = tree.state.downcast_mut(); + order.clone_from(&self.panes); } fn size(&self) -> Size<Length> { @@ -347,14 +365,23 @@ where limits: &layout::Limits, ) -> layout::Node { let size = limits.resolve(self.width, self.height, Size::ZERO); - let node = self.contents.layout(); - let regions = node.pane_regions(self.spacing, size); + let regions = self.internal.layout().pane_regions(self.spacing, size); let children = self - .contents + .panes .iter() + .copied() + .zip(&self.contents) .zip(tree.children.iter_mut()) .filter_map(|((pane, content), tree)| { + if self + .internal + .maximized() + .is_some_and(|maximized| maximized != pane) + { + return Some(layout::Node::new(Size::ZERO)); + } + let region = regions.get(&pane)?; let size = Size::new(region.width, region.height); @@ -379,11 +406,18 @@ where operation: &mut dyn widget::Operation, ) { operation.container(None, layout.bounds(), &mut |operation| { - self.contents + self.panes .iter() + .copied() + .zip(&self.contents) .zip(&mut tree.children) .zip(layout.children()) - .for_each(|(((_pane, content), state), layout)| { + .filter(|(((pane, _), _), _)| { + self.internal + .maximized() + .map_or(true, |maximized| *pane == maximized) + }) + .for_each(|(((_, content), state), layout)| { content.operate(state, layout, renderer, operation); }); }); @@ -402,8 +436,8 @@ where ) -> event::Status { let mut event_status = event::Status::Ignored; - let action = tree.state.downcast_mut::<state::Action>(); - let node = self.contents.layout(); + let Memory { action, .. } = tree.state.downcast_mut(); + let node = self.internal.layout(); let on_drag = if self.drag_enabled() { &self.on_drag @@ -448,7 +482,10 @@ where layout, cursor_position, shell, - self.contents.iter(), + self.panes + .iter() + .copied() + .zip(&self.contents), &self.on_click, on_drag, ); @@ -460,7 +497,7 @@ where layout, cursor_position, shell, - self.contents.iter(), + self.panes.iter().copied().zip(&self.contents), &self.on_click, on_drag, ); @@ -486,8 +523,10 @@ where } } else { let dropped_region = self - .contents + .panes .iter() + .copied() + .zip(&self.contents) .zip(layout.children()) .find_map(|(target, layout)| { layout_region( @@ -572,10 +611,17 @@ where let picked_pane = action.picked_pane().map(|(pane, _)| pane); - self.contents - .iter_mut() + self.panes + .iter() + .copied() + .zip(&mut self.contents) .zip(&mut tree.children) .zip(layout.children()) + .filter(|(((pane, _), _), _)| { + self.internal + .maximized() + .map_or(true, |maximized| *pane == maximized) + }) .map(|(((pane, content), tree), layout)| { let is_picked = picked_pane == Some(pane); @@ -602,14 +648,14 @@ where viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - let action = tree.state.downcast_ref::<state::Action>(); + let Memory { action, .. } = tree.state.downcast_ref(); if action.picked_pane().is_some() { return mouse::Interaction::Grabbing; } let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); - let node = self.contents.layout(); + let node = self.internal.layout(); let resize_axis = action.picked_split().map(|(_, axis)| axis).or_else(|| { @@ -641,11 +687,18 @@ where }; } - self.contents + self.panes .iter() + .copied() + .zip(&self.contents) .zip(&tree.children) .zip(layout.children()) - .map(|(((_pane, content), tree), layout)| { + .filter(|(((pane, _), _), _)| { + self.internal + .maximized() + .map_or(true, |maximized| *pane == maximized) + }) + .map(|(((_, content), tree), layout)| { content.mouse_interaction( tree, layout, @@ -669,16 +722,10 @@ where cursor: mouse::Cursor, viewport: &Rectangle, ) { - let action = tree.state.downcast_ref::<state::Action>(); - let node = self.contents.layout(); + let Memory { action, .. } = tree.state.downcast_ref(); + let node = self.internal.layout(); let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); - let contents = self - .contents - .iter() - .zip(&tree.children) - .map(|((pane, content), tree)| (pane, (content, tree))); - let picked_pane = action.picked_pane().filter(|(_, origin)| { cursor .position() @@ -747,8 +794,18 @@ where let style = Catalog::style(theme, &self.class); - for ((id, (content, tree)), pane_layout) in - contents.zip(layout.children()) + for (((id, content), tree), pane_layout) in self + .panes + .iter() + .copied() + .zip(&self.contents) + .zip(&tree.children) + .zip(layout.children()) + .filter(|(((pane, _), _), _)| { + self.internal + .maximized() + .map_or(true, |maximized| maximized == *pane) + }) { match picked_pane { Some((dragging, origin)) if id == dragging => { @@ -883,11 +940,21 @@ where translation: Vector, ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { let children = self - .contents - .iter_mut() + .panes + .iter() + .copied() + .zip(&mut self.contents) .zip(&mut tree.children) .zip(layout.children()) - .filter_map(|(((_, content), state), layout)| { + .filter_map(|(((pane, content), state), layout)| { + if self + .internal + .maximized() + .is_some_and(|maximized| maximized != pane) + { + return None; + } + content.overlay(state, layout, renderer, translation) }) .collect::<Vec<_>>(); @@ -1136,52 +1203,6 @@ fn hovered_split<'a>( }) } -/// The visible contents of the [`PaneGrid`] -#[derive(Debug)] -pub enum Contents<'a, T> { - /// All panes are visible - All(Vec<(Pane, T)>, &'a state::Internal), - /// A maximized pane is visible - Maximized(Pane, T, Node), -} - -impl<'a, T> Contents<'a, T> { - /// Returns the layout [`Node`] of the [`Contents`] - pub fn layout(&self) -> &Node { - match self { - Contents::All(_, state) => state.layout(), - Contents::Maximized(_, _, layout) => layout, - } - } - - /// Returns an iterator over the values of the [`Contents`] - pub fn iter(&self) -> Box<dyn Iterator<Item = (Pane, &T)> + '_> { - match self { - Contents::All(contents, _) => Box::new( - contents.iter().map(|(pane, content)| (*pane, content)), - ), - Contents::Maximized(pane, content, _) => { - Box::new(std::iter::once((*pane, content))) - } - } - } - - fn iter_mut(&mut self) -> Box<dyn Iterator<Item = (Pane, &mut T)> + '_> { - match self { - Contents::All(contents, _) => Box::new( - contents.iter_mut().map(|(pane, content)| (*pane, content)), - ), - Contents::Maximized(pane, content, _) => { - Box::new(std::iter::once((*pane, content))) - } - } - } - - fn is_maximized(&self) -> bool { - matches!(self, Self::Maximized(..)) - } -} - /// The appearance of a [`PaneGrid`]. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Style { diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index c20c3b9c..2f8a64ea 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -6,7 +6,8 @@ use crate::pane_grid::{ Axis, Configuration, Direction, Edge, Node, Pane, Region, Split, Target, }; -use rustc_hash::FxHashMap; +use std::borrow::Cow; +use std::collections::BTreeMap; /// The state of a [`PaneGrid`]. /// @@ -25,17 +26,12 @@ pub struct State<T> { /// The panes of the [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid - pub panes: FxHashMap<Pane, T>, + pub panes: BTreeMap<Pane, T>, /// The internal state of the [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid pub internal: Internal, - - /// The maximized [`Pane`] of the [`PaneGrid`]. - /// - /// [`PaneGrid`]: super::PaneGrid - pub(super) maximized: Option<Pane>, } impl<T> State<T> { @@ -52,16 +48,12 @@ impl<T> State<T> { /// Creates a new [`State`] with the given [`Configuration`]. pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self { - let mut panes = FxHashMap::default(); + let mut panes = BTreeMap::default(); let internal = Internal::from_configuration(&mut panes, config.into(), 0); - State { - panes, - internal, - maximized: None, - } + State { panes, internal } } /// Returns the total amount of panes in the [`State`]. @@ -214,7 +206,7 @@ impl<T> State<T> { } let _ = self.panes.insert(new_pane, state); - let _ = self.maximized.take(); + let _ = self.internal.maximized.take(); Some((new_pane, new_split)) } @@ -228,8 +220,11 @@ impl<T> State<T> { ) { if let Some((state, _)) = self.close(pane) { if let Some((new_pane, _)) = self.split(axis, target, state) { + // Ensure new node corresponds to original closed `Pane` for state continuity + self.relabel(new_pane, pane); + if swap { - self.swap(target, new_pane); + self.swap(target, pane); } } } @@ -259,13 +254,27 @@ impl<T> State<T> { &mut self, axis: Axis, pane: Pane, - swap: bool, + inverse: bool, ) { if let Some((state, _)) = self.close(pane) { - let _ = self.split_node(axis, None, state, swap); + if let Some((new_pane, _)) = + self.split_node(axis, None, state, inverse) + { + // Ensure new node corresponds to original closed `Pane` for state continuity + self.relabel(new_pane, pane); + } } } + fn relabel(&mut self, target: Pane, label: Pane) { + self.swap(target, label); + + let _ = self + .panes + .remove(&target) + .and_then(|state| self.panes.insert(label, state)); + } + /// Swaps the position of the provided panes in the [`State`]. /// /// If you want to swap panes on drag and drop in your [`PaneGrid`], you @@ -303,8 +312,8 @@ impl<T> State<T> { /// Closes the given [`Pane`] and returns its internal state and its closest /// sibling, if it exists. pub fn close(&mut self, pane: Pane) -> Option<(T, Pane)> { - if self.maximized == Some(pane) { - let _ = self.maximized.take(); + if self.internal.maximized == Some(pane) { + let _ = self.internal.maximized.take(); } if let Some(sibling) = self.internal.layout.remove(pane) { @@ -319,7 +328,7 @@ impl<T> State<T> { /// /// [`PaneGrid`]: super::PaneGrid pub fn maximize(&mut self, pane: Pane) { - self.maximized = Some(pane); + self.internal.maximized = Some(pane); } /// Restore the currently maximized [`Pane`] to it's normal size. All panes @@ -327,14 +336,14 @@ impl<T> State<T> { /// /// [`PaneGrid`]: super::PaneGrid pub fn restore(&mut self) { - let _ = self.maximized.take(); + let _ = self.internal.maximized.take(); } /// Returns the maximized [`Pane`] of the [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid pub fn maximized(&self) -> Option<Pane> { - self.maximized + self.internal.maximized } } @@ -345,6 +354,7 @@ impl<T> State<T> { pub struct Internal { layout: Node, last_id: usize, + maximized: Option<Pane>, } impl Internal { @@ -353,7 +363,7 @@ impl Internal { /// /// [`PaneGrid`]: super::PaneGrid pub fn from_configuration<T>( - panes: &mut FxHashMap<Pane, T>, + panes: &mut BTreeMap<Pane, T>, content: Configuration<T>, next_id: usize, ) -> Self { @@ -390,18 +400,34 @@ impl Internal { } }; - Self { layout, last_id } + Self { + layout, + last_id, + maximized: None, + } + } + + pub(super) fn layout(&self) -> Cow<'_, Node> { + match self.maximized { + Some(pane) => Cow::Owned(Node::Pane(pane)), + None => Cow::Borrowed(&self.layout), + } + } + + pub(super) fn maximized(&self) -> Option<Pane> { + self.maximized } } /// The current action of a [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub enum Action { /// The [`PaneGrid`] is idle. /// /// [`PaneGrid`]: super::PaneGrid + #[default] Idle, /// A [`Pane`] in the [`PaneGrid`] is being dragged. /// @@ -440,10 +466,3 @@ impl Action { } } } - -impl Internal { - /// The layout [`Node`] of the [`Internal`] state - pub fn layout(&self) -> &Node { - &self.layout - } -} |