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