Merge pull request #2628 from tarkah/fix/pane-grid-continuity

Fix/pane grid continuity
This commit is contained in:
Héctor 2024-10-24 15:04:23 +02:00 committed by GitHub
commit 17b35df160
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 194 additions and 151 deletions

View file

@ -83,7 +83,10 @@ where
new_size: Size, new_size: Size,
view: &dyn Fn(Size) -> Element<'a, Message, Theme, Renderer>, 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; return;
} }

View file

@ -157,7 +157,9 @@ pub struct PaneGrid<
Theme: Catalog, Theme: Catalog,
Renderer: core::Renderer, 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, width: Length,
height: Length, height: Length,
spacing: f32, spacing: f32,
@ -180,29 +182,19 @@ where
state: &'a State<T>, state: &'a State<T>,
view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>, view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Theme, Renderer>,
) -> Self { ) -> Self {
let contents = if let Some((pane, pane_state)) = let panes = state.panes.keys().copied().collect();
state.maximized.and_then(|pane| { let contents = state
state.panes.get(&pane).map(|pane_state| (pane, pane_state)) .panes
}) { .iter()
Contents::Maximized( .map(|(pane, pane_state)| match state.maximized() {
pane, Some(p) if *pane == p => view(*pane, pane_state, true),
view(pane, pane_state, true), _ => view(*pane, pane_state, false),
Node::Pane(pane), })
) .collect();
} else {
Contents::All(
state
.panes
.iter()
.map(|(pane, pane_state)| {
(*pane, view(*pane, pane_state, false))
})
.collect(),
&state.internal,
)
};
Self { Self {
internal: &state.internal,
panes,
contents, contents,
width: Length::Fill, width: Length::Fill,
height: Length::Fill, height: Length::Fill,
@ -248,7 +240,9 @@ where
where where
F: 'a + Fn(DragEvent) -> Message, 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 self
} }
@ -265,7 +259,9 @@ where
where where
F: 'a + Fn(ResizeEvent) -> Message, 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 self
} }
@ -291,12 +287,20 @@ where
} }
fn drag_enabled(&self) -> bool { fn drag_enabled(&self) -> bool {
(!self.contents.is_maximized()) self.internal
.maximized()
.is_none()
.then(|| self.on_drag.is_some()) .then(|| self.on_drag.is_some())
.unwrap_or_default() .unwrap_or_default()
} }
} }
#[derive(Default)]
struct Memory {
action: state::Action,
order: Vec<Pane>,
}
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for PaneGrid<'a, Message, Theme, Renderer> for PaneGrid<'a, Message, Theme, Renderer>
where where
@ -304,33 +308,47 @@ where
Renderer: core::Renderer, Renderer: core::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
tree::Tag::of::<state::Action>() tree::Tag::of::<Memory>()
} }
fn state(&self) -> tree::State { fn state(&self) -> tree::State {
tree::State::new(state::Action::Idle) tree::State::new(Memory::default())
} }
fn children(&self) -> Vec<Tree> { fn children(&self) -> Vec<Tree> {
self.contents self.contents.iter().map(Content::state).collect()
.iter()
.map(|(_, content)| content.state())
.collect()
} }
fn diff(&self, tree: &mut Tree) { fn diff(&self, tree: &mut Tree) {
match &self.contents { let Memory { order, .. } = tree.state.downcast_ref();
Contents::All(contents, _) => tree.diff_children_custom(
contents, // `Pane` always increments and is iterated by Ord so new
|state, (_, content)| content.diff(state), // states are always added at the end. We can simply remove
|(_, content)| content.state(), // states which no longer exist and `diff_children` will
), // diff the remaining values in the correct order and
Contents::Maximized(_, content, _) => tree.diff_children_custom( // add new states at the end
&[content],
|state, content| content.diff(state), let mut i = 0;
|content| content.state(), 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> { fn size(&self) -> Size<Length> {
@ -347,14 +365,23 @@ where
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
let size = limits.resolve(self.width, self.height, Size::ZERO); let size = limits.resolve(self.width, self.height, Size::ZERO);
let node = self.contents.layout(); let regions = self.internal.layout().pane_regions(self.spacing, size);
let regions = node.pane_regions(self.spacing, size);
let children = self let children = self
.contents .panes
.iter() .iter()
.copied()
.zip(&self.contents)
.zip(tree.children.iter_mut()) .zip(tree.children.iter_mut())
.filter_map(|((pane, content), tree)| { .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 region = regions.get(&pane)?;
let size = Size::new(region.width, region.height); let size = Size::new(region.width, region.height);
@ -379,11 +406,18 @@ where
operation: &mut dyn widget::Operation, operation: &mut dyn widget::Operation,
) { ) {
operation.container(None, layout.bounds(), &mut |operation| { operation.container(None, layout.bounds(), &mut |operation| {
self.contents self.panes
.iter() .iter()
.copied()
.zip(&self.contents)
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.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); content.operate(state, layout, renderer, operation);
}); });
}); });
@ -402,8 +436,8 @@ where
) -> event::Status { ) -> event::Status {
let mut event_status = event::Status::Ignored; let mut event_status = event::Status::Ignored;
let action = tree.state.downcast_mut::<state::Action>(); let Memory { action, .. } = tree.state.downcast_mut();
let node = self.contents.layout(); let node = self.internal.layout();
let on_drag = if self.drag_enabled() { let on_drag = if self.drag_enabled() {
&self.on_drag &self.on_drag
@ -448,7 +482,10 @@ where
layout, layout,
cursor_position, cursor_position,
shell, shell,
self.contents.iter(), self.panes
.iter()
.copied()
.zip(&self.contents),
&self.on_click, &self.on_click,
on_drag, on_drag,
); );
@ -460,7 +497,7 @@ where
layout, layout,
cursor_position, cursor_position,
shell, shell,
self.contents.iter(), self.panes.iter().copied().zip(&self.contents),
&self.on_click, &self.on_click,
on_drag, on_drag,
); );
@ -486,8 +523,10 @@ where
} }
} else { } else {
let dropped_region = self let dropped_region = self
.contents .panes
.iter() .iter()
.copied()
.zip(&self.contents)
.zip(layout.children()) .zip(layout.children())
.find_map(|(target, layout)| { .find_map(|(target, layout)| {
layout_region( layout_region(
@ -572,10 +611,17 @@ where
let picked_pane = action.picked_pane().map(|(pane, _)| pane); let picked_pane = action.picked_pane().map(|(pane, _)| pane);
self.contents self.panes
.iter_mut() .iter()
.copied()
.zip(&mut self.contents)
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
.filter(|(((pane, _), _), _)| {
self.internal
.maximized()
.map_or(true, |maximized| *pane == maximized)
})
.map(|(((pane, content), tree), layout)| { .map(|(((pane, content), tree), layout)| {
let is_picked = picked_pane == Some(pane); let is_picked = picked_pane == Some(pane);
@ -602,14 +648,14 @@ where
viewport: &Rectangle, viewport: &Rectangle,
renderer: &Renderer, renderer: &Renderer,
) -> mouse::Interaction { ) -> mouse::Interaction {
let action = tree.state.downcast_ref::<state::Action>(); let Memory { action, .. } = tree.state.downcast_ref();
if action.picked_pane().is_some() { if action.picked_pane().is_some() {
return mouse::Interaction::Grabbing; return mouse::Interaction::Grabbing;
} }
let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway);
let node = self.contents.layout(); let node = self.internal.layout();
let resize_axis = let resize_axis =
action.picked_split().map(|(_, axis)| axis).or_else(|| { action.picked_split().map(|(_, axis)| axis).or_else(|| {
@ -641,11 +687,18 @@ where
}; };
} }
self.contents self.panes
.iter() .iter()
.copied()
.zip(&self.contents)
.zip(&tree.children) .zip(&tree.children)
.zip(layout.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( content.mouse_interaction(
tree, tree,
layout, layout,
@ -669,16 +722,10 @@ where
cursor: mouse::Cursor, cursor: mouse::Cursor,
viewport: &Rectangle, viewport: &Rectangle,
) { ) {
let action = tree.state.downcast_ref::<state::Action>(); let Memory { action, .. } = tree.state.downcast_ref();
let node = self.contents.layout(); let node = self.internal.layout();
let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); 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)| { let picked_pane = action.picked_pane().filter(|(_, origin)| {
cursor cursor
.position() .position()
@ -747,8 +794,18 @@ where
let style = Catalog::style(theme, &self.class); let style = Catalog::style(theme, &self.class);
for ((id, (content, tree)), pane_layout) in for (((id, content), tree), pane_layout) in self
contents.zip(layout.children()) .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 { match picked_pane {
Some((dragging, origin)) if id == dragging => { Some((dragging, origin)) if id == dragging => {
@ -883,11 +940,21 @@ where
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
let children = self let children = self
.contents .panes
.iter_mut() .iter()
.copied()
.zip(&mut self.contents)
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.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) content.overlay(state, layout, renderer, translation)
}) })
.collect::<Vec<_>>(); .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`]. /// The appearance of a [`PaneGrid`].
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style { pub struct Style {

View file

@ -6,7 +6,8 @@ use crate::pane_grid::{
Axis, Configuration, Direction, Edge, Node, Pane, Region, Split, Target, 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`]. /// The state of a [`PaneGrid`].
/// ///
@ -25,17 +26,12 @@ pub struct State<T> {
/// The panes of the [`PaneGrid`]. /// The panes of the [`PaneGrid`].
/// ///
/// [`PaneGrid`]: super::PaneGrid /// [`PaneGrid`]: super::PaneGrid
pub panes: FxHashMap<Pane, T>, pub panes: BTreeMap<Pane, T>,
/// The internal state of the [`PaneGrid`]. /// The internal state of the [`PaneGrid`].
/// ///
/// [`PaneGrid`]: super::PaneGrid /// [`PaneGrid`]: super::PaneGrid
pub internal: Internal, pub internal: Internal,
/// The maximized [`Pane`] of the [`PaneGrid`].
///
/// [`PaneGrid`]: super::PaneGrid
pub(super) maximized: Option<Pane>,
} }
impl<T> State<T> { impl<T> State<T> {
@ -52,16 +48,12 @@ impl<T> State<T> {
/// Creates a new [`State`] with the given [`Configuration`]. /// Creates a new [`State`] with the given [`Configuration`].
pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self { pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self {
let mut panes = FxHashMap::default(); let mut panes = BTreeMap::default();
let internal = let internal =
Internal::from_configuration(&mut panes, config.into(), 0); Internal::from_configuration(&mut panes, config.into(), 0);
State { State { panes, internal }
panes,
internal,
maximized: None,
}
} }
/// Returns the total amount of panes in the [`State`]. /// 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.panes.insert(new_pane, state);
let _ = self.maximized.take(); let _ = self.internal.maximized.take();
Some((new_pane, new_split)) Some((new_pane, new_split))
} }
@ -228,8 +220,11 @@ impl<T> State<T> {
) { ) {
if let Some((state, _)) = self.close(pane) { if let Some((state, _)) = self.close(pane) {
if let Some((new_pane, _)) = self.split(axis, target, state) { 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 { if swap {
self.swap(target, new_pane); self.swap(target, pane);
} }
} }
} }
@ -259,13 +254,27 @@ impl<T> State<T> {
&mut self, &mut self,
axis: Axis, axis: Axis,
pane: Pane, pane: Pane,
swap: bool, inverse: bool,
) { ) {
if let Some((state, _)) = self.close(pane) { 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`]. /// Swaps the position of the provided panes in the [`State`].
/// ///
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you /// 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 /// Closes the given [`Pane`] and returns its internal state and its closest
/// sibling, if it exists. /// sibling, if it exists.
pub fn close(&mut self, pane: Pane) -> Option<(T, Pane)> { pub fn close(&mut self, pane: Pane) -> Option<(T, Pane)> {
if self.maximized == Some(pane) { if self.internal.maximized == Some(pane) {
let _ = self.maximized.take(); let _ = self.internal.maximized.take();
} }
if let Some(sibling) = self.internal.layout.remove(pane) { if let Some(sibling) = self.internal.layout.remove(pane) {
@ -319,7 +328,7 @@ impl<T> State<T> {
/// ///
/// [`PaneGrid`]: super::PaneGrid /// [`PaneGrid`]: super::PaneGrid
pub fn maximize(&mut self, pane: Pane) { 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 /// Restore the currently maximized [`Pane`] to it's normal size. All panes
@ -327,14 +336,14 @@ impl<T> State<T> {
/// ///
/// [`PaneGrid`]: super::PaneGrid /// [`PaneGrid`]: super::PaneGrid
pub fn restore(&mut self) { pub fn restore(&mut self) {
let _ = self.maximized.take(); let _ = self.internal.maximized.take();
} }
/// Returns the maximized [`Pane`] of the [`PaneGrid`]. /// Returns the maximized [`Pane`] of the [`PaneGrid`].
/// ///
/// [`PaneGrid`]: super::PaneGrid /// [`PaneGrid`]: super::PaneGrid
pub fn maximized(&self) -> Option<Pane> { pub fn maximized(&self) -> Option<Pane> {
self.maximized self.internal.maximized
} }
} }
@ -345,6 +354,7 @@ impl<T> State<T> {
pub struct Internal { pub struct Internal {
layout: Node, layout: Node,
last_id: usize, last_id: usize,
maximized: Option<Pane>,
} }
impl Internal { impl Internal {
@ -353,7 +363,7 @@ impl Internal {
/// ///
/// [`PaneGrid`]: super::PaneGrid /// [`PaneGrid`]: super::PaneGrid
pub fn from_configuration<T>( pub fn from_configuration<T>(
panes: &mut FxHashMap<Pane, T>, panes: &mut BTreeMap<Pane, T>,
content: Configuration<T>, content: Configuration<T>,
next_id: usize, next_id: usize,
) -> Self { ) -> 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`]. /// The current action of a [`PaneGrid`].
/// ///
/// [`PaneGrid`]: super::PaneGrid /// [`PaneGrid`]: super::PaneGrid
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Action { pub enum Action {
/// The [`PaneGrid`] is idle. /// The [`PaneGrid`] is idle.
/// ///
/// [`PaneGrid`]: super::PaneGrid /// [`PaneGrid`]: super::PaneGrid
#[default]
Idle, Idle,
/// A [`Pane`] in the [`PaneGrid`] is being dragged. /// 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
}
}