//! The state of a [`PaneGrid`]. //! //! [`PaneGrid`]: super::PaneGrid use crate::core::{Point, Size}; use crate::pane_grid::{ Axis, Configuration, Direction, Edge, Node, Pane, Region, Split, Target, }; use std::collections::HashMap; /// The state of a [`PaneGrid`]. /// /// It keeps track of the state of each [`Pane`] and the position of each /// [`Split`]. /// /// The [`State`] needs to own any mutable contents a [`Pane`] may need. This is /// why this struct is generic over the type `T`. Values of this type are /// provided to the view function of [`PaneGrid::new`] for displaying each /// [`Pane`]. /// /// [`PaneGrid`]: super::PaneGrid /// [`PaneGrid::new`]: super::PaneGrid::new #[derive(Debug, Clone)] pub struct State { /// The panes of the [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid pub panes: HashMap, /// 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, } impl State { /// Creates a new [`State`], initializing the first pane with the provided /// state. /// /// Alongside the [`State`], it returns the first [`Pane`] identifier. pub fn new(first_pane_state: T) -> (Self, Pane) { ( Self::with_configuration(Configuration::Pane(first_pane_state)), Pane(0), ) } /// Creates a new [`State`] with the given [`Configuration`]. pub fn with_configuration(config: impl Into>) -> Self { let mut panes = HashMap::new(); let internal = Internal::from_configuration(&mut panes, config.into(), 0); State { panes, internal, maximized: None, } } /// Returns the total amount of panes in the [`State`]. pub fn len(&self) -> usize { self.panes.len() } /// Returns `true` if the amount of panes in the [`State`] is 0. pub fn is_empty(&self) -> bool { self.len() == 0 } /// Returns the internal state of the given [`Pane`], if it exists. pub fn get(&self, pane: Pane) -> Option<&T> { self.panes.get(&pane) } /// Returns the internal state of the given [`Pane`] with mutability, if it /// exists. pub fn get_mut(&mut self, pane: Pane) -> Option<&mut T> { self.panes.get_mut(&pane) } /// Returns an iterator over all the panes of the [`State`], alongside its /// internal state. pub fn iter(&self) -> impl Iterator { self.panes.iter() } /// Returns a mutable iterator over all the panes of the [`State`], /// alongside its internal state. pub fn iter_mut(&mut self) -> impl Iterator { self.panes.iter_mut() } /// Returns the layout of the [`State`]. pub fn layout(&self) -> &Node { &self.internal.layout } /// Returns the adjacent [`Pane`] of another [`Pane`] in the given /// direction, if there is one. pub fn adjacent(&self, pane: Pane, direction: Direction) -> Option { let regions = self .internal .layout .pane_regions(0.0, Size::new(4096.0, 4096.0)); let current_region = regions.get(&pane)?; let target = match direction { Direction::Left => { Point::new(current_region.x - 1.0, current_region.y + 1.0) } Direction::Right => Point::new( current_region.x + current_region.width + 1.0, current_region.y + 1.0, ), Direction::Up => { Point::new(current_region.x + 1.0, current_region.y - 1.0) } Direction::Down => Point::new( current_region.x + 1.0, current_region.y + current_region.height + 1.0, ), }; let mut colliding_regions = regions.iter().filter(|(_, region)| region.contains(target)); let (pane, _) = colliding_regions.next()?; Some(*pane) } /// Splits the given [`Pane`] into two in the given [`Axis`] and /// initializing the new [`Pane`] with the provided internal state. pub fn split( &mut self, axis: Axis, pane: Pane, state: T, ) -> Option<(Pane, Split)> { self.split_node(axis, Some(pane), state, false) } /// Split a target [`Pane`] with a given [`Pane`] on a given [`Region`]. /// /// Panes will be swapped by default for [`Region::Center`]. pub fn split_with(&mut self, target: Pane, pane: Pane, region: Region) { match region { Region::Center => self.swap(pane, target), Region::Edge(edge) => match edge { Edge::Top => { self.split_and_swap(Axis::Horizontal, target, pane, true); } Edge::Bottom => { self.split_and_swap(Axis::Horizontal, target, pane, false); } Edge::Left => { self.split_and_swap(Axis::Vertical, target, pane, true); } Edge::Right => { self.split_and_swap(Axis::Vertical, target, pane, false); } }, } } /// Drops the given [`Pane`] into the provided [`Target`]. pub fn drop(&mut self, pane: Pane, target: Target) { match target { Target::Edge(edge) => self.move_to_edge(pane, edge), Target::Pane(target, region) => { self.split_with(target, pane, region); } } } fn split_node( &mut self, axis: Axis, pane: Option, state: T, inverse: bool, ) -> Option<(Pane, Split)> { let node = if let Some(pane) = pane { self.internal.layout.find(pane)? } else { // Major node &mut self.internal.layout }; let new_pane = { self.internal.last_id = self.internal.last_id.checked_add(1)?; Pane(self.internal.last_id) }; let new_split = { self.internal.last_id = self.internal.last_id.checked_add(1)?; Split(self.internal.last_id) }; if inverse { node.split_inverse(new_split, axis, new_pane); } else { node.split(new_split, axis, new_pane); } let _ = self.panes.insert(new_pane, state); let _ = self.maximized.take(); Some((new_pane, new_split)) } fn split_and_swap( &mut self, axis: Axis, target: Pane, pane: Pane, swap: bool, ) { if let Some((state, _)) = self.close(pane) { if let Some((new_pane, _)) = self.split(axis, target, state) { if swap { self.swap(target, new_pane); } } } } /// Move [`Pane`] to an [`Edge`] of the [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid pub fn move_to_edge(&mut self, pane: Pane, edge: Edge) { match edge { Edge::Top => { self.split_major_node_and_swap(Axis::Horizontal, pane, true); } Edge::Bottom => { self.split_major_node_and_swap(Axis::Horizontal, pane, false); } Edge::Left => { self.split_major_node_and_swap(Axis::Vertical, pane, true); } Edge::Right => { self.split_major_node_and_swap(Axis::Vertical, pane, false); } } } fn split_major_node_and_swap( &mut self, axis: Axis, pane: Pane, swap: bool, ) { if let Some((state, _)) = self.close(pane) { let _ = self.split_node(axis, None, state, swap); } } /// Swaps the position of the provided panes in the [`State`]. /// /// If you want to swap panes on drag and drop in your [`PaneGrid`], you /// will need to call this method when handling a [`DragEvent`]. /// /// [`PaneGrid`]: super::PaneGrid /// [`DragEvent`]: super::DragEvent pub fn swap(&mut self, a: Pane, b: Pane) { self.internal.layout.update(&|node| match node { Node::Split { .. } => {} Node::Pane(pane) => { if *pane == a { *node = Node::Pane(b); } else if *pane == b { *node = Node::Pane(a); } } }); } /// Resizes two panes by setting the position of the provided [`Split`]. /// /// The ratio is a value in [0, 1], representing the exact position of a /// [`Split`] between two panes. /// /// If you want to enable resize interactions in your [`PaneGrid`], you will /// need to call this method when handling a [`ResizeEvent`]. /// /// [`PaneGrid`]: super::PaneGrid /// [`ResizeEvent`]: super::ResizeEvent pub fn resize(&mut self, split: Split, ratio: f32) { let _ = self.internal.layout.resize(split, ratio); } /// 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 let Some(sibling) = self.internal.layout.remove(pane) { self.panes.remove(&pane).map(|state| (state, sibling)) } else { None } } /// Maximize the given [`Pane`]. Only this pane will be rendered by the /// [`PaneGrid`] until [`Self::restore()`] is called. /// /// [`PaneGrid`]: super::PaneGrid pub fn maximize(&mut self, pane: Pane) { self.maximized = Some(pane); } /// Restore the currently maximized [`Pane`] to it's normal size. All panes /// will be rendered by the [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid pub fn restore(&mut self) { let _ = self.maximized.take(); } /// Returns the maximized [`Pane`] of the [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid pub fn maximized(&self) -> Option { self.maximized } } /// The internal state of a [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid #[derive(Debug, Clone)] pub struct Internal { layout: Node, last_id: usize, } impl Internal { /// Initializes the [`Internal`] state of a [`PaneGrid`] from a /// [`Configuration`]. /// /// [`PaneGrid`]: super::PaneGrid pub fn from_configuration( panes: &mut HashMap, content: Configuration, next_id: usize, ) -> Self { let (layout, last_id) = match content { Configuration::Split { axis, ratio, a, b } => { let Internal { layout: a, last_id: next_id, .. } = Self::from_configuration(panes, *a, next_id); let Internal { layout: b, last_id: next_id, .. } = Self::from_configuration(panes, *b, next_id); ( Node::Split { id: Split(next_id), axis, ratio, a: Box::new(a), b: Box::new(b), }, next_id + 1, ) } Configuration::Pane(state) => { let id = Pane(next_id); let _ = panes.insert(id, state); (Node::Pane(id), next_id + 1) } }; Self { layout, last_id } } } /// The current action of a [`PaneGrid`]. /// /// [`PaneGrid`]: super::PaneGrid #[derive(Debug, Clone, Copy, PartialEq)] pub enum Action { /// The [`PaneGrid`] is idle. /// /// [`PaneGrid`]: super::PaneGrid Idle, /// A [`Pane`] in the [`PaneGrid`] is being dragged. /// /// [`PaneGrid`]: super::PaneGrid Dragging { /// The [`Pane`] being dragged. pane: Pane, /// The starting [`Point`] of the drag interaction. origin: Point, }, /// A [`Split`] in the [`PaneGrid`] is being dragged. /// /// [`PaneGrid`]: super::PaneGrid Resizing { /// The [`Split`] being dragged. split: Split, /// The [`Axis`] of the [`Split`]. axis: Axis, }, } impl Action { /// Returns the current [`Pane`] that is being dragged, if any. pub fn picked_pane(&self) -> Option<(Pane, Point)> { match *self { Action::Dragging { pane, origin, .. } => Some((pane, origin)), _ => None, } } /// Returns the current [`Split`] that is being dragged, if any. pub fn picked_split(&self) -> Option<(Split, Axis)> { match *self { Action::Resizing { split, axis, .. } => Some((split, axis)), _ => None, } } } impl Internal { /// The layout [`Node`] of the [`Internal`] state pub fn layout(&self) -> &Node { &self.layout } }