summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Joao Freitas <51237625+jhff@users.noreply.github.com>2023-05-19 11:24:52 +0100
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-07-06 07:55:49 +0200
commite5c9dd54b3f51e913f39b38e8907c321c8bfd040 (patch)
tree69c0dc7aa682bec3f00d91b16fecb4bb38bdc8db
parent7f805bc5dd9ed38c904d83f9ea931eed6f3234bf (diff)
downloadiced-e5c9dd54b3f51e913f39b38e8907c321c8bfd040.tar.gz
iced-e5c9dd54b3f51e913f39b38e8907c321c8bfd040.tar.bz2
iced-e5c9dd54b3f51e913f39b38e8907c321c8bfd040.zip
Add ability to drag pane to the pane grid edges & optional style for dragged pane
-rw-r--r--examples/pane_grid/src/main.rs13
-rw-r--r--style/src/pane_grid.rs2
-rw-r--r--widget/src/pane_grid.rs281
-rw-r--r--widget/src/pane_grid/node.rs10
-rw-r--r--widget/src/pane_grid/state.rs89
5 files changed, 291 insertions, 104 deletions
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index 54c36d69..c5652e2d 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -108,10 +108,15 @@ impl Application for Example {
Message::Dragged(pane_grid::DragEvent::Dropped {
pane,
target,
- region,
- }) => {
- self.panes.split_with(&target, &pane, region);
- }
+ }) => match target {
+ pane_grid::Target::PaneGrid(edge) => {
+ self.panes.move_to_edge(&pane, edge)
+ }
+ pane_grid::Target::Pane {
+ pane: target,
+ region,
+ } => self.panes.split_with(&target, &pane, region),
+ },
Message::Dragged(_) => {}
Message::TogglePin(pane) => {
if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&pane)
diff --git a/style/src/pane_grid.rs b/style/src/pane_grid.rs
index b99af955..dfdc9186 100644
--- a/style/src/pane_grid.rs
+++ b/style/src/pane_grid.rs
@@ -31,7 +31,7 @@ pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;
- /// The [`Region`] to draw when a pane is hovered.
+ /// The [`Appearance`] to draw when a pane is hovered.
fn hovered_region(&self, style: &Self::Style) -> Appearance;
/// The [`Line`] to draw when a split is picked.
diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs
index 040d6bb3..0a8500dc 100644
--- a/widget/src/pane_grid.rs
+++ b/widget/src/pane_grid.rs
@@ -581,39 +581,49 @@ pub fn update<'a, Message, T: Draggable>(
| Event::Touch(touch::Event::FingerLost { .. }) => {
if let Some((pane, _)) = action.picked_pane() {
if let Some(on_drag) = on_drag {
- let dropped_region =
- cursor.position().and_then(|cursor_position| {
- contents
+ if let Some(cursor_position) = cursor.position() {
+ let event = if let Some(edge) =
+ in_edge(layout, cursor_position)
+ {
+ DragEvent::Dropped {
+ pane,
+ target: Target::PaneGrid(edge),
+ }
+ } else {
+ let dropped_region = contents
.zip(layout.children())
.filter_map(|(target, layout)| {
layout_region(layout, cursor_position)
.map(|region| (target, region))
})
- .next()
- });
-
- let event = match dropped_region {
- Some(((target, _), region)) if pane != target => {
- DragEvent::Dropped {
- pane,
- target,
- region,
+ .next();
+
+ match dropped_region {
+ Some(((target, _), region))
+ if pane != target =>
+ {
+ DragEvent::Dropped {
+ pane,
+ target: Target::Pane {
+ pane: target,
+ region,
+ },
+ }
+ }
+ _ => DragEvent::Canceled { pane },
}
- }
- _ => DragEvent::Canceled { pane },
- };
+ };
- shell.publish(on_drag(event));
+ shell.publish(on_drag(event));
+ }
}
- *action = state::Action::Idle;
-
event_status = event::Status::Captured;
} else if action.picked_split().is_some() {
- *action = state::Action::Idle;
-
event_status = event::Status::Captured;
}
+
+ *action = state::Action::Idle;
}
Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => {
@@ -671,13 +681,13 @@ fn layout_region(layout: Layout<'_>, cursor_position: Point) -> Option<Region> {
}
let region = if cursor_position.x < (bounds.x + bounds.width / 3.0) {
- Region::Left
+ Region::Edge(Edge::Left)
} else if cursor_position.x > (bounds.x + 2.0 * bounds.width / 3.0) {
- Region::Right
+ Region::Edge(Edge::Right)
} else if cursor_position.y < (bounds.y + bounds.height / 3.0) {
- Region::Top
+ Region::Edge(Edge::Top)
} else if cursor_position.y > (bounds.y + 2.0 * bounds.height / 3.0) {
- Region::Bottom
+ Region::Edge(Edge::Bottom)
} else {
Region::Center
};
@@ -833,28 +843,32 @@ pub fn draw<Renderer, T>(
let mut render_picked_pane = None;
- for ((id, pane), layout) in contents.zip(layout.children()) {
+ let cursor_in_edge = cursor
+ .position()
+ .and_then(|cursor_position| in_edge(layout, cursor_position));
+
+ for ((id, pane), pane_layout) in contents.zip(layout.children()) {
match picked_pane {
Some((dragging, origin)) if id == dragging => {
- render_picked_pane = Some((pane, origin, layout));
+ render_picked_pane = Some((pane, origin, pane_layout));
}
Some((dragging, _)) if id != dragging => {
draw_pane(
pane,
renderer,
default_style,
- layout,
+ pane_layout,
pane_cursor,
viewport,
);
- if picked_pane.is_some() {
+ if picked_pane.is_some() && cursor_in_edge.is_none() {
if let Some(region) =
cursor.position().and_then(|cursor_position| {
- layout_region(layout, cursor_position)
+ layout_region(pane_layout, cursor_position)
})
{
- let bounds = layout_region_bounds(layout, region);
+ let bounds = layout_region_bounds(pane_layout, region);
let hovered_region_style = theme.hovered_region(style);
renderer.fill_quad(
@@ -875,7 +889,7 @@ pub fn draw<Renderer, T>(
pane,
renderer,
default_style,
- layout,
+ pane_layout,
pane_cursor,
viewport,
);
@@ -883,6 +897,23 @@ pub fn draw<Renderer, T>(
}
}
+ if picked_pane.is_some() {
+ if let Some(edge) = cursor_in_edge {
+ let hovered_region_style = theme.hovered_region(style);
+ let bounds = edge_bounds(layout, edge);
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ border_radius: hovered_region_style.border_radius.into(),
+ border_width: hovered_region_style.border_width,
+ border_color: hovered_region_style.border_color,
+ },
+ theme.hovered_region(style).background,
+ );
+ }
+ }
+
// Render picked pane last
if let Some((pane, origin, layout)) = render_picked_pane {
if let Some(cursor_position) = cursor.position() {
@@ -907,71 +938,131 @@ pub fn draw<Renderer, T>(
}
}
- if let Some((axis, split_region, is_picked)) = picked_split {
- let highlight = if is_picked {
- theme.picked_split(style)
- } else {
- theme.hovered_split(style)
- };
-
- if let Some(highlight) = highlight {
- renderer.fill_quad(
- renderer::Quad {
- bounds: match axis {
- Axis::Horizontal => Rectangle {
- x: split_region.x,
- y: (split_region.y
- + (split_region.height - highlight.width)
- / 2.0)
- .round(),
- width: split_region.width,
- height: highlight.width,
- },
- Axis::Vertical => Rectangle {
- x: (split_region.x
- + (split_region.width - highlight.width) / 2.0)
- .round(),
- y: split_region.y,
- width: highlight.width,
- height: split_region.height,
+ if picked_pane.is_none() {
+ if let Some((axis, split_region, is_picked)) = picked_split {
+ let highlight = if is_picked {
+ theme.picked_split(style)
+ } else {
+ theme.hovered_split(style)
+ };
+
+ if let Some(highlight) = highlight {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: match axis {
+ Axis::Horizontal => Rectangle {
+ x: split_region.x,
+ y: (split_region.y
+ + (split_region.height - highlight.width)
+ / 2.0)
+ .round(),
+ width: split_region.width,
+ height: highlight.width,
+ },
+ Axis::Vertical => Rectangle {
+ x: (split_region.x
+ + (split_region.width - highlight.width)
+ / 2.0)
+ .round(),
+ y: split_region.y,
+ width: highlight.width,
+ height: split_region.height,
+ },
},
+ border_radius: 0.0.into(),
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
},
- border_radius: 0.0.into(),
- border_width: 0.0,
- border_color: Color::TRANSPARENT,
- },
- highlight.color,
- );
+ highlight.color,
+ );
+ }
}
}
}
-fn layout_region_bounds(layout: Layout<'_>, region: Region) -> Rectangle {
+const THICKNESS_RATIO: f32 = 25.0;
+
+fn in_edge(layout: Layout<'_>, cursor: Point) -> Option<Edge> {
let bounds = layout.bounds();
- match region {
- Region::Center => bounds,
- Region::Top => Rectangle {
- height: bounds.height / 2.0,
+ let height_thickness = bounds.height / THICKNESS_RATIO;
+ let width_thickness = bounds.width / THICKNESS_RATIO;
+ let thickness = height_thickness.min(width_thickness);
+
+ if cursor.x > bounds.x && cursor.x < bounds.x + thickness {
+ Some(Edge::Left)
+ } else if cursor.x > bounds.x + bounds.width - thickness
+ && cursor.x < bounds.x + bounds.width
+ {
+ Some(Edge::Right)
+ } else if cursor.y > bounds.y && cursor.y < bounds.y + thickness {
+ Some(Edge::Top)
+ } else if cursor.y > bounds.y + bounds.height - thickness
+ && cursor.y < bounds.y + bounds.height
+ {
+ Some(Edge::Bottom)
+ } else {
+ None
+ }
+}
+
+fn edge_bounds(layout: Layout<'_>, edge: Edge) -> Rectangle {
+ let bounds = layout.bounds();
+
+ let height_thickness = bounds.height / THICKNESS_RATIO;
+ let width_thickness = bounds.width / THICKNESS_RATIO;
+ let thickness = height_thickness.min(width_thickness);
+
+ match edge {
+ Edge::Top => Rectangle {
+ height: thickness,
..bounds
},
- Region::Left => Rectangle {
- width: bounds.width / 2.0,
+ Edge::Left => Rectangle {
+ width: thickness,
..bounds
},
- Region::Right => Rectangle {
- x: bounds.x + bounds.width / 2.0,
- width: bounds.width / 2.0,
+ Edge::Right => Rectangle {
+ x: bounds.x + bounds.width - thickness,
+ width: thickness,
..bounds
},
- Region::Bottom => Rectangle {
- y: bounds.y + bounds.height / 2.0,
- height: bounds.height / 2.0,
+ Edge::Bottom => Rectangle {
+ y: bounds.y + bounds.height - thickness,
+ height: thickness,
..bounds
},
}
}
+fn layout_region_bounds(layout: Layout<'_>, region: Region) -> Rectangle {
+ let bounds = layout.bounds();
+
+ match region {
+ Region::Center => bounds,
+ Region::Edge(edge) => match edge {
+ Edge::Top => Rectangle {
+ height: bounds.height / 2.0,
+ ..bounds
+ },
+ Edge::Left => Rectangle {
+ width: bounds.width / 2.0,
+ ..bounds
+ },
+ Edge::Right => Rectangle {
+ x: bounds.x + bounds.width / 2.0,
+ width: bounds.width / 2.0,
+ ..bounds
+ },
+ Edge::Bottom => Rectangle {
+ y: bounds.y + bounds.height / 2.0,
+ height: bounds.height / 2.0,
+ ..bounds
+ },
+ },
+ }
+}
+
/// An event produced during a drag and drop interaction of a [`PaneGrid`].
#[derive(Debug, Clone, Copy)]
pub enum DragEvent {
@@ -986,11 +1077,8 @@ pub enum DragEvent {
/// The picked [`Pane`].
pane: Pane,
- /// The [`Pane`] where the picked one was dropped on.
- target: Pane,
-
- /// The [`Region`] of the target [`Pane`] where the picked one was dropped on.
- region: Region,
+ /// The [`Target`] where the picked [`Pane`] was dropped on.
+ target: Target,
},
/// A [`Pane`] was picked and then dropped outside of other [`Pane`]
@@ -1001,19 +1089,40 @@ pub enum DragEvent {
},
}
+/// The [`Target`] area a pane can be dropped on.
+#[derive(Debug, Clone, Copy)]
+pub enum Target {
+ /// The [`Edge`} of the full [`PaneGrid`].
+ PaneGrid(Edge),
+ /// A single [`Pane`] of the [`PaneGrid`].
+ Pane {
+ /// The targetted [`Pane`].
+ pane: Pane,
+ /// The targetted area of the [`Pane`].
+ region: Region,
+ },
+}
+
/// The region of a [`Pane`].
#[derive(Debug, Clone, Copy, Default)]
pub enum Region {
/// Center region.
#[default]
Center,
- /// Top region.
+ /// Edge region.
+ Edge(Edge),
+}
+
+/// The edges of an area.
+#[derive(Debug, Clone, Copy)]
+pub enum Edge {
+ /// Top edge.
Top,
- /// Left region.
+ /// Left edge.
Left,
- /// Right region.
+ /// Right edge.
Right,
- /// Bottom region.
+ /// Bottom edge.
Bottom,
}
diff --git a/widget/src/pane_grid/node.rs b/widget/src/pane_grid/node.rs
index 3976acd8..6de5920f 100644
--- a/widget/src/pane_grid/node.rs
+++ b/widget/src/pane_grid/node.rs
@@ -120,6 +120,16 @@ impl Node {
};
}
+ pub(crate) fn split_inverse(&mut self, id: Split, axis: Axis, pane: Pane) {
+ *self = Node::Split {
+ id,
+ axis,
+ ratio: 0.5,
+ a: Box::new(Node::Pane(pane)),
+ b: Box::new(self.clone()),
+ };
+ }
+
pub(crate) fn update(&mut self, f: &impl Fn(&mut Node)) {
if let Node::Split { a, b, .. } = self {
a.update(f);
diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs
index 1f034ca3..34781a90 100644
--- a/widget/src/pane_grid/state.rs
+++ b/widget/src/pane_grid/state.rs
@@ -3,7 +3,7 @@
//! [`PaneGrid`]: crate::widget::PaneGrid
use crate::core::{Point, Size};
use crate::pane_grid::{
- Axis, Configuration, Direction, Node, Pane, Region, Split,
+ Axis, Configuration, Direction, Edge, Node, Pane, Region, Split,
};
use std::collections::HashMap;
@@ -173,18 +173,20 @@ impl<T> State<T> {
pub fn split_with(&mut self, target: &Pane, pane: &Pane, region: Region) {
match region {
Region::Center => self.swap(pane, target),
- Region::Top => {
- self.split_and_swap(Axis::Horizontal, target, pane, true)
- }
- Region::Bottom => {
- self.split_and_swap(Axis::Horizontal, target, pane, false)
- }
- Region::Left => {
- self.split_and_swap(Axis::Vertical, target, pane, true)
- }
- Region::Right => {
- self.split_and_swap(Axis::Vertical, target, pane, false)
- }
+ Region::Edge(edge) => match edge {
+ Edge::Top => {
+ self.split_and_swap(Axis::Horizontal, target, pane, true)
+ }
+ Edge::Bottom => {
+ self.split_and_swap(Axis::Horizontal, target, pane, false)
+ }
+ Edge::Left => {
+ self.split_and_swap(Axis::Vertical, target, pane, true)
+ }
+ Edge::Right => {
+ self.split_and_swap(Axis::Vertical, target, pane, false)
+ }
+ },
}
}
@@ -204,6 +206,67 @@ impl<T> State<T> {
}
}
+ /// Move [`Pane`] to an [`Edge`] of the [`PaneGrid`].
+ pub fn move_to_edge(&mut self, pane: &Pane, edge: Edge) {
+ match edge {
+ Edge::Top => {
+ self.split_major_node_and_swap(Axis::Horizontal, pane, true)
+ }
+ Edge::Bottom => {
+ self.split_major_node_and_swap(Axis::Horizontal, pane, false)
+ }
+ Edge::Left => {
+ self.split_major_node_and_swap(Axis::Vertical, pane, true)
+ }
+ Edge::Right => {
+ self.split_major_node_and_swap(Axis::Vertical, pane, false)
+ }
+ }
+ }
+
+ fn split_major_node_and_swap(
+ &mut self,
+ axis: Axis,
+ pane: &Pane,
+ swap: bool,
+ ) {
+ if let Some((state, _)) = self.close(pane) {
+ let _ = self.split_major_node(axis, state, swap);
+ }
+ }
+
+ fn split_major_node(
+ &mut self,
+ axis: Axis,
+ state: T,
+ swap: bool,
+ ) -> Option<(Pane, Split)> {
+ let major_node = &mut self.internal.layout;
+
+ let new_pane = {
+ self.internal.last_id = self.internal.last_id.checked_add(1)?;
+
+ Pane(self.internal.last_id)
+ };
+
+ let new_split = {
+ self.internal.last_id = self.internal.last_id.checked_add(1)?;
+
+ Split(self.internal.last_id)
+ };
+
+ if swap {
+ major_node.split_inverse(new_split, axis, new_pane)
+ } else {
+ major_node.split(new_split, axis, new_pane)
+ };
+
+ let _ = self.panes.insert(new_pane, state);
+ let _ = self.maximized.take();
+
+ Some((new_pane, new_split))
+ }
+
/// Swaps the position of the provided panes in the [`State`].
///
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you