Add ability to drag pane to the pane grid edges & optional style for dragged pane

This commit is contained in:
Joao Freitas 2023-05-19 11:24:52 +01:00 committed by Héctor Ramón Jiménez
parent 7f805bc5dd
commit e5c9dd54b3
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
5 changed files with 293 additions and 106 deletions

View file

@ -108,10 +108,15 @@ impl Application for Example {
Message::Dragged(pane_grid::DragEvent::Dropped { Message::Dragged(pane_grid::DragEvent::Dropped {
pane, pane,
target, target,
region, }) => match target {
}) => { pane_grid::Target::PaneGrid(edge) => {
self.panes.split_with(&target, &pane, region); self.panes.move_to_edge(&pane, edge)
} }
pane_grid::Target::Pane {
pane: target,
region,
} => self.panes.split_with(&target, &pane, region),
},
Message::Dragged(_) => {} Message::Dragged(_) => {}
Message::TogglePin(pane) => { Message::TogglePin(pane) => {
if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&pane) if let Some(Pane { is_pinned, .. }) = self.panes.get_mut(&pane)

View file

@ -31,7 +31,7 @@ pub trait StyleSheet {
/// The supported style of the [`StyleSheet`]. /// The supported style of the [`StyleSheet`].
type Style: Default; 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; fn hovered_region(&self, style: &Self::Style) -> Appearance;
/// The [`Line`] to draw when a split is picked. /// The [`Line`] to draw when a split is picked.

View file

@ -581,39 +581,49 @@ pub fn update<'a, Message, T: Draggable>(
| Event::Touch(touch::Event::FingerLost { .. }) => { | Event::Touch(touch::Event::FingerLost { .. }) => {
if let Some((pane, _)) = action.picked_pane() { if let Some((pane, _)) = action.picked_pane() {
if let Some(on_drag) = on_drag { if let Some(on_drag) = on_drag {
let dropped_region = if let Some(cursor_position) = cursor.position() {
cursor.position().and_then(|cursor_position| { let event = if let Some(edge) =
contents in_edge(layout, cursor_position)
{
DragEvent::Dropped {
pane,
target: Target::PaneGrid(edge),
}
} else {
let dropped_region = contents
.zip(layout.children()) .zip(layout.children())
.filter_map(|(target, layout)| { .filter_map(|(target, layout)| {
layout_region(layout, cursor_position) layout_region(layout, cursor_position)
.map(|region| (target, region)) .map(|region| (target, region))
}) })
.next() .next();
});
let event = match dropped_region { match dropped_region {
Some(((target, _), region)) if pane != target => { Some(((target, _), region))
DragEvent::Dropped { if pane != target =>
pane, {
target, DragEvent::Dropped {
region, 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; event_status = event::Status::Captured;
} else if action.picked_split().is_some() { } else if action.picked_split().is_some() {
*action = state::Action::Idle;
event_status = event::Status::Captured; event_status = event::Status::Captured;
} }
*action = state::Action::Idle;
} }
Event::Mouse(mouse::Event::CursorMoved { .. }) Event::Mouse(mouse::Event::CursorMoved { .. })
| Event::Touch(touch::Event::FingerMoved { .. }) => { | 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) { 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) { } 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) { } 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) { } else if cursor_position.y > (bounds.y + 2.0 * bounds.height / 3.0) {
Region::Bottom Region::Edge(Edge::Bottom)
} else { } else {
Region::Center Region::Center
}; };
@ -833,28 +843,32 @@ pub fn draw<Renderer, T>(
let mut render_picked_pane = None; 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 { match picked_pane {
Some((dragging, origin)) if id == dragging => { 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 => { Some((dragging, _)) if id != dragging => {
draw_pane( draw_pane(
pane, pane,
renderer, renderer,
default_style, default_style,
layout, pane_layout,
pane_cursor, pane_cursor,
viewport, viewport,
); );
if picked_pane.is_some() { if picked_pane.is_some() && cursor_in_edge.is_none() {
if let Some(region) = if let Some(region) =
cursor.position().and_then(|cursor_position| { 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); let hovered_region_style = theme.hovered_region(style);
renderer.fill_quad( renderer.fill_quad(
@ -875,7 +889,7 @@ pub fn draw<Renderer, T>(
pane, pane,
renderer, renderer,
default_style, default_style,
layout, pane_layout,
pane_cursor, pane_cursor,
viewport, 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 // Render picked pane last
if let Some((pane, origin, layout)) = render_picked_pane { if let Some((pane, origin, layout)) = render_picked_pane {
if let Some(cursor_position) = cursor.position() { if let Some(cursor_position) = cursor.position() {
@ -907,67 +938,127 @@ pub fn draw<Renderer, T>(
} }
} }
if let Some((axis, split_region, is_picked)) = picked_split { if picked_pane.is_none() {
let highlight = if is_picked { if let Some((axis, split_region, is_picked)) = picked_split {
theme.picked_split(style) let highlight = if is_picked {
} else { theme.picked_split(style)
theme.hovered_split(style) } else {
}; theme.hovered_split(style)
};
if let Some(highlight) = highlight { if let Some(highlight) = highlight {
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds: match axis { bounds: match axis {
Axis::Horizontal => Rectangle { Axis::Horizontal => Rectangle {
x: split_region.x, x: split_region.x,
y: (split_region.y y: (split_region.y
+ (split_region.height - highlight.width) + (split_region.height - highlight.width)
/ 2.0) / 2.0)
.round(), .round(),
width: split_region.width, width: split_region.width,
height: highlight.width, height: highlight.width,
}, },
Axis::Vertical => Rectangle { Axis::Vertical => Rectangle {
x: (split_region.x x: (split_region.x
+ (split_region.width - highlight.width) / 2.0) + (split_region.width - highlight.width)
.round(), / 2.0)
y: split_region.y, .round(),
width: highlight.width, y: split_region.y,
height: split_region.height, 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(), highlight.color,
border_width: 0.0, );
border_color: Color::TRANSPARENT, }
},
highlight.color,
);
} }
} }
} }
const THICKNESS_RATIO: f32 = 25.0;
fn in_edge(layout: Layout<'_>, cursor: Point) -> Option<Edge> {
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);
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
},
Edge::Left => Rectangle {
width: thickness,
..bounds
},
Edge::Right => Rectangle {
x: bounds.x + bounds.width - thickness,
width: thickness,
..bounds
},
Edge::Bottom => Rectangle {
y: bounds.y + bounds.height - thickness,
height: thickness,
..bounds
},
}
}
fn layout_region_bounds(layout: Layout<'_>, region: Region) -> Rectangle { fn layout_region_bounds(layout: Layout<'_>, region: Region) -> Rectangle {
let bounds = layout.bounds(); let bounds = layout.bounds();
match region { match region {
Region::Center => bounds, Region::Center => bounds,
Region::Top => Rectangle { Region::Edge(edge) => match edge {
height: bounds.height / 2.0, Edge::Top => Rectangle {
..bounds height: bounds.height / 2.0,
}, ..bounds
Region::Left => Rectangle { },
width: bounds.width / 2.0, Edge::Left => Rectangle {
..bounds width: bounds.width / 2.0,
}, ..bounds
Region::Right => Rectangle { },
x: bounds.x + bounds.width / 2.0, Edge::Right => Rectangle {
width: bounds.width / 2.0, x: bounds.x + bounds.width / 2.0,
..bounds width: bounds.width / 2.0,
}, ..bounds
Region::Bottom => Rectangle { },
y: bounds.y + bounds.height / 2.0, Edge::Bottom => Rectangle {
height: bounds.height / 2.0, y: bounds.y + bounds.height / 2.0,
..bounds height: bounds.height / 2.0,
..bounds
},
}, },
} }
} }
@ -986,11 +1077,8 @@ pub enum DragEvent {
/// The picked [`Pane`]. /// The picked [`Pane`].
pane: Pane, pane: Pane,
/// The [`Pane`] where the picked one was dropped on. /// The [`Target`] where the picked [`Pane`] was dropped on.
target: Pane, target: Target,
/// The [`Region`] of the target [`Pane`] where the picked one was dropped on.
region: Region,
}, },
/// A [`Pane`] was picked and then dropped outside of other [`Pane`] /// 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`]. /// The region of a [`Pane`].
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub enum Region { pub enum Region {
/// Center region. /// Center region.
#[default] #[default]
Center, Center,
/// Top region. /// Edge region.
Edge(Edge),
}
/// The edges of an area.
#[derive(Debug, Clone, Copy)]
pub enum Edge {
/// Top edge.
Top, Top,
/// Left region. /// Left edge.
Left, Left,
/// Right region. /// Right edge.
Right, Right,
/// Bottom region. /// Bottom edge.
Bottom, Bottom,
} }

View file

@ -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)) { pub(crate) fn update(&mut self, f: &impl Fn(&mut Node)) {
if let Node::Split { a, b, .. } = self { if let Node::Split { a, b, .. } = self {
a.update(f); a.update(f);

View file

@ -3,7 +3,7 @@
//! [`PaneGrid`]: crate::widget::PaneGrid //! [`PaneGrid`]: crate::widget::PaneGrid
use crate::core::{Point, Size}; use crate::core::{Point, Size};
use crate::pane_grid::{ use crate::pane_grid::{
Axis, Configuration, Direction, Node, Pane, Region, Split, Axis, Configuration, Direction, Edge, Node, Pane, Region, Split,
}; };
use std::collections::HashMap; use std::collections::HashMap;
@ -173,18 +173,20 @@ impl<T> State<T> {
pub fn split_with(&mut self, target: &Pane, pane: &Pane, region: Region) { pub fn split_with(&mut self, target: &Pane, pane: &Pane, region: Region) {
match region { match region {
Region::Center => self.swap(pane, target), Region::Center => self.swap(pane, target),
Region::Top => { Region::Edge(edge) => match edge {
self.split_and_swap(Axis::Horizontal, target, pane, true) Edge::Top => {
} self.split_and_swap(Axis::Horizontal, target, pane, true)
Region::Bottom => { }
self.split_and_swap(Axis::Horizontal, target, pane, false) Edge::Bottom => {
} self.split_and_swap(Axis::Horizontal, target, pane, false)
Region::Left => { }
self.split_and_swap(Axis::Vertical, target, pane, true) Edge::Left => {
} self.split_and_swap(Axis::Vertical, target, pane, true)
Region::Right => { }
self.split_and_swap(Axis::Vertical, target, pane, false) 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`]. /// Swaps the position of the provided panes in the [`State`].
/// ///
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you /// If you want to swap panes on drag and drop in your [`PaneGrid`], you