summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector0193@gmail.com>2023-07-06 08:40:05 +0200
committerLibravatar GitHub <noreply@github.com>2023-07-06 08:40:05 +0200
commitfb512c3b70fbec6cc63298626d714ef818560519 (patch)
treef216d551a975d05c836d686f3a71c27805b9ab38
parent7f805bc5dd9ed38c904d83f9ea931eed6f3234bf (diff)
parent0964f12db7002f535f8eeda1791ea8962be6e71a (diff)
downloadiced-fb512c3b70fbec6cc63298626d714ef818560519.tar.gz
iced-fb512c3b70fbec6cc63298626d714ef818560519.tar.bz2
iced-fb512c3b70fbec6cc63298626d714ef818560519.zip
Merge pull request #1865 from jhff/pane_grid_edge_with_dragged_pane
[Feature] Pane Grid: drag & drop panes to the edges
-rw-r--r--examples/pane_grid/src/main.rs3
-rw-r--r--style/src/pane_grid.rs2
-rw-r--r--widget/src/pane_grid.rs275
-rw-r--r--widget/src/pane_grid/node.rs10
-rw-r--r--widget/src/pane_grid/state.rs108
5 files changed, 285 insertions, 113 deletions
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
index 54c36d69..04896e20 100644
--- a/examples/pane_grid/src/main.rs
+++ b/examples/pane_grid/src/main.rs
@@ -108,9 +108,8 @@ impl Application for Example {
Message::Dragged(pane_grid::DragEvent::Dropped {
pane,
target,
- region,
}) => {
- self.panes.split_with(&target, &pane, region);
+ self.panes.drop(&pane, target);
}
Message::Dragged(_) => {}
Message::TogglePin(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..31bb0e86 100644
--- a/widget/src/pane_grid.rs
+++ b/widget/src/pane_grid.rs
@@ -581,39 +581,46 @@ 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::Edge(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(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 +678,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 +840,36 @@ pub fn draw<Renderer, T>(
let mut render_picked_pane = None;
- for ((id, pane), layout) in contents.zip(layout.children()) {
+ let pane_in_edge = if picked_pane.is_some() {
+ cursor
+ .position()
+ .and_then(|cursor_position| in_edge(layout, cursor_position))
+ } else {
+ None
+ };
+
+ 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() && pane_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 +890,7 @@ pub fn draw<Renderer, T>(
pane,
renderer,
default_style,
- layout,
+ pane_layout,
pane_cursor,
viewport,
);
@@ -883,6 +898,21 @@ pub fn draw<Renderer, T>(
}
}
+ if let Some(edge) = pane_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,
+ 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 +937,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 +1076,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 +1088,35 @@ pub enum DragEvent {
},
}
+/// The [`Target`] area a pane can be dropped on.
+#[derive(Debug, Clone, Copy)]
+pub enum Target {
+ /// An [`Edge`] of the full [`PaneGrid`].
+ Edge(Edge),
+ /// A single [`Pane`] of the [`PaneGrid`].
+ Pane(Pane, 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..6fd15890 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, Target,
};
use std::collections::HashMap;
@@ -145,7 +145,55 @@ impl<T> State<T> {
pane: &Pane,
state: T,
) -> Option<(Pane, Split)> {
- let node = self.internal.layout.find(pane)?;
+ self.split_node(axis, Some(pane), state, false)
+ }
+
+ /// Split a target [`Pane`] with a given [`Pane`] on a given [`Region`].
+ ///
+ /// Panes will be swapped by default for [`Region::Center`].
+ pub fn split_with(&mut self, target: &Pane, pane: &Pane, region: Region) {
+ match region {
+ Region::Center => self.swap(pane, target),
+ 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)
+ }
+ },
+ }
+ }
+
+ /// Drops the given [`Pane`] into the provided [`Target`].
+ pub fn drop(&mut self, pane: &Pane, target: Target) {
+ match target {
+ Target::Edge(edge) => self.move_to_edge(pane, edge),
+ Target::Pane(target, region) => {
+ self.split_with(&target, pane, region)
+ }
+ }
+ }
+
+ fn split_node(
+ &mut self,
+ axis: Axis,
+ pane: Option<&Pane>,
+ state: T,
+ inverse: bool,
+ ) -> Option<(Pane, Split)> {
+ let node = if let Some(pane) = pane {
+ self.internal.layout.find(pane)?
+ } else {
+ // Major node
+ &mut self.internal.layout
+ };
let new_pane = {
self.internal.last_id = self.internal.last_id.checked_add(1)?;
@@ -159,7 +207,11 @@ impl<T> State<T> {
Split(self.internal.last_id)
};
- node.split(new_split, axis, new_pane);
+ if inverse {
+ node.split_inverse(new_split, axis, new_pane);
+ } else {
+ node.split(new_split, axis, new_pane);
+ }
let _ = self.panes.insert(new_pane, state);
let _ = self.maximized.take();
@@ -167,27 +219,6 @@ impl<T> State<T> {
Some((new_pane, new_split))
}
- /// Split a target [`Pane`] with a given [`Pane`] on a given [`Region`].
- ///
- /// Panes will be swapped by default for [`Region::Center`].
- 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)
- }
- }
- }
-
fn split_and_swap(
&mut self,
axis: Axis,
@@ -204,6 +235,35 @@ 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_node(axis, None, state, swap);
+ }
+ }
+
/// Swaps the position of the provided panes in the [`State`].
///
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you