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