iced_native/widget/pane_grid/state.rs
1//! The state of a [`PaneGrid`].
2//!
3//! [`PaneGrid`]: crate::widget::PaneGrid
4use crate::widget::pane_grid::{
5 Axis, Configuration, Direction, Node, Pane, Split,
6};
7use crate::{Point, Size};
8
9use std::collections::HashMap;
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`]: crate::widget::PaneGrid
22/// [`PaneGrid::new`]: crate::widget::PaneGrid::new
23#[derive(Debug, Clone)]
24pub struct State<T> {
25 /// The panes of the [`PaneGrid`].
26 ///
27 /// [`PaneGrid`]: crate::widget::PaneGrid
28 pub panes: HashMap<Pane, T>,
29
30 /// The internal state of the [`PaneGrid`].
31 ///
32 /// [`PaneGrid`]: crate::widget::PaneGrid
33 pub internal: Internal,
34
35 /// The maximized [`Pane`] of the [`PaneGrid`].
36 ///
37 /// [`PaneGrid`]: crate::widget::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 = HashMap::new();
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 let node = self.internal.layout.find(pane)?;
149
150 let new_pane = {
151 self.internal.last_id = self.internal.last_id.checked_add(1)?;
152
153 Pane(self.internal.last_id)
154 };
155
156 let new_split = {
157 self.internal.last_id = self.internal.last_id.checked_add(1)?;
158
159 Split(self.internal.last_id)
160 };
161
162 node.split(new_split, axis, new_pane);
163
164 let _ = self.panes.insert(new_pane, state);
165 let _ = self.maximized.take();
166
167 Some((new_pane, new_split))
168 }
169
170 /// Swaps the position of the provided panes in the [`State`].
171 ///
172 /// If you want to swap panes on drag and drop in your [`PaneGrid`], you
173 /// will need to call this method when handling a [`DragEvent`].
174 ///
175 /// [`PaneGrid`]: crate::widget::PaneGrid
176 /// [`DragEvent`]: crate::widget::pane_grid::DragEvent
177 pub fn swap(&mut self, a: &Pane, b: &Pane) {
178 self.internal.layout.update(&|node| match node {
179 Node::Split { .. } => {}
180 Node::Pane(pane) => {
181 if pane == a {
182 *node = Node::Pane(*b);
183 } else if pane == b {
184 *node = Node::Pane(*a);
185 }
186 }
187 });
188 }
189
190 /// Resizes two panes by setting the position of the provided [`Split`].
191 ///
192 /// The ratio is a value in [0, 1], representing the exact position of a
193 /// [`Split`] between two panes.
194 ///
195 /// If you want to enable resize interactions in your [`PaneGrid`], you will
196 /// need to call this method when handling a [`ResizeEvent`].
197 ///
198 /// [`PaneGrid`]: crate::widget::PaneGrid
199 /// [`ResizeEvent`]: crate::widget::pane_grid::ResizeEvent
200 pub fn resize(&mut self, split: &Split, ratio: f32) {
201 let _ = self.internal.layout.resize(split, ratio);
202 }
203
204 /// Closes the given [`Pane`] and returns its internal state and its closest
205 /// sibling, if it exists.
206 pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> {
207 if self.maximized == Some(*pane) {
208 let _ = self.maximized.take();
209 }
210
211 if let Some(sibling) = self.internal.layout.remove(pane) {
212 self.panes.remove(pane).map(|state| (state, sibling))
213 } else {
214 None
215 }
216 }
217
218 /// Maximize the given [`Pane`]. Only this pane will be rendered by the
219 /// [`PaneGrid`] until [`Self::restore()`] is called.
220 ///
221 /// [`PaneGrid`]: crate::widget::PaneGrid
222 pub fn maximize(&mut self, pane: &Pane) {
223 self.maximized = Some(*pane);
224 }
225
226 /// Restore the currently maximized [`Pane`] to it's normal size. All panes
227 /// will be rendered by the [`PaneGrid`].
228 ///
229 /// [`PaneGrid`]: crate::widget::PaneGrid
230 pub fn restore(&mut self) {
231 let _ = self.maximized.take();
232 }
233
234 /// Returns the maximized [`Pane`] of the [`PaneGrid`].
235 ///
236 /// [`PaneGrid`]: crate::widget::PaneGrid
237 pub fn maximized(&self) -> Option<Pane> {
238 self.maximized
239 }
240}
241
242/// The internal state of a [`PaneGrid`].
243///
244/// [`PaneGrid`]: crate::widget::PaneGrid
245#[derive(Debug, Clone)]
246pub struct Internal {
247 layout: Node,
248 last_id: usize,
249}
250
251impl Internal {
252 /// Initializes the [`Internal`] state of a [`PaneGrid`] from a
253 /// [`Configuration`].
254 ///
255 /// [`PaneGrid`]: crate::widget::PaneGrid
256 pub fn from_configuration<T>(
257 panes: &mut HashMap<Pane, T>,
258 content: Configuration<T>,
259 next_id: usize,
260 ) -> Self {
261 let (layout, last_id) = match content {
262 Configuration::Split { axis, ratio, a, b } => {
263 let Internal {
264 layout: a,
265 last_id: next_id,
266 ..
267 } = Self::from_configuration(panes, *a, next_id);
268
269 let Internal {
270 layout: b,
271 last_id: next_id,
272 ..
273 } = Self::from_configuration(panes, *b, next_id);
274
275 (
276 Node::Split {
277 id: Split(next_id),
278 axis,
279 ratio,
280 a: Box::new(a),
281 b: Box::new(b),
282 },
283 next_id + 1,
284 )
285 }
286 Configuration::Pane(state) => {
287 let id = Pane(next_id);
288 let _ = panes.insert(id, state);
289
290 (Node::Pane(id), next_id + 1)
291 }
292 };
293
294 Self { layout, last_id }
295 }
296}
297
298/// The current action of a [`PaneGrid`].
299///
300/// [`PaneGrid`]: crate::widget::PaneGrid
301#[derive(Debug, Clone, Copy, PartialEq)]
302pub enum Action {
303 /// The [`PaneGrid`] is idle.
304 ///
305 /// [`PaneGrid`]: crate::widget::PaneGrid
306 Idle,
307 /// A [`Pane`] in the [`PaneGrid`] is being dragged.
308 ///
309 /// [`PaneGrid`]: crate::widget::PaneGrid
310 Dragging {
311 /// The [`Pane`] being dragged.
312 pane: Pane,
313 /// The starting [`Point`] of the drag interaction.
314 origin: Point,
315 },
316 /// A [`Split`] in the [`PaneGrid`] is being dragged.
317 ///
318 /// [`PaneGrid`]: crate::widget::PaneGrid
319 Resizing {
320 /// The [`Split`] being dragged.
321 split: Split,
322 /// The [`Axis`] of the [`Split`].
323 axis: Axis,
324 },
325}
326
327impl Action {
328 /// Returns the current [`Pane`] that is being dragged, if any.
329 pub fn picked_pane(&self) -> Option<(Pane, Point)> {
330 match *self {
331 Action::Dragging { pane, origin, .. } => Some((pane, origin)),
332 _ => None,
333 }
334 }
335
336 /// Returns the current [`Split`] that is being dragged, if any.
337 pub fn picked_split(&self) -> Option<(Split, Axis)> {
338 match *self {
339 Action::Resizing { split, axis, .. } => Some((split, axis)),
340 _ => None,
341 }
342 }
343}
344
345impl Internal {
346 /// The layout [`Node`] of the [`Internal`] state
347 pub fn layout(&self) -> &Node {
348 &self.layout
349 }
350}