Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Enhance PaneGrid to split panes by drag & drop #1856

Merged
merged 4 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion examples/pane_grid/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,9 @@ impl Application for Example {
Message::Dragged(pane_grid::DragEvent::Dropped {
pane,
target,
region,
}) => {
self.panes.swap(&pane, &target);
self.panes.split_with(&target, &pane, region);
}
Message::Dragged(_) => {}
Message::TogglePin(pane) => {
Expand Down Expand Up @@ -255,6 +256,7 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
}
}

#[derive(Clone, Copy)]
struct Pane {
id: usize,
pub is_pinned: bool,
Expand Down
38 changes: 27 additions & 11 deletions style/src/pane_grid.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
//! Change the appearance of a pane grid.
use iced_core::Color;
use iced_core::{Background, Color};

/// A set of rules that dictate the style of a container.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;

/// The [`Line`] to draw when a split is picked.
fn picked_split(&self, style: &Self::Style) -> Option<Line>;

/// The [`Line`] to draw when a split is hovered.
fn hovered_split(&self, style: &Self::Style) -> Option<Line>;
/// The appearance of the hovered region of a pane grid.
#[derive(Debug, Clone, Copy)]
pub struct Appearance {
/// The [`Background`] of the hovered pane region.
pub background: Background,
/// The border width of the hovered pane region.
pub border_width: f32,
/// The border [`Color`] of the hovered pane region.
pub border_color: Color,
/// The border radius of the hovered pane region.
pub border_radius: f32,
}

/// A line.
Expand All @@ -24,3 +25,18 @@ pub struct Line {
/// The width of the [`Line`].
pub width: f32,
}

/// A set of rules that dictate the style of a container.
pub trait StyleSheet {
/// The supported style of the [`StyleSheet`].
type Style: Default;

/// The [`Region`] to draw when a pane is hovered.
fn hovered_region(&self, style: &Self::Style) -> Appearance;

/// The [`Line`] to draw when a split is picked.
fn picked_split(&self, style: &Self::Style) -> Option<Line>;

/// The [`Line`] to draw when a split is hovered.
fn hovered_split(&self, style: &Self::Style) -> Option<Line>;
}
19 changes: 19 additions & 0 deletions style/src/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,25 @@ pub enum PaneGrid {
impl pane_grid::StyleSheet for Theme {
type Style = PaneGrid;

fn hovered_region(&self, style: &Self::Style) -> pane_grid::Appearance {
match style {
PaneGrid::Default => {
let palette = self.extended_palette();

pane_grid::Appearance {
background: Background::Color(Color {
a: 0.5,
..palette.primary.base.color
}),
border_width: 2.0,
border_color: palette.primary.strong.color,
border_radius: 0.0,
}
}
PaneGrid::Custom(custom) => custom.hovered_region(self),
}
}

fn picked_split(&self, style: &Self::Style) -> Option<pane_grid::Line> {
match style {
PaneGrid::Default => {
Expand Down
112 changes: 107 additions & 5 deletions widget/src/pane_grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub use split::Split;
pub use state::State;
pub use title_bar::TitleBar;

pub use crate::style::pane_grid::{Line, StyleSheet};
pub use crate::style::pane_grid::{Appearance, Line, StyleSheet};

use crate::container;
use crate::core::event::{self, Event};
Expand Down Expand Up @@ -594,13 +594,18 @@ pub fn update<'a, Message, T: Draggable>(
if let Some(on_drag) = on_drag {
let mut dropped_region = contents
.zip(layout.children())
.filter(|(_, layout)| {
layout.bounds().contains(cursor_position)
.filter_map(|(target, layout)| {
layout_region(layout, cursor_position)
.map(|region| (target, region))
});

let event = match dropped_region.next() {
Some(((target, _), _)) if pane != target => {
DragEvent::Dropped { pane, target }
Some(((target, _), region)) if pane != target => {
DragEvent::Dropped {
pane,
target,
region,
}
}
_ => DragEvent::Canceled { pane },
};
Expand Down Expand Up @@ -657,6 +662,28 @@ pub fn update<'a, Message, T: Draggable>(
event_status
}

fn layout_region(layout: Layout<'_>, cursor_position: Point) -> Option<Region> {
let bounds = layout.bounds();

if !bounds.contains(cursor_position) {
return None;
}

let region = if cursor_position.x < (bounds.x + bounds.width / 3.0) {
Region::Left
} else if cursor_position.x > (bounds.x + 2.0 * bounds.width / 3.0) {
Region::Right
} else if cursor_position.y < (bounds.y + bounds.height / 3.0) {
Region::Top
} else if cursor_position.y > (bounds.y + 2.0 * bounds.height / 3.0) {
Region::Bottom
} else {
Region::Center
};

Some(region)
}

fn click_pane<'a, Message, T>(
action: &mut state::Action,
layout: Layout<'_>,
Expand Down Expand Up @@ -810,6 +837,36 @@ pub fn draw<Renderer, T>(
Some((dragging, origin)) if id == dragging => {
render_picked_pane = Some((pane, origin, layout));
}
Some((dragging, _)) if id != dragging => {
draw_pane(
pane,
renderer,
default_style,
layout,
pane_cursor_position,
viewport,
);

if picked_pane.is_some() {
if let Some(region) = layout_region(layout, cursor_position)
{
let bounds = layout_region_bounds(layout, region);
let hovered_region_style = theme.hovered_region(style);

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,
);
}
}
}
_ => {
draw_pane(
pane,
Expand Down Expand Up @@ -884,6 +941,32 @@ pub fn draw<Renderer, T>(
}
}

fn layout_region_bounds(layout: Layout<'_>, region: Region) -> Rectangle {
let bounds = layout.bounds();

match region {
Region::Center => bounds,
Region::Top => Rectangle {
height: bounds.height / 2.0,
..bounds
},
Region::Left => Rectangle {
width: bounds.width / 2.0,
..bounds
},
Region::Right => Rectangle {
x: bounds.x + bounds.width / 2.0,
width: bounds.width / 2.0,
..bounds
},
Region::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 {
Expand All @@ -900,6 +983,9 @@ pub enum DragEvent {

/// 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,
},

/// A [`Pane`] was picked and then dropped outside of other [`Pane`]
Expand All @@ -910,6 +996,22 @@ pub enum DragEvent {
},
}

/// The region of a [`Pane`].
#[derive(Debug, Clone, Copy, Default)]
pub enum Region {
/// Center region.
#[default]
Center,
/// Top region.
Top,
/// Left region.
Left,
/// Right region.
Right,
/// Bottom region.
Bottom,
}

/// An event produced during a resize interaction of a [`PaneGrid`].
#[derive(Debug, Clone, Copy)]
pub struct ResizeEvent {
Expand Down
41 changes: 40 additions & 1 deletion widget/src/pane_grid/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
//!
//! [`PaneGrid`]: crate::widget::PaneGrid
use crate::core::{Point, Size};
use crate::pane_grid::{Axis, Configuration, Direction, Node, Pane, Split};
use crate::pane_grid::{
Axis, Configuration, Direction, Node, Pane, Region, Split,
};

use std::collections::HashMap;

Expand Down Expand Up @@ -165,6 +167,43 @@ 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,
target: &Pane,
pane: &Pane,
swap: bool,
) {
if let Some((state, _)) = self.close(pane) {
if let Some((new_pane, _)) = self.split(axis, target, state) {
if swap {
self.swap(target, &new_pane);
}
}
}
}

/// Swaps the position of the provided panes in the [`State`].
///
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you
Expand Down