1mod 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#[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 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 pub fn width(mut self, width: impl Into<Length>) -> Self {
163 self.width = width.into();
164 self
165 }
166
167 pub fn height(mut self, height: impl Into<Length>) -> Self {
169 self.height = height.into();
170 self
171 }
172
173 pub fn spacing(mut self, amount: impl Into<Pixels>) -> Self {
175 self.spacing = amount.into().0;
176 self
177 }
178
179 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 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 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 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
481pub 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
516pub 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
695pub 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
735pub 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 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 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#[derive(Debug, Clone, Copy)]
889pub enum DragEvent {
890 Picked {
892 pane: Pane,
894 },
895
896 Dropped {
898 pane: Pane,
900
901 target: Pane,
903 },
904
905 Canceled {
908 pane: Pane,
910 },
911}
912
913#[derive(Debug, Clone, Copy)]
915pub struct ResizeEvent {
916 pub split: Split,
918
919 pub ratio: f32,
924}
925
926fn 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#[derive(Debug)]
949pub enum Contents<'a, T> {
950 All(Vec<(Pane, T)>, &'a state::Internal),
952 Maximized(Pane, T, Node),
954}
955
956impl<'a, T> Contents<'a, T> {
957 pub fn layout(&self) -> &Node {
959 match self {
960 Contents::All(_, state) => state.layout(),
961 Contents::Maximized(_, _, layout) => layout,
962 }
963 }
964
965 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}