iced_widget/pane_grid/
state.rs

1//! The state of a [`PaneGrid`].
2//!
3//! [`PaneGrid`]: super::PaneGrid
4use crate::core::{Point, Size};
5use crate::pane_grid::{Axis, Configuration, Direction, Edge, Node, Pane, Region, Split, Target};
6
7use std::borrow::Cow;
8use std::collections::BTreeMap;
9
10/// The state of a [`PaneGrid`].
11///
12/// It keeps track of the state of each [`Pane`] and the position of each
13/// [`Split`].
14///
15/// The [`State`] needs to own any mutable contents a [`Pane`] may need. This is
16/// why this struct is generic over the type `T`. Values of this type are
17/// provided to the view function of [`PaneGrid::new`] for displaying each
18/// [`Pane`].
19///
20/// [`PaneGrid`]: super::PaneGrid
21/// [`PaneGrid::new`]: super::PaneGrid::new
22#[derive(Debug, Clone)]
23pub struct State<T> {
24    /// The panes of the [`PaneGrid`].
25    ///
26    /// [`PaneGrid`]: super::PaneGrid
27    pub panes: BTreeMap<Pane, T>,
28
29    /// The internal state of the [`PaneGrid`].
30    ///
31    /// [`PaneGrid`]: super::PaneGrid
32    pub internal: Internal,
33}
34
35impl<T> State<T> {
36    /// Creates a new [`State`], initializing the first pane with the provided
37    /// state.
38    ///
39    /// Alongside the [`State`], it returns the first [`Pane`] identifier.
40    pub fn new(first_pane_state: T) -> (Self, Pane) {
41        (
42            Self::with_configuration(Configuration::Pane(first_pane_state)),
43            Pane(0),
44        )
45    }
46
47    /// Creates a new [`State`] with the given [`Configuration`].
48    pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self {
49        let mut panes = BTreeMap::default();
50
51        let internal = Internal::from_configuration(&mut panes, config.into(), 0);
52
53        State { panes, internal }
54    }
55
56    /// Returns the total amount of panes in the [`State`].
57    pub fn len(&self) -> usize {
58        self.panes.len()
59    }
60
61    /// Returns `true` if the amount of panes in the [`State`] is 0.
62    pub fn is_empty(&self) -> bool {
63        self.len() == 0
64    }
65
66    /// Returns the internal state of the given [`Pane`], if it exists.
67    pub fn get(&self, pane: Pane) -> Option<&T> {
68        self.panes.get(&pane)
69    }
70
71    /// Returns the internal state of the given [`Pane`] with mutability, if it
72    /// exists.
73    pub fn get_mut(&mut self, pane: Pane) -> Option<&mut T> {
74        self.panes.get_mut(&pane)
75    }
76
77    /// Returns an iterator over all the panes of the [`State`], alongside its
78    /// internal state.
79    pub fn iter(&self) -> impl Iterator<Item = (&Pane, &T)> {
80        self.panes.iter()
81    }
82
83    /// Returns a mutable iterator over all the panes of the [`State`],
84    /// alongside its internal state.
85    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Pane, &mut T)> {
86        self.panes.iter_mut()
87    }
88
89    /// Returns the layout of the [`State`].
90    pub fn layout(&self) -> &Node {
91        &self.internal.layout
92    }
93
94    /// Returns the adjacent [`Pane`] of another [`Pane`] in the given
95    /// direction, if there is one.
96    pub fn adjacent(&self, pane: Pane, direction: Direction) -> Option<Pane> {
97        let regions = self
98            .internal
99            .layout
100            .pane_regions(0.0, 0.0, Size::new(4096.0, 4096.0));
101
102        let current_region = regions.get(&pane)?;
103
104        let target = match direction {
105            Direction::Left => Point::new(current_region.x - 1.0, current_region.y + 1.0),
106            Direction::Right => Point::new(
107                current_region.x + current_region.width + 1.0,
108                current_region.y + 1.0,
109            ),
110            Direction::Up => Point::new(current_region.x + 1.0, current_region.y - 1.0),
111            Direction::Down => Point::new(
112                current_region.x + 1.0,
113                current_region.y + current_region.height + 1.0,
114            ),
115        };
116
117        let mut colliding_regions = regions.iter().filter(|(_, region)| region.contains(target));
118
119        let (pane, _) = colliding_regions.next()?;
120
121        Some(*pane)
122    }
123
124    /// Splits the given [`Pane`] into two in the given [`Axis`] and
125    /// initializing the new [`Pane`] with the provided internal state.
126    pub fn split(&mut self, axis: Axis, pane: Pane, state: T) -> Option<(Pane, Split)> {
127        self.split_node(axis, Some(pane), state, false)
128    }
129
130    /// Split a target [`Pane`] with a given [`Pane`] on a given [`Region`].
131    ///
132    /// Panes will be swapped by default for [`Region::Center`].
133    pub fn split_with(&mut self, target: Pane, pane: Pane, region: Region) {
134        match region {
135            Region::Center => self.swap(pane, target),
136            Region::Edge(edge) => match edge {
137                Edge::Top => {
138                    self.split_and_swap(Axis::Horizontal, target, pane, true);
139                }
140                Edge::Bottom => {
141                    self.split_and_swap(Axis::Horizontal, target, pane, false);
142                }
143                Edge::Left => {
144                    self.split_and_swap(Axis::Vertical, target, pane, true);
145                }
146                Edge::Right => {
147                    self.split_and_swap(Axis::Vertical, target, pane, false);
148                }
149            },
150        }
151    }
152
153    /// Drops the given [`Pane`] into the provided [`Target`].
154    pub fn drop(&mut self, pane: Pane, target: Target) {
155        match target {
156            Target::Edge(edge) => self.move_to_edge(pane, edge),
157            Target::Pane(target, region) => {
158                self.split_with(target, pane, region);
159            }
160        }
161    }
162
163    fn split_node(
164        &mut self,
165        axis: Axis,
166        pane: Option<Pane>,
167        state: T,
168        inverse: bool,
169    ) -> Option<(Pane, Split)> {
170        let node = if let Some(pane) = pane {
171            self.internal.layout.find(pane)?
172        } else {
173            // Major node
174            &mut self.internal.layout
175        };
176
177        let new_pane = {
178            self.internal.last_id = self.internal.last_id.checked_add(1)?;
179
180            Pane(self.internal.last_id)
181        };
182
183        let new_split = {
184            self.internal.last_id = self.internal.last_id.checked_add(1)?;
185
186            Split(self.internal.last_id)
187        };
188
189        if inverse {
190            node.split_inverse(new_split, axis, new_pane);
191        } else {
192            node.split(new_split, axis, new_pane);
193        }
194
195        let _ = self.panes.insert(new_pane, state);
196        let _ = self.internal.maximized.take();
197
198        Some((new_pane, new_split))
199    }
200
201    fn split_and_swap(&mut self, axis: Axis, target: Pane, pane: Pane, swap: bool) {
202        if let Some((state, _)) = self.close(pane)
203            && let Some((new_pane, _)) = self.split(axis, target, state)
204        {
205            // Ensure new node corresponds to original closed `Pane` for state continuity
206            self.relabel(new_pane, pane);
207
208            if swap {
209                self.swap(target, pane);
210            }
211        }
212    }
213
214    /// Move [`Pane`] to an [`Edge`] of the [`PaneGrid`].
215    ///
216    /// [`PaneGrid`]: super::PaneGrid
217    pub fn move_to_edge(&mut self, pane: Pane, edge: Edge) {
218        match edge {
219            Edge::Top => {
220                self.split_major_node_and_swap(Axis::Horizontal, pane, true);
221            }
222            Edge::Bottom => {
223                self.split_major_node_and_swap(Axis::Horizontal, pane, false);
224            }
225            Edge::Left => {
226                self.split_major_node_and_swap(Axis::Vertical, pane, true);
227            }
228            Edge::Right => {
229                self.split_major_node_and_swap(Axis::Vertical, pane, false);
230            }
231        }
232    }
233
234    fn split_major_node_and_swap(&mut self, axis: Axis, pane: Pane, inverse: bool) {
235        if let Some((state, _)) = self.close(pane)
236            && let Some((new_pane, _)) = self.split_node(axis, None, state, inverse)
237        {
238            // Ensure new node corresponds to original closed `Pane` for state continuity
239            self.relabel(new_pane, pane);
240        }
241    }
242
243    fn relabel(&mut self, target: Pane, label: Pane) {
244        self.swap(target, label);
245
246        let _ = self
247            .panes
248            .remove(&target)
249            .and_then(|state| self.panes.insert(label, state));
250    }
251
252    /// Swaps the position of the provided panes in the [`State`].
253    ///
254    /// If you want to swap panes on drag and drop in your [`PaneGrid`], you
255    /// will need to call this method when handling a [`DragEvent`].
256    ///
257    /// [`PaneGrid`]: super::PaneGrid
258    /// [`DragEvent`]: super::DragEvent
259    pub fn swap(&mut self, a: Pane, b: Pane) {
260        self.internal.layout.update(&|node| match node {
261            Node::Split { .. } => {}
262            Node::Pane(pane) => {
263                if *pane == a {
264                    *node = Node::Pane(b);
265                } else if *pane == b {
266                    *node = Node::Pane(a);
267                }
268            }
269        });
270    }
271
272    /// Resizes two panes by setting the position of the provided [`Split`].
273    ///
274    /// The ratio is a value in [0, 1], representing the exact position of a
275    /// [`Split`] between two panes.
276    ///
277    /// If you want to enable resize interactions in your [`PaneGrid`], you will
278    /// need to call this method when handling a [`ResizeEvent`].
279    ///
280    /// [`PaneGrid`]: super::PaneGrid
281    /// [`ResizeEvent`]: super::ResizeEvent
282    pub fn resize(&mut self, split: Split, ratio: f32) {
283        let _ = self.internal.layout.resize(split, ratio);
284    }
285
286    /// Closes the given [`Pane`] and returns its internal state and its closest
287    /// sibling, if it exists.
288    pub fn close(&mut self, pane: Pane) -> Option<(T, Pane)> {
289        if self.internal.maximized == Some(pane) {
290            let _ = self.internal.maximized.take();
291        }
292
293        if let Some(sibling) = self.internal.layout.remove(pane) {
294            self.panes.remove(&pane).map(|state| (state, sibling))
295        } else {
296            None
297        }
298    }
299
300    /// Maximize the given [`Pane`]. Only this pane will be rendered by the
301    /// [`PaneGrid`] until [`Self::restore()`] is called.
302    ///
303    /// [`PaneGrid`]: super::PaneGrid
304    pub fn maximize(&mut self, pane: Pane) {
305        self.internal.maximized = Some(pane);
306    }
307
308    /// Restore the currently maximized [`Pane`] to it's normal size. All panes
309    /// will be rendered by the [`PaneGrid`].
310    ///
311    /// [`PaneGrid`]: super::PaneGrid
312    pub fn restore(&mut self) {
313        let _ = self.internal.maximized.take();
314    }
315
316    /// Returns the maximized [`Pane`] of the [`PaneGrid`].
317    ///
318    /// [`PaneGrid`]: super::PaneGrid
319    pub fn maximized(&self) -> Option<Pane> {
320        self.internal.maximized
321    }
322}
323
324/// The internal state of a [`PaneGrid`].
325///
326/// [`PaneGrid`]: super::PaneGrid
327#[derive(Debug, Clone)]
328pub struct Internal {
329    layout: Node,
330    last_id: usize,
331    maximized: Option<Pane>,
332}
333
334impl Internal {
335    /// Initializes the [`Internal`] state of a [`PaneGrid`] from a
336    /// [`Configuration`].
337    ///
338    /// [`PaneGrid`]: super::PaneGrid
339    pub fn from_configuration<T>(
340        panes: &mut BTreeMap<Pane, T>,
341        content: Configuration<T>,
342        next_id: usize,
343    ) -> Self {
344        let (layout, last_id) = match content {
345            Configuration::Split { axis, ratio, a, b } => {
346                let Internal {
347                    layout: a,
348                    last_id: next_id,
349                    ..
350                } = Self::from_configuration(panes, *a, next_id);
351
352                let Internal {
353                    layout: b,
354                    last_id: next_id,
355                    ..
356                } = Self::from_configuration(panes, *b, next_id);
357
358                (
359                    Node::Split {
360                        id: Split(next_id),
361                        axis,
362                        ratio,
363                        a: Box::new(a),
364                        b: Box::new(b),
365                    },
366                    next_id + 1,
367                )
368            }
369            Configuration::Pane(state) => {
370                let id = Pane(next_id);
371                let _ = panes.insert(id, state);
372
373                (Node::Pane(id), next_id + 1)
374            }
375        };
376
377        Self {
378            layout,
379            last_id,
380            maximized: None,
381        }
382    }
383
384    pub(super) fn layout(&self) -> Cow<'_, Node> {
385        match self.maximized {
386            Some(pane) => Cow::Owned(Node::Pane(pane)),
387            None => Cow::Borrowed(&self.layout),
388        }
389    }
390
391    pub(super) fn maximized(&self) -> Option<Pane> {
392        self.maximized
393    }
394}
395
396/// The current action of a [`PaneGrid`].
397///
398/// [`PaneGrid`]: super::PaneGrid
399#[derive(Debug, Clone, Copy, PartialEq, Default)]
400pub enum Action {
401    /// The [`PaneGrid`] is idle.
402    ///
403    /// [`PaneGrid`]: super::PaneGrid
404    #[default]
405    Idle,
406    /// A [`Pane`] in the [`PaneGrid`] is being dragged.
407    ///
408    /// [`PaneGrid`]: super::PaneGrid
409    Dragging {
410        /// The [`Pane`] being dragged.
411        pane: Pane,
412        /// The starting [`Point`] of the drag interaction.
413        origin: Point,
414    },
415    /// A [`Split`] in the [`PaneGrid`] is being dragged.
416    ///
417    /// [`PaneGrid`]: super::PaneGrid
418    Resizing {
419        /// The [`Split`] being dragged.
420        split: Split,
421        /// The [`Axis`] of the [`Split`].
422        axis: Axis,
423    },
424}
425
426impl Action {
427    /// Returns the current [`Pane`] that is being dragged, if any.
428    pub fn picked_pane(&self) -> Option<(Pane, Point)> {
429        match *self {
430            Action::Dragging { pane, origin, .. } => Some((pane, origin)),
431            _ => None,
432        }
433    }
434
435    /// Returns the current [`Split`] that is being dragged, if any.
436    pub fn picked_split(&self) -> Option<(Split, Axis)> {
437        match *self {
438            Action::Resizing { split, axis, .. } => Some((split, axis)),
439            _ => None,
440        }
441    }
442}