iced_native/widget/
pane_grid.rs

1//! Let your users split regions of your application and organize layout dynamically.
2//!
3//! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish)
4//!
5//! # Example
6//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
7//! drag and drop, and hotkey support.
8//!
9//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.9/examples/pane_grid
10mod axis;
11mod configuration;
12mod content;
13mod direction;
14mod draggable;
15mod node;
16mod pane;
17mod split;
18mod title_bar;
19
20pub mod state;
21
22pub use axis::Axis;
23pub use configuration::Configuration;
24pub use content::Content;
25pub use direction::Direction;
26pub use draggable::Draggable;
27pub use node::Node;
28pub use pane::Pane;
29pub use split::Split;
30pub use state::State;
31pub use title_bar::TitleBar;
32
33pub use iced_style::pane_grid::{Line, StyleSheet};
34
35use crate::event::{self, Event};
36use crate::layout;
37use crate::mouse;
38use crate::overlay::{self, Group};
39use crate::renderer;
40use crate::touch;
41use crate::widget;
42use crate::widget::container;
43use crate::widget::tree::{self, Tree};
44use crate::{
45    Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell,
46    Size, Vector, Widget,
47};
48
49/// A collection of panes distributed using either vertical or horizontal splits
50/// to completely fill the space available.
51///
52/// [![Pane grid - Iced](https://thumbs.gfycat.com/FrailFreshAiredaleterrier-small.gif)](https://gfycat.com/frailfreshairedaleterrier)
53///
54/// This distribution of space is common in tiling window managers (like
55/// [`awesome`](https://awesomewm.org/), [`i3`](https://i3wm.org/), or even
56/// [`tmux`](https://github.com/tmux/tmux)).
57///
58/// A [`PaneGrid`] supports:
59///
60/// * Vertical and horizontal splits
61/// * Tracking of the last active pane
62/// * Mouse-based resizing
63/// * Drag and drop to reorganize panes
64/// * Hotkey support
65/// * Configurable modifier keys
66/// * [`State`] API to perform actions programmatically (`split`, `swap`, `resize`, etc.)
67///
68/// ## Example
69///
70/// ```
71/// # use iced_native::widget::{pane_grid, text};
72/// #
73/// # type PaneGrid<'a, Message> =
74/// #     iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>;
75/// #
76/// enum PaneState {
77///     SomePane,
78///     AnotherKindOfPane,
79/// }
80///
81/// enum Message {
82///     PaneDragged(pane_grid::DragEvent),
83///     PaneResized(pane_grid::ResizeEvent),
84/// }
85///
86/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
87///
88/// let pane_grid =
89///     PaneGrid::new(&state, |pane, state, is_maximized| {
90///         pane_grid::Content::new(match state {
91///             PaneState::SomePane => text("This is some pane"),
92///             PaneState::AnotherKindOfPane => text("This is another kind of pane"),
93///         })
94///     })
95///     .on_drag(Message::PaneDragged)
96///     .on_resize(10, Message::PaneResized);
97/// ```
98#[allow(missing_debug_implementations)]
99pub struct PaneGrid<'a, Message, Renderer>
100where
101    Renderer: crate::Renderer,
102    Renderer::Theme: StyleSheet + container::StyleSheet,
103{
104    contents: Contents<'a, Content<'a, Message, Renderer>>,
105    width: Length,
106    height: Length,
107    spacing: f32,
108    on_click: Option<Box<dyn Fn(Pane) -> Message + 'a>>,
109    on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
110    on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
111    style: <Renderer::Theme as StyleSheet>::Style,
112}
113
114impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
115where
116    Renderer: crate::Renderer,
117    Renderer::Theme: StyleSheet + container::StyleSheet,
118{
119    /// Creates a [`PaneGrid`] with the given [`State`] and view function.
120    ///
121    /// The view function will be called to display each [`Pane`] present in the
122    /// [`State`]. [`bool`] is set if the pane is maximized.
123    pub fn new<T>(
124        state: &'a State<T>,
125        view: impl Fn(Pane, &'a T, bool) -> Content<'a, Message, Renderer>,
126    ) -> Self {
127        let contents = if let Some((pane, pane_state)) =
128            state.maximized.and_then(|pane| {
129                state.panes.get(&pane).map(|pane_state| (pane, pane_state))
130            }) {
131            Contents::Maximized(
132                pane,
133                view(pane, pane_state, true),
134                Node::Pane(pane),
135            )
136        } else {
137            Contents::All(
138                state
139                    .panes
140                    .iter()
141                    .map(|(pane, pane_state)| {
142                        (*pane, view(*pane, pane_state, false))
143                    })
144                    .collect(),
145                &state.internal,
146            )
147        };
148
149        Self {
150            contents,
151            width: Length::Fill,
152            height: Length::Fill,
153            spacing: 0.0,
154            on_click: None,
155            on_drag: None,
156            on_resize: None,
157            style: Default::default(),
158        }
159    }
160
161    /// Sets the width of the [`PaneGrid`].
162    pub fn width(mut self, width: impl Into<Length>) -> Self {
163        self.width = width.into();
164        self
165    }
166
167    /// Sets the height of the [`PaneGrid`].
168    pub fn height(mut self, height: impl Into<Length>) -> Self {
169        self.height = height.into();
170        self
171    }
172
173    /// Sets the spacing _between_ the panes of the [`PaneGrid`].
174    pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
175        self.spacing = amount.into().0;
176        self
177    }
178
179    /// Sets the message that will be produced when a [`Pane`] of the
180    /// [`PaneGrid`] is clicked.
181    pub fn on_click<F>(mut self, f: F) -> Self
182    where
183        F: 'a + Fn(Pane) -> Message,
184    {
185        self.on_click = Some(Box::new(f));
186        self
187    }
188
189    /// Enables the drag and drop interactions of the [`PaneGrid`], which will
190    /// use the provided function to produce messages.
191    pub fn on_drag<F>(mut self, f: F) -> Self
192    where
193        F: 'a + Fn(DragEvent) -> Message,
194    {
195        self.on_drag = Some(Box::new(f));
196        self
197    }
198
199    /// Enables the resize interactions of the [`PaneGrid`], which will
200    /// use the provided function to produce messages.
201    ///
202    /// The `leeway` describes the amount of space around a split that can be
203    /// used to grab it.
204    ///
205    /// The grabbable area of a split will have a length of `spacing + leeway`,
206    /// properly centered. In other words, a length of
207    /// `(spacing + leeway) / 2.0` on either side of the split line.
208    pub fn on_resize<F>(mut self, leeway: impl Into<Pixels>, f: F) -> Self
209    where
210        F: 'a + Fn(ResizeEvent) -> Message,
211    {
212        self.on_resize = Some((leeway.into().0, Box::new(f)));
213        self
214    }
215
216    /// Sets the style of the [`PaneGrid`].
217    pub fn style(
218        mut self,
219        style: impl Into<<Renderer::Theme as StyleSheet>::Style>,
220    ) -> Self {
221        self.style = style.into();
222        self
223    }
224
225    fn drag_enabled(&self) -> bool {
226        (!self.contents.is_maximized())
227            .then(|| self.on_drag.is_some())
228            .unwrap_or_default()
229    }
230}
231
232impl<'a, Message, Renderer> Widget<Message, Renderer>
233    for PaneGrid<'a, Message, Renderer>
234where
235    Renderer: crate::Renderer,
236    Renderer::Theme: StyleSheet + container::StyleSheet,
237{
238    fn tag(&self) -> tree::Tag {
239        tree::Tag::of::<state::Action>()
240    }
241
242    fn state(&self) -> tree::State {
243        tree::State::new(state::Action::Idle)
244    }
245
246    fn children(&self) -> Vec<Tree> {
247        self.contents
248            .iter()
249            .map(|(_, content)| content.state())
250            .collect()
251    }
252
253    fn diff(&self, tree: &mut Tree) {
254        match &self.contents {
255            Contents::All(contents, _) => tree.diff_children_custom(
256                contents,
257                |state, (_, content)| content.diff(state),
258                |(_, content)| content.state(),
259            ),
260            Contents::Maximized(_, content, _) => tree.diff_children_custom(
261                &[content],
262                |state, content| content.diff(state),
263                |content| content.state(),
264            ),
265        }
266    }
267
268    fn width(&self) -> Length {
269        self.width
270    }
271
272    fn height(&self) -> Length {
273        self.height
274    }
275
276    fn layout(
277        &self,
278        renderer: &Renderer,
279        limits: &layout::Limits,
280    ) -> layout::Node {
281        layout(
282            renderer,
283            limits,
284            self.contents.layout(),
285            self.width,
286            self.height,
287            self.spacing,
288            self.contents.iter(),
289            |content, renderer, limits| content.layout(renderer, limits),
290        )
291    }
292
293    fn operate(
294        &self,
295        tree: &mut Tree,
296        layout: Layout<'_>,
297        renderer: &Renderer,
298        operation: &mut dyn widget::Operation<Message>,
299    ) {
300        operation.container(None, &mut |operation| {
301            self.contents
302                .iter()
303                .zip(&mut tree.children)
304                .zip(layout.children())
305                .for_each(|(((_pane, content), state), layout)| {
306                    content.operate(state, layout, renderer, operation);
307                })
308        });
309    }
310
311    fn on_event(
312        &mut self,
313        tree: &mut Tree,
314        event: Event,
315        layout: Layout<'_>,
316        cursor_position: Point,
317        renderer: &Renderer,
318        clipboard: &mut dyn Clipboard,
319        shell: &mut Shell<'_, Message>,
320    ) -> event::Status {
321        let action = tree.state.downcast_mut::<state::Action>();
322
323        let on_drag = if self.drag_enabled() {
324            &self.on_drag
325        } else {
326            &None
327        };
328
329        let event_status = update(
330            action,
331            self.contents.layout(),
332            &event,
333            layout,
334            cursor_position,
335            shell,
336            self.spacing,
337            self.contents.iter(),
338            &self.on_click,
339            on_drag,
340            &self.on_resize,
341        );
342
343        let picked_pane = action.picked_pane().map(|(pane, _)| pane);
344
345        self.contents
346            .iter_mut()
347            .zip(&mut tree.children)
348            .zip(layout.children())
349            .map(|(((pane, content), tree), layout)| {
350                let is_picked = picked_pane == Some(pane);
351
352                content.on_event(
353                    tree,
354                    event.clone(),
355                    layout,
356                    cursor_position,
357                    renderer,
358                    clipboard,
359                    shell,
360                    is_picked,
361                )
362            })
363            .fold(event_status, event::Status::merge)
364    }
365
366    fn mouse_interaction(
367        &self,
368        tree: &Tree,
369        layout: Layout<'_>,
370        cursor_position: Point,
371        viewport: &Rectangle,
372        renderer: &Renderer,
373    ) -> mouse::Interaction {
374        mouse_interaction(
375            tree.state.downcast_ref(),
376            self.contents.layout(),
377            layout,
378            cursor_position,
379            self.spacing,
380            self.on_resize.as_ref().map(|(leeway, _)| *leeway),
381        )
382        .unwrap_or_else(|| {
383            self.contents
384                .iter()
385                .zip(&tree.children)
386                .zip(layout.children())
387                .map(|(((_pane, content), tree), layout)| {
388                    content.mouse_interaction(
389                        tree,
390                        layout,
391                        cursor_position,
392                        viewport,
393                        renderer,
394                        self.drag_enabled(),
395                    )
396                })
397                .max()
398                .unwrap_or_default()
399        })
400    }
401
402    fn draw(
403        &self,
404        tree: &Tree,
405        renderer: &mut Renderer,
406        theme: &Renderer::Theme,
407        style: &renderer::Style,
408        layout: Layout<'_>,
409        cursor_position: Point,
410        viewport: &Rectangle,
411    ) {
412        draw(
413            tree.state.downcast_ref(),
414            self.contents.layout(),
415            layout,
416            cursor_position,
417            renderer,
418            theme,
419            style,
420            viewport,
421            self.spacing,
422            self.on_resize.as_ref().map(|(leeway, _)| *leeway),
423            &self.style,
424            self.contents
425                .iter()
426                .zip(&tree.children)
427                .map(|((pane, content), tree)| (pane, (content, tree))),
428            |(content, tree),
429             renderer,
430             style,
431             layout,
432             cursor_position,
433             rectangle| {
434                content.draw(
435                    tree,
436                    renderer,
437                    theme,
438                    style,
439                    layout,
440                    cursor_position,
441                    rectangle,
442                );
443            },
444        )
445    }
446
447    fn overlay<'b>(
448        &'b mut self,
449        tree: &'b mut Tree,
450        layout: Layout<'_>,
451        renderer: &Renderer,
452    ) -> Option<overlay::Element<'_, Message, Renderer>> {
453        let children = self
454            .contents
455            .iter_mut()
456            .zip(&mut tree.children)
457            .zip(layout.children())
458            .filter_map(|(((_, content), state), layout)| {
459                content.overlay(state, layout, renderer)
460            })
461            .collect::<Vec<_>>();
462
463        (!children.is_empty()).then(|| Group::with_children(children).overlay())
464    }
465}
466
467impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
468    for Element<'a, Message, Renderer>
469where
470    Message: 'a,
471    Renderer: 'a + crate::Renderer,
472    Renderer::Theme: StyleSheet + container::StyleSheet,
473{
474    fn from(
475        pane_grid: PaneGrid<'a, Message, Renderer>,
476    ) -> Element<'a, Message, Renderer> {
477        Element::new(pane_grid)
478    }
479}
480
481/// Calculates the [`Layout`] of a [`PaneGrid`].
482pub fn layout<Renderer, T>(
483    renderer: &Renderer,
484    limits: &layout::Limits,
485    node: &Node,
486    width: Length,
487    height: Length,
488    spacing: f32,
489    contents: impl Iterator<Item = (Pane, T)>,
490    layout_content: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node,
491) -> layout::Node {
492    let limits = limits.width(width).height(height);
493    let size = limits.resolve(Size::ZERO);
494
495    let regions = node.pane_regions(spacing, size);
496    let children = contents
497        .filter_map(|(pane, content)| {
498            let region = regions.get(&pane)?;
499            let size = Size::new(region.width, region.height);
500
501            let mut node = layout_content(
502                content,
503                renderer,
504                &layout::Limits::new(size, size),
505            );
506
507            node.move_to(Point::new(region.x, region.y));
508
509            Some(node)
510        })
511        .collect();
512
513    layout::Node::with_children(size, children)
514}
515
516/// Processes an [`Event`] and updates the [`state`] of a [`PaneGrid`]
517/// accordingly.
518pub fn update<'a, Message, T: Draggable>(
519    action: &mut state::Action,
520    node: &Node,
521    event: &Event,
522    layout: Layout<'_>,
523    cursor_position: Point,
524    shell: &mut Shell<'_, Message>,
525    spacing: f32,
526    contents: impl Iterator<Item = (Pane, T)>,
527    on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>,
528    on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
529    on_resize: &Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
530) -> event::Status {
531    let mut event_status = event::Status::Ignored;
532
533    match event {
534        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
535        | Event::Touch(touch::Event::FingerPressed { .. }) => {
536            let bounds = layout.bounds();
537
538            if bounds.contains(cursor_position) {
539                event_status = event::Status::Captured;
540
541                match on_resize {
542                    Some((leeway, _)) => {
543                        let relative_cursor = Point::new(
544                            cursor_position.x - bounds.x,
545                            cursor_position.y - bounds.y,
546                        );
547
548                        let splits = node.split_regions(
549                            spacing,
550                            Size::new(bounds.width, bounds.height),
551                        );
552
553                        let clicked_split = hovered_split(
554                            splits.iter(),
555                            spacing + leeway,
556                            relative_cursor,
557                        );
558
559                        if let Some((split, axis, _)) = clicked_split {
560                            if action.picked_pane().is_none() {
561                                *action =
562                                    state::Action::Resizing { split, axis };
563                            }
564                        } else {
565                            click_pane(
566                                action,
567                                layout,
568                                cursor_position,
569                                shell,
570                                contents,
571                                on_click,
572                                on_drag,
573                            );
574                        }
575                    }
576                    None => {
577                        click_pane(
578                            action,
579                            layout,
580                            cursor_position,
581                            shell,
582                            contents,
583                            on_click,
584                            on_drag,
585                        );
586                    }
587                }
588            }
589        }
590        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
591        | Event::Touch(touch::Event::FingerLifted { .. })
592        | Event::Touch(touch::Event::FingerLost { .. }) => {
593            if let Some((pane, _)) = action.picked_pane() {
594                if let Some(on_drag) = on_drag {
595                    let mut dropped_region = contents
596                        .zip(layout.children())
597                        .filter(|(_, layout)| {
598                            layout.bounds().contains(cursor_position)
599                        });
600
601                    let event = match dropped_region.next() {
602                        Some(((target, _), _)) if pane != target => {
603                            DragEvent::Dropped { pane, target }
604                        }
605                        _ => DragEvent::Canceled { pane },
606                    };
607
608                    shell.publish(on_drag(event));
609                }
610
611                *action = state::Action::Idle;
612
613                event_status = event::Status::Captured;
614            } else if action.picked_split().is_some() {
615                *action = state::Action::Idle;
616
617                event_status = event::Status::Captured;
618            }
619        }
620        Event::Mouse(mouse::Event::CursorMoved { .. })
621        | Event::Touch(touch::Event::FingerMoved { .. }) => {
622            if let Some((_, on_resize)) = on_resize {
623                if let Some((split, _)) = action.picked_split() {
624                    let bounds = layout.bounds();
625
626                    let splits = node.split_regions(
627                        spacing,
628                        Size::new(bounds.width, bounds.height),
629                    );
630
631                    if let Some((axis, rectangle, _)) = splits.get(&split) {
632                        let ratio = match axis {
633                            Axis::Horizontal => {
634                                let position =
635                                    cursor_position.y - bounds.y - rectangle.y;
636
637                                (position / rectangle.height).clamp(0.1, 0.9)
638                            }
639                            Axis::Vertical => {
640                                let position =
641                                    cursor_position.x - bounds.x - rectangle.x;
642
643                                (position / rectangle.width).clamp(0.1, 0.9)
644                            }
645                        };
646
647                        shell.publish(on_resize(ResizeEvent { split, ratio }));
648
649                        event_status = event::Status::Captured;
650                    }
651                }
652            }
653        }
654        _ => {}
655    }
656
657    event_status
658}
659
660fn click_pane<'a, Message, T>(
661    action: &mut state::Action,
662    layout: Layout<'_>,
663    cursor_position: Point,
664    shell: &mut Shell<'_, Message>,
665    contents: impl Iterator<Item = (Pane, T)>,
666    on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>,
667    on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
668) where
669    T: Draggable,
670{
671    let mut clicked_region = contents
672        .zip(layout.children())
673        .filter(|(_, layout)| layout.bounds().contains(cursor_position));
674
675    if let Some(((pane, content), layout)) = clicked_region.next() {
676        if let Some(on_click) = &on_click {
677            shell.publish(on_click(pane));
678        }
679
680        if let Some(on_drag) = &on_drag {
681            if content.can_be_dragged_at(layout, cursor_position) {
682                let pane_position = layout.position();
683
684                let origin = cursor_position
685                    - Vector::new(pane_position.x, pane_position.y);
686
687                *action = state::Action::Dragging { pane, origin };
688
689                shell.publish(on_drag(DragEvent::Picked { pane }));
690            }
691        }
692    }
693}
694
695/// Returns the current [`mouse::Interaction`] of a [`PaneGrid`].
696pub fn mouse_interaction(
697    action: &state::Action,
698    node: &Node,
699    layout: Layout<'_>,
700    cursor_position: Point,
701    spacing: f32,
702    resize_leeway: Option<f32>,
703) -> Option<mouse::Interaction> {
704    if action.picked_pane().is_some() {
705        return Some(mouse::Interaction::Grabbing);
706    }
707
708    let resize_axis =
709        action.picked_split().map(|(_, axis)| axis).or_else(|| {
710            resize_leeway.and_then(|leeway| {
711                let bounds = layout.bounds();
712
713                let splits = node.split_regions(spacing, bounds.size());
714
715                let relative_cursor = Point::new(
716                    cursor_position.x - bounds.x,
717                    cursor_position.y - bounds.y,
718                );
719
720                hovered_split(splits.iter(), spacing + leeway, relative_cursor)
721                    .map(|(_, axis, _)| axis)
722            })
723        });
724
725    if let Some(resize_axis) = resize_axis {
726        return Some(match resize_axis {
727            Axis::Horizontal => mouse::Interaction::ResizingVertically,
728            Axis::Vertical => mouse::Interaction::ResizingHorizontally,
729        });
730    }
731
732    None
733}
734
735/// Draws a [`PaneGrid`].
736pub fn draw<Renderer, T>(
737    action: &state::Action,
738    node: &Node,
739    layout: Layout<'_>,
740    cursor_position: Point,
741    renderer: &mut Renderer,
742    theme: &Renderer::Theme,
743    default_style: &renderer::Style,
744    viewport: &Rectangle,
745    spacing: f32,
746    resize_leeway: Option<f32>,
747    style: &<Renderer::Theme as StyleSheet>::Style,
748    contents: impl Iterator<Item = (Pane, T)>,
749    draw_pane: impl Fn(
750        T,
751        &mut Renderer,
752        &renderer::Style,
753        Layout<'_>,
754        Point,
755        &Rectangle,
756    ),
757) where
758    Renderer: crate::Renderer,
759    Renderer::Theme: StyleSheet,
760{
761    let picked_pane = action.picked_pane();
762
763    let picked_split = action
764        .picked_split()
765        .and_then(|(split, axis)| {
766            let bounds = layout.bounds();
767
768            let splits = node.split_regions(spacing, bounds.size());
769
770            let (_axis, region, ratio) = splits.get(&split)?;
771
772            let region = axis.split_line_bounds(*region, *ratio, spacing);
773
774            Some((axis, region + Vector::new(bounds.x, bounds.y), true))
775        })
776        .or_else(|| match resize_leeway {
777            Some(leeway) => {
778                let bounds = layout.bounds();
779
780                let relative_cursor = Point::new(
781                    cursor_position.x - bounds.x,
782                    cursor_position.y - bounds.y,
783                );
784
785                let splits = node.split_regions(spacing, bounds.size());
786
787                let (_split, axis, region) = hovered_split(
788                    splits.iter(),
789                    spacing + leeway,
790                    relative_cursor,
791                )?;
792
793                Some((axis, region + Vector::new(bounds.x, bounds.y), false))
794            }
795            None => None,
796        });
797
798    let pane_cursor_position = if picked_pane.is_some() {
799        // TODO: Remove once cursor availability is encoded in the type
800        // system
801        Point::new(-1.0, -1.0)
802    } else {
803        cursor_position
804    };
805
806    let mut render_picked_pane = None;
807
808    for ((id, pane), layout) in contents.zip(layout.children()) {
809        match picked_pane {
810            Some((dragging, origin)) if id == dragging => {
811                render_picked_pane = Some((pane, origin, layout));
812            }
813            _ => {
814                draw_pane(
815                    pane,
816                    renderer,
817                    default_style,
818                    layout,
819                    pane_cursor_position,
820                    viewport,
821                );
822            }
823        }
824    }
825
826    // Render picked pane last
827    if let Some((pane, origin, layout)) = render_picked_pane {
828        let bounds = layout.bounds();
829
830        renderer.with_translation(
831            cursor_position
832                - Point::new(bounds.x + origin.x, bounds.y + origin.y),
833            |renderer| {
834                renderer.with_layer(bounds, |renderer| {
835                    draw_pane(
836                        pane,
837                        renderer,
838                        default_style,
839                        layout,
840                        pane_cursor_position,
841                        viewport,
842                    );
843                });
844            },
845        );
846    };
847
848    if let Some((axis, split_region, is_picked)) = picked_split {
849        let highlight = if is_picked {
850            theme.picked_split(style)
851        } else {
852            theme.hovered_split(style)
853        };
854
855        if let Some(highlight) = highlight {
856            renderer.fill_quad(
857                renderer::Quad {
858                    bounds: match axis {
859                        Axis::Horizontal => Rectangle {
860                            x: split_region.x,
861                            y: (split_region.y
862                                + (split_region.height - highlight.width)
863                                    / 2.0)
864                                .round(),
865                            width: split_region.width,
866                            height: highlight.width,
867                        },
868                        Axis::Vertical => Rectangle {
869                            x: (split_region.x
870                                + (split_region.width - highlight.width) / 2.0)
871                                .round(),
872                            y: split_region.y,
873                            width: highlight.width,
874                            height: split_region.height,
875                        },
876                    },
877                    border_radius: 0.0.into(),
878                    border_width: 0.0,
879                    border_color: Color::TRANSPARENT,
880                },
881                highlight.color,
882            );
883        }
884    }
885}
886
887/// An event produced during a drag and drop interaction of a [`PaneGrid`].
888#[derive(Debug, Clone, Copy)]
889pub enum DragEvent {
890    /// A [`Pane`] was picked for dragging.
891    Picked {
892        /// The picked [`Pane`].
893        pane: Pane,
894    },
895
896    /// A [`Pane`] was dropped on top of another [`Pane`].
897    Dropped {
898        /// The picked [`Pane`].
899        pane: Pane,
900
901        /// The [`Pane`] where the picked one was dropped on.
902        target: Pane,
903    },
904
905    /// A [`Pane`] was picked and then dropped outside of other [`Pane`]
906    /// boundaries.
907    Canceled {
908        /// The picked [`Pane`].
909        pane: Pane,
910    },
911}
912
913/// An event produced during a resize interaction of a [`PaneGrid`].
914#[derive(Debug, Clone, Copy)]
915pub struct ResizeEvent {
916    /// The [`Split`] that is being dragged for resizing.
917    pub split: Split,
918
919    /// The new ratio of the [`Split`].
920    ///
921    /// The ratio is a value in [0, 1], representing the exact position of a
922    /// [`Split`] between two panes.
923    pub ratio: f32,
924}
925
926/*
927 * Helpers
928 */
929fn hovered_split<'a>(
930    splits: impl Iterator<Item = (&'a Split, &'a (Axis, Rectangle, f32))>,
931    spacing: f32,
932    cursor_position: Point,
933) -> Option<(Split, Axis, Rectangle)> {
934    splits
935        .filter_map(|(split, (axis, region, ratio))| {
936            let bounds = axis.split_line_bounds(*region, *ratio, spacing);
937
938            if bounds.contains(cursor_position) {
939                Some((*split, *axis, bounds))
940            } else {
941                None
942            }
943        })
944        .next()
945}
946
947/// The visible contents of the [`PaneGrid`]
948#[derive(Debug)]
949pub enum Contents<'a, T> {
950    /// All panes are visible
951    All(Vec<(Pane, T)>, &'a state::Internal),
952    /// A maximized pane is visible
953    Maximized(Pane, T, Node),
954}
955
956impl<'a, T> Contents<'a, T> {
957    /// Returns the layout [`Node`] of the [`Contents`]
958    pub fn layout(&self) -> &Node {
959        match self {
960            Contents::All(_, state) => state.layout(),
961            Contents::Maximized(_, _, layout) => layout,
962        }
963    }
964
965    /// Returns an iterator over the values of the [`Contents`]
966    pub fn iter(&self) -> Box<dyn Iterator<Item = (Pane, &T)> + '_> {
967        match self {
968            Contents::All(contents, _) => Box::new(
969                contents.iter().map(|(pane, content)| (*pane, content)),
970            ),
971            Contents::Maximized(pane, content, _) => {
972                Box::new(std::iter::once((*pane, content)))
973            }
974        }
975    }
976
977    fn iter_mut(&mut self) -> Box<dyn Iterator<Item = (Pane, &mut T)> + '_> {
978        match self {
979            Contents::All(contents, _) => Box::new(
980                contents.iter_mut().map(|(pane, content)| (*pane, content)),
981            ),
982            Contents::Maximized(pane, content, _) => {
983                Box::new(std::iter::once((*pane, content)))
984            }
985        }
986    }
987
988    fn is_maximized(&self) -> bool {
989        matches!(self, Self::Maximized(..))
990    }
991}