Draft drag and drop support for PaneGrid

This commit is contained in:
Héctor Ramón Jiménez 2020-03-10 06:47:32 +01:00
parent ed7c327b48
commit eb070b9652
2 changed files with 196 additions and 46 deletions

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
input::{mouse, ButtonState}, input::{keyboard, mouse, ButtonState},
layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point,
Rectangle, Size, Widget, Rectangle, Size, Widget,
}; };
@ -12,6 +12,7 @@ pub struct PaneGrid<'a, Message, Renderer> {
elements: Vec<(Pane, Element<'a, Message, Renderer>)>, elements: Vec<(Pane, Element<'a, Message, Renderer>)>,
width: Length, width: Length,
height: Length, height: Length,
on_drop: Option<Box<dyn Fn(Drop) -> Message>>,
} }
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> {
@ -30,6 +31,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> {
elements, elements,
width: Length::Fill, width: Length::Fill,
height: Length::Fill, height: Length::Fill,
on_drop: None,
} }
} }
@ -48,6 +50,17 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> {
self.height = height; self.height = height;
self self
} }
pub fn on_drop(mut self, f: impl Fn(Drop) -> Message + 'static) -> Self {
self.on_drop = Some(Box::new(f));
self
}
}
#[derive(Debug, Clone, Copy)]
pub struct Drop {
pub pane: Pane,
pub target: Pane,
} }
impl<'a, Message, Renderer> Widget<Message, Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer>
@ -105,32 +118,76 @@ where
match event { match event {
Event::Mouse(mouse::Event::Input { Event::Mouse(mouse::Event::Input {
button: mouse::Button::Left, button: mouse::Button::Left,
state: ButtonState::Pressed, state,
}) => { }) => match state {
let mut clicked_region = ButtonState::Pressed => {
self.elements.iter().zip(layout.children()).filter( let mut clicked_region =
|(_, layout)| layout.bounds().contains(cursor_position), self.elements.iter().zip(layout.children()).filter(
); |(_, layout)| {
layout.bounds().contains(cursor_position)
},
);
if let Some(((pane, _), _)) = clicked_region.next() { if let Some(((pane, _), _)) = clicked_region.next() {
self.state.focused_pane = Some(*pane); self.state.focus = if self.on_drop.is_some()
&& self.state.modifiers.alt
{
Some(Focus::Dragging(*pane))
} else {
Some(Focus::Idle(*pane))
}
}
} }
ButtonState::Released => {
if let Some(on_drop) = &self.on_drop {
if let Some(Focus::Dragging(pane)) = self.state.focus {
let mut dropped_region = self
.elements
.iter()
.zip(layout.children())
.filter(|(_, layout)| {
layout.bounds().contains(cursor_position)
});
if let Some(((target, _), _)) =
dropped_region.next()
{
if pane != *target {
messages.push(on_drop(Drop {
pane,
target: *target,
}));
}
}
self.state.focus = Some(Focus::Idle(pane));
}
}
}
},
Event::Keyboard(keyboard::Event::Input { modifiers, .. }) => {
self.state.modifiers = modifiers;
} }
_ => {} _ => {}
} }
self.elements.iter_mut().zip(layout.children()).for_each( match self.state.focus {
|((_, pane), layout)| { Some(Focus::Dragging(_)) => {}
pane.widget.on_event( _ => {
event.clone(), self.elements.iter_mut().zip(layout.children()).for_each(
layout, |((_, pane), layout)| {
cursor_position, pane.widget.on_event(
messages, event.clone(),
renderer, layout,
clipboard, cursor_position,
) messages,
}, renderer,
); clipboard,
)
},
);
}
}
} }
fn draw( fn draw(
@ -140,7 +197,18 @@ where
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
) -> Renderer::Output { ) -> Renderer::Output {
renderer.draw(defaults, &self.elements, layout, cursor_position) let dragging = match self.state.focus {
Some(Focus::Dragging(pane)) => Some(pane),
_ => None,
};
renderer.draw(
defaults,
&self.elements,
dragging,
layout,
cursor_position,
)
} }
fn hash_layout(&self, state: &mut Hasher) { fn hash_layout(&self, state: &mut Hasher) {
@ -176,7 +244,14 @@ pub struct State<T> {
struct Internal { struct Internal {
layout: Node, layout: Node,
last_pane: usize, last_pane: usize,
focused_pane: Option<Pane>, focus: Option<Focus>,
modifiers: keyboard::ModifiersState,
}
#[derive(Debug)]
enum Focus {
Idle(Pane),
Dragging(Pane),
} }
impl<T> State<T> { impl<T> State<T> {
@ -192,7 +267,8 @@ impl<T> State<T> {
internal: Internal { internal: Internal {
layout: Node::Pane(first_pane), layout: Node::Pane(first_pane),
last_pane: 0, last_pane: 0,
focused_pane: None, focus: None,
modifiers: keyboard::ModifiersState::default(),
}, },
}, },
first_pane, first_pane,
@ -216,11 +292,15 @@ impl<T> State<T> {
} }
pub fn focused_pane(&self) -> Option<Pane> { pub fn focused_pane(&self) -> Option<Pane> {
self.internal.focused_pane match self.internal.focus {
Some(Focus::Idle(pane)) => Some(pane),
Some(Focus::Dragging(_)) => None,
None => None,
}
} }
pub fn focus(&mut self, pane: Pane) { pub fn focus(&mut self, pane: Pane) {
self.internal.focused_pane = Some(pane); self.internal.focus = Some(Focus::Idle(pane));
} }
pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option<Pane> { pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option<Pane> {
@ -252,14 +332,27 @@ impl<T> State<T> {
node.split(kind, new_pane); node.split(kind, new_pane);
let _ = self.panes.insert(new_pane, state); let _ = self.panes.insert(new_pane, state);
self.internal.focused_pane = Some(new_pane); self.internal.focus = Some(Focus::Idle(new_pane));
Some(new_pane) Some(new_pane)
} }
pub fn swap(&mut self, a: &Pane, b: &Pane) {
self.internal.layout.update(&|node| match node {
Node::Split { .. } => {}
Node::Pane(pane) => {
if pane == a {
*node = Node::Pane(*b);
} else if pane == b {
*node = Node::Pane(*a);
}
}
});
}
pub fn close(&mut self, pane: &Pane) -> Option<T> { pub fn close(&mut self, pane: &Pane) -> Option<T> {
if let Some(sibling) = self.internal.layout.remove(pane) { if let Some(sibling) = self.internal.layout.remove(pane) {
self.internal.focused_pane = Some(sibling); self.internal.focus = Some(Focus::Idle(sibling));
self.panes.remove(pane) self.panes.remove(pane)
} else { } else {
None None
@ -307,6 +400,18 @@ impl Node {
}; };
} }
fn update(&mut self, f: &impl Fn(&mut Node)) {
match self {
Node::Split { a, b, .. } => {
a.update(f);
b.update(f);
}
_ => {}
}
f(self);
}
fn remove(&mut self, pane: &Pane) -> Option<Pane> { fn remove(&mut self, pane: &Pane) -> Option<Pane> {
match self { match self {
Node::Split { a, b, .. } => { Node::Split { a, b, .. } => {
@ -444,6 +549,7 @@ pub trait Renderer: crate::Renderer + Sized {
&mut self, &mut self,
defaults: &Self::Defaults, defaults: &Self::Defaults,
content: &[(Pane, Element<'_, Message, Self>)], content: &[(Pane, Element<'_, Message, Self>)],
dragging: Option<Pane>,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
) -> Self::Output; ) -> Self::Output;

View file

@ -1,34 +1,78 @@
use crate::{Primitive, Renderer}; use crate::{Primitive, Renderer};
use iced_native::{pane_grid, Element, Layout, MouseCursor, Point}; use iced_native::{
pane_grid::{self, Pane},
Element, Layout, MouseCursor, Point, Rectangle, Vector,
};
impl pane_grid::Renderer for Renderer { impl pane_grid::Renderer for Renderer {
fn draw<Message>( fn draw<Message>(
&mut self, &mut self,
defaults: &Self::Defaults, defaults: &Self::Defaults,
content: &[(pane_grid::Pane, Element<'_, Message, Self>)], content: &[(Pane, Element<'_, Message, Self>)],
dragging: Option<Pane>,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
) -> Self::Output { ) -> Self::Output {
let mut mouse_cursor = MouseCursor::OutOfBounds; let mut mouse_cursor = MouseCursor::OutOfBounds;
let mut dragged_pane = None;
let mut panes: Vec<_> = content
.iter()
.zip(layout.children())
.enumerate()
.map(|(i, ((id, pane), layout))| {
let (primitive, new_mouse_cursor) =
pane.draw(self, defaults, layout, cursor_position);
if new_mouse_cursor > mouse_cursor {
mouse_cursor = new_mouse_cursor;
}
if Some(*id) == dragging {
dragged_pane = Some((i, layout));
}
primitive
})
.collect();
let primitives = if let Some((index, layout)) = dragged_pane {
let pane = panes.remove(index);
let bounds = layout.bounds();
// TODO: Fix once proper layering is implemented.
// This is a pretty hacky way to achieve layering.
let clip = Primitive::Clip {
bounds: Rectangle {
x: cursor_position.x - bounds.width / 2.0,
y: cursor_position.y - bounds.height / 2.0,
width: bounds.width + 0.5,
height: bounds.height + 0.5,
},
offset: Vector::new(0, 0),
content: Box::new(Primitive::Cached {
origin: Point::new(
cursor_position.x - bounds.x - bounds.width / 2.0,
cursor_position.y - bounds.y - bounds.height / 2.0,
),
cache: std::sync::Arc::new(pane),
}),
};
panes.push(clip);
panes
} else {
panes
};
( (
Primitive::Group { Primitive::Group { primitives },
primitives: content if dragging.is_some() {
.iter() MouseCursor::Grabbing
.zip(layout.children()) } else {
.map(|((_, pane), layout)| { mouse_cursor
let (primitive, new_mouse_cursor) =
pane.draw(self, defaults, layout, cursor_position);
if new_mouse_cursor > mouse_cursor {
mouse_cursor = new_mouse_cursor;
}
primitive
})
.collect(),
}, },
mouse_cursor,
) )
} }
} }