From 99aa54cd88d7eb99149699d539ee4d59e08047b1 Mon Sep 17 00:00:00 2001 From: Joao Freitas <51237625+jhff@users.noreply.github.com> Date: Tue, 16 May 2023 16:12:29 +0100 Subject: Add pane_grid functionality to split a pane with another pane --- examples/pane_grid/src/main.rs | 7 ++- style/src/pane_grid.rs | 18 ++++++- style/src/theme.rs | 19 +++++++ widget/src/pane_grid.rs | 110 +++++++++++++++++++++++++++++++++++++++-- widget/src/pane_grid/state.rs | 51 ++++++++++++++++++- 5 files changed, 198 insertions(+), 7 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index dfb80853..d670e1e8 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -108,8 +108,12 @@ impl Application for Example { Message::Dragged(pane_grid::DragEvent::Dropped { pane, target, + region, }) => { - self.panes.swap(&pane, &target); + if let Some(state) = self.panes.get(&pane) { + let pane = (*state, &pane); + self.panes.split_with(&target, pane, region); + } } Message::Dragged(_) => {} Message::TogglePin(pane) => { @@ -255,6 +259,7 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { } } +#[derive(Clone, Copy)] struct Pane { id: usize, pub is_pinned: bool, diff --git a/style/src/pane_grid.rs b/style/src/pane_grid.rs index fd8fc05f..ec422e86 100644 --- a/style/src/pane_grid.rs +++ b/style/src/pane_grid.rs @@ -1,11 +1,14 @@ //! 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 [`Region`] to draw when a pane is hovered. + fn hovered_region(&self, style: &Self::Style) -> Region; + /// The [`Line`] to draw when a split is picked. fn picked_split(&self, style: &Self::Style) -> Option; @@ -13,6 +16,19 @@ pub trait StyleSheet { fn hovered_split(&self, style: &Self::Style) -> Option; } +/// The appearance of the hovered region of a pane grid. +#[derive(Debug, Clone, Copy)] +pub struct Region { + /// 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. /// /// It is normally used to define the highlight of something, like a split. diff --git a/style/src/theme.rs b/style/src/theme.rs index d9893bcf..c7b2bada 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -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::Region { + match style { + PaneGrid::Default => { + let palette = self.extended_palette(); + + pane_grid::Region { + 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 { match style { PaneGrid::Default => { diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 67145e8e..f7fdc072 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -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 }, }; @@ -657,6 +662,28 @@ pub fn update<'a, Message, T: Draggable>( event_status } +fn layout_region(layout: Layout<'_>, cursor_position: Point) -> Option { + 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<'_>, @@ -810,6 +837,36 @@ pub fn draw( 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, @@ -884,6 +941,32 @@ pub fn draw( } } +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 { @@ -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`] @@ -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 { diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index a6e2ec7f..b57cdd92 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -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; @@ -165,6 +167,53 @@ impl State { 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: (T, &Pane), + region: Region, + ) { + match region { + Region::Center => { + let (_, pane) = pane; + 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: (T, &Pane), + invert: bool, + ) { + let (state, pane) = pane; + + if let Some((new_pane, _)) = self.split(axis, target, state) { + if invert { + self.swap(target, &new_pane); + } + + let _ = self.close(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 -- cgit From 0cb84c1c4cc128f8260054192b4f7798de52a82a Mon Sep 17 00:00:00 2001 From: Joao Freitas <51237625+jhff@users.noreply.github.com> Date: Wed, 17 May 2023 15:30:35 +0100 Subject: Change name of hovered region style struct, reorder lines & export Appearance --- style/src/pane_grid.rs | 32 ++++++++++++++++---------------- style/src/theme.rs | 4 ++-- widget/src/pane_grid.rs | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/style/src/pane_grid.rs b/style/src/pane_grid.rs index ec422e86..c1002725 100644 --- a/style/src/pane_grid.rs +++ b/style/src/pane_grid.rs @@ -1,24 +1,9 @@ //! Change the appearance of a pane grid. 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 [`Region`] to draw when a pane is hovered. - fn hovered_region(&self, style: &Self::Style) -> Region; - - /// The [`Line`] to draw when a split is picked. - fn picked_split(&self, style: &Self::Style) -> Option; - - /// The [`Line`] to draw when a split is hovered. - fn hovered_split(&self, style: &Self::Style) -> Option; -} - /// The appearance of the hovered region of a pane grid. #[derive(Debug, Clone, Copy)] -pub struct Region { +pub struct Appearance { /// The [`Background`] of the hovered pane region. pub background: Background, /// The border width of the hovered pane region. @@ -40,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; + + /// The [`Line`] to draw when a split is hovered. + fn hovered_split(&self, style: &Self::Style) -> Option; +} diff --git a/style/src/theme.rs b/style/src/theme.rs index c7b2bada..8a7be72c 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -703,12 +703,12 @@ pub enum PaneGrid { impl pane_grid::StyleSheet for Theme { type Style = PaneGrid; - fn hovered_region(&self, style: &Self::Style) -> pane_grid::Region { + fn hovered_region(&self, style: &Self::Style) -> pane_grid::Appearance { match style { PaneGrid::Default => { let palette = self.extended_palette(); - pane_grid::Region { + pane_grid::Appearance { background: Background::Color(Color { a: 0.5, ..palette.primary.base.color diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index f7fdc072..e6ffb1d6 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -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}; -- cgit From bc590e2d6f3a39b09b11928c829f05d7bc9f9211 Mon Sep 17 00:00:00 2001 From: Joao Freitas <51237625+jhff@users.noreply.github.com> Date: Fri, 19 May 2023 12:12:08 +0100 Subject: Take pane state internally --- examples/pane_grid/src/main.rs | 5 +---- widget/src/pane_grid/state.rs | 26 ++++++++------------------ 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index d670e1e8..2ffdcc69 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -110,10 +110,7 @@ impl Application for Example { target, region, }) => { - if let Some(state) = self.panes.get(&pane) { - let pane = (*state, &pane); - self.panes.split_with(&target, pane, region); - } + self.panes.split_with(&target, &pane, region); } Message::Dragged(_) => {} Message::TogglePin(pane) => { diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index b57cdd92..6ab34884 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -170,17 +170,9 @@ impl State { /// 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: (T, &Pane), - region: Region, - ) { + pub fn split_with(&mut self, target: &Pane, pane: &Pane, region: Region) { match region { - Region::Center => { - let (_, pane) = pane; - self.swap(pane, target); - } + Region::Center => self.swap(pane, target), Region::Top => { self.split_and_swap(Axis::Horizontal, target, pane, true) } @@ -200,17 +192,15 @@ impl State { &mut self, axis: Axis, target: &Pane, - pane: (T, &Pane), + pane: &Pane, invert: bool, ) { - let (state, pane) = pane; - - if let Some((new_pane, _)) = self.split(axis, target, state) { - if invert { - self.swap(target, &new_pane); + if let Some((state, _)) = self.close(pane) { + if let Some((new_pane, _)) = self.split(axis, target, state) { + if invert { + self.swap(target, &new_pane); + } } - - let _ = self.close(pane); } } -- cgit From 9b5f32ee403c8b0730e3bac2b48aab6b87d7b653 Mon Sep 17 00:00:00 2001 From: Joao Freitas <51237625+jhff@users.noreply.github.com> Date: Fri, 19 May 2023 12:15:44 +0100 Subject: Rename invert -> swap --- widget/src/pane_grid/state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index 6ab34884..1f034ca3 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -193,11 +193,11 @@ impl State { axis: Axis, target: &Pane, pane: &Pane, - invert: bool, + swap: bool, ) { if let Some((state, _)) = self.close(pane) { if let Some((new_pane, _)) = self.split(axis, target, state) { - if invert { + if swap { self.swap(target, &new_pane); } } -- cgit