Remove focus concept from `pane_grid`

This commit is contained in:
Héctor Ramón Jiménez 2020-11-10 01:14:19 +01:00
parent d0402d072d
commit 5681c83d3c
3 changed files with 37 additions and 244 deletions

View File

@ -11,6 +11,7 @@ pub fn main() -> iced::Result {
struct Example { struct Example {
panes: pane_grid::State<Content>, panes: pane_grid::State<Content>,
panes_created: usize, panes_created: usize,
focus: Option<pane_grid::Pane>,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -33,6 +34,7 @@ impl Sandbox for Example {
Example { Example {
panes, panes,
panes_created: 1, panes_created: 1,
focus: None,
} }
} }
@ -52,7 +54,7 @@ impl Sandbox for Example {
self.panes_created += 1; self.panes_created += 1;
} }
Message::SplitFocused(axis) => { Message::SplitFocused(axis) => {
if let Some(pane) = self.panes.active() { if let Some(pane) = self.focus {
let _ = self.panes.split( let _ = self.panes.split(
axis, axis,
&pane, &pane,
@ -63,11 +65,11 @@ impl Sandbox for Example {
} }
} }
Message::FocusAdjacent(direction) => { Message::FocusAdjacent(direction) => {
if let Some(pane) = self.panes.active() { if let Some(pane) = self.focus {
if let Some(adjacent) = if let Some(adjacent) =
self.panes.adjacent(&pane, direction) self.panes.adjacent(&pane, direction)
{ {
self.panes.focus(&adjacent); self.focus = Some(adjacent);
} }
} }
} }
@ -85,7 +87,7 @@ impl Sandbox for Example {
let _ = self.panes.close(&pane); let _ = self.panes.close(&pane);
} }
Message::CloseFocused => { Message::CloseFocused => {
if let Some(pane) = self.panes.active() { if let Some(pane) = self.focus {
let _ = self.panes.close(&pane); let _ = self.panes.close(&pane);
} }
} }
@ -93,26 +95,26 @@ impl Sandbox for Example {
} }
fn view(&mut self) -> Element<Message> { fn view(&mut self) -> Element<Message> {
let focus = self.focus;
let total_panes = self.panes.len(); let total_panes = self.panes.len();
let pane_grid = let pane_grid = PaneGrid::new(&mut self.panes, |pane, content| {
PaneGrid::new(&mut self.panes, |pane, content, focus| { let is_focused = focus == Some(pane);
let is_focused = focus.is_some();
let title_bar =
pane_grid::TitleBar::new(format!("Pane {}", content.id))
.padding(10)
.style(style::TitleBar { is_focused });
pane_grid::Content::new(content.view(pane, total_panes)) let title_bar =
.title_bar(title_bar) pane_grid::TitleBar::new(format!("Pane {}", content.id))
.style(style::Pane { is_focused }) .padding(10)
}) .style(style::TitleBar { is_focused });
.width(Length::Fill)
.height(Length::Fill) pane_grid::Content::new(content.view(pane, total_panes))
.spacing(10) .title_bar(title_bar)
.on_drag(Message::Dragged) .style(style::Pane { is_focused })
.on_resize(10, Message::Resized) })
.on_key_press(handle_hotkey); .width(Length::Fill)
.height(Length::Fill)
.spacing(10)
.on_drag(Message::Dragged)
.on_resize(10, Message::Resized);
Container::new(pane_grid) Container::new(pane_grid)
.width(Length::Fill) .width(Length::Fill)

View File

@ -73,7 +73,7 @@ use crate::{
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane); /// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
/// ///
/// let pane_grid = /// let pane_grid =
/// PaneGrid::new(&mut state, |pane, state, focus| { /// PaneGrid::new(&mut state, |pane, state| {
/// pane_grid::Content::new(match state { /// pane_grid::Content::new(match state {
/// PaneState::SomePane => Text::new("This is some pane"), /// PaneState::SomePane => Text::new("This is some pane"),
/// PaneState::AnotherKindOfPane => Text::new("This is another kind of pane"), /// PaneState::AnotherKindOfPane => Text::new("This is another kind of pane"),
@ -92,10 +92,8 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> {
width: Length, width: Length,
height: Length, height: Length,
spacing: u16, spacing: u16,
modifier_keys: keyboard::ModifiersState,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,
on_key_press: Option<Box<dyn Fn(KeyPressEvent) -> Option<Message> + 'a>>,
} }
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
@ -112,31 +110,13 @@ where
/// [`Pane`]: struct.Pane.html /// [`Pane`]: struct.Pane.html
pub fn new<T>( pub fn new<T>(
state: &'a mut State<T>, state: &'a mut State<T>,
view: impl Fn( view: impl Fn(Pane, &'a mut T) -> Content<'a, Message, Renderer>,
Pane,
&'a mut T,
Option<Focus>,
) -> Content<'a, Message, Renderer>,
) -> Self { ) -> Self {
let elements = { let elements = {
let action = state.internal.action();
let current_focus = action.focus();
state state
.panes .panes
.iter_mut() .iter_mut()
.map(move |(pane, pane_state)| { .map(move |(pane, pane_state)| (*pane, view(*pane, pane_state)))
let focus = match current_focus {
Some((focused_pane, focus))
if *pane == focused_pane =>
{
Some(focus)
}
_ => None,
};
(*pane, view(*pane, pane_state, focus))
})
.collect() .collect()
}; };
@ -146,13 +126,8 @@ where
width: Length::Fill, width: Length::Fill,
height: Length::Fill, height: Length::Fill,
spacing: 0, spacing: 0,
modifier_keys: keyboard::ModifiersState {
control: true,
..Default::default()
},
on_drag: None, on_drag: None,
on_resize: None, on_resize: None,
on_key_press: None,
} }
} }
@ -180,21 +155,6 @@ where
self self
} }
/// Sets the modifier keys of the [`PaneGrid`].
///
/// The modifier keys will need to be pressed to trigger key events.
///
/// The default modifier key is `Ctrl`.
///
/// [`PaneGrid`]: struct.PaneGrid.html
pub fn modifier_keys(
mut self,
modifier_keys: keyboard::ModifiersState,
) -> Self {
self.modifier_keys = modifier_keys;
self
}
/// Enables the drag and drop interactions of the [`PaneGrid`], which will /// Enables the drag and drop interactions of the [`PaneGrid`], which will
/// use the provided function to produce messages. /// use the provided function to produce messages.
/// ///
@ -225,31 +185,6 @@ where
self.on_resize = Some((leeway, Box::new(f))); self.on_resize = Some((leeway, Box::new(f)));
self self
} }
/// Captures hotkey interactions with the [`PaneGrid`], using the provided
/// function to produce messages.
///
/// The function will be called when:
/// - a [`Pane`] is focused
/// - a key is pressed
/// - all the modifier keys are pressed
///
/// If the function returns `None`, the key press event will be discarded
/// without producing any message.
///
/// This method is particularly useful to implement hotkey interactions.
/// For instance, you can use it to enable splitting, swapping, or resizing
/// panes by pressing combinations of keys.
///
/// [`PaneGrid`]: struct.PaneGrid.html
/// [`Pane`]: struct.Pane.html
pub fn on_key_press<F>(mut self, f: F) -> Self
where
F: 'a + Fn(KeyPressEvent) -> Option<Message>,
{
self.on_key_press = Some(Box::new(f));
self
}
} }
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer>
@ -280,13 +215,9 @@ where
messages messages
.push(on_drag(DragEvent::Picked { pane: *pane })); .push(on_drag(DragEvent::Picked { pane: *pane }));
} else {
self.state.focus(pane);
} }
} }
None => { None => {}
self.state.focus(pane);
}
} }
} }
} }
@ -495,17 +426,10 @@ where
); );
} }
} }
} else {
// TODO: Encode cursor availability in the type system
if cursor_position.x > 0.0 && cursor_position.y > 0.0 {
self.state.unfocus();
}
} }
} }
mouse::Event::ButtonReleased(mouse::Button::Left) => { mouse::Event::ButtonReleased(mouse::Button::Left) => {
if let Some((pane, _)) = self.state.picked_pane() { if let Some((pane, _)) = self.state.picked_pane() {
self.state.focus(&pane);
if let Some(on_drag) = &self.on_drag { if let Some(on_drag) = &self.on_drag {
let mut dropped_region = self let mut dropped_region = self
.elements .elements
@ -527,8 +451,10 @@ where
messages.push(on_drag(event)); messages.push(on_drag(event));
} }
self.state.idle();
} else if self.state.picked_split().is_some() { } else if self.state.picked_split().is_some() {
self.state.drop_split(); self.state.idle();
} }
} }
mouse::Event::CursorMoved { .. } => { mouse::Event::CursorMoved { .. } => {
@ -536,31 +462,6 @@ where
} }
_ => {} _ => {}
}, },
Event::Keyboard(keyboard_event) => {
match keyboard_event {
keyboard::Event::KeyPressed {
modifiers,
key_code,
} => {
if let Some(on_key_press) = &self.on_key_press {
// TODO: Discard when event is captured
if let Some(_) = self.state.active_pane() {
if modifiers.matches(self.modifier_keys) {
if let Some(message) =
on_key_press(KeyPressEvent {
key_code,
modifiers,
})
{
messages.push(message);
}
}
}
}
}
_ => {}
}
}
_ => {} _ => {}
} }

View File

@ -72,7 +72,7 @@ impl<T> State<T> {
internal: Internal { internal: Internal {
layout, layout,
last_id, last_id,
action: Action::Idle { focus: None }, action: Action::Idle,
}, },
} }
} }
@ -122,45 +122,10 @@ impl<T> State<T> {
&self.internal.layout &self.internal.layout
} }
/// Returns the focused [`Pane`] of the [`State`], if there is one.
///
/// [`Pane`]: struct.Pane.html
/// [`State`]: struct.State.html
pub fn focused(&self) -> Option<Pane> {
self.internal.focused_pane()
}
/// Returns the active [`Pane`] of the [`State`], if there is one.
///
/// A [`Pane`] is active if it is focused and is __not__ being dragged.
///
/// [`Pane`]: struct.Pane.html
/// [`State`]: struct.State.html
pub fn active(&self) -> Option<Pane> {
self.internal.active_pane()
}
/// Returns the adjacent [`Pane`] of another [`Pane`] in the given /// Returns the adjacent [`Pane`] of another [`Pane`] in the given
/// direction, if there is one. /// direction, if there is one.
/// ///
/// ## Example
/// You can combine this with [`State::active`] to find the pane that is
/// adjacent to the current active one, and then swap them. For instance:
///
/// ```
/// # use iced_native::pane_grid;
/// #
/// # let (mut state, _) = pane_grid::State::new(());
/// #
/// if let Some(active) = state.active() {
/// if let Some(adjacent) = state.adjacent(&active, pane_grid::Direction::Right) {
/// state.swap(&active, &adjacent);
/// }
/// }
/// ```
///
/// [`Pane`]: struct.Pane.html /// [`Pane`]: struct.Pane.html
/// [`State::active`]: struct.State.html#method.active
pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> { pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> {
let regions = self let regions = self
.internal .internal
@ -194,20 +159,6 @@ impl<T> State<T> {
Some(*pane) Some(*pane)
} }
/// Focuses the given [`Pane`].
///
/// [`Pane`]: struct.Pane.html
pub fn focus(&mut self, pane: &Pane) {
self.internal.focus(pane);
}
/// Unfocuses the current focused [`Pane`].
///
/// [`Pane`]: struct.Pane.html
pub fn unfocus(&mut self) {
self.internal.unfocus();
}
/// Splits the given [`Pane`] into two in the given [`Axis`] and /// Splits the given [`Pane`] into two in the given [`Axis`] and
/// initializing the new [`Pane`] with the provided internal state. /// initializing the new [`Pane`] with the provided internal state.
/// ///
@ -236,7 +187,6 @@ impl<T> State<T> {
node.split(new_split, axis, new_pane); node.split(new_split, axis, new_pane);
let _ = self.panes.insert(new_pane, state); let _ = self.panes.insert(new_pane, state);
self.focus(&new_pane);
Some((new_pane, new_split)) Some((new_pane, new_split))
} }
@ -281,8 +231,7 @@ impl<T> State<T> {
/// ///
/// [`Pane`]: struct.Pane.html /// [`Pane`]: struct.Pane.html
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(_) = self.internal.layout.remove(pane) {
self.focus(&sibling);
self.panes.remove(pane) self.panes.remove(pane)
} else { } else {
None None
@ -329,52 +278,12 @@ pub struct Internal {
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum Action { pub enum Action {
Idle { Idle,
focus: Option<Pane>, Dragging { pane: Pane, origin: Point },
}, Resizing { split: Split, axis: Axis },
Dragging {
pane: Pane,
origin: Point,
focus: Option<Pane>,
},
Resizing {
split: Split,
axis: Axis,
focus: Option<Pane>,
},
}
impl Action {
pub fn focus(&self) -> Option<(Pane, Focus)> {
match self {
Action::Idle { focus } | Action::Resizing { focus, .. } => {
focus.map(|pane| (pane, Focus::Idle))
}
Action::Dragging { pane, .. } => Some((*pane, Focus::Dragging)),
}
}
} }
impl Internal { impl Internal {
pub fn action(&self) -> Action {
self.action
}
pub fn focused_pane(&self) -> Option<Pane> {
match self.action {
Action::Idle { focus } => focus,
Action::Dragging { focus, .. } => focus,
Action::Resizing { focus, .. } => focus,
}
}
pub fn active_pane(&self) -> Option<Pane> {
match self.action {
Action::Idle { focus } => focus,
_ => None,
}
}
pub fn picked_pane(&self) -> Option<(Pane, Point)> { pub fn picked_pane(&self) -> Option<(Pane, Point)> {
match self.action { match self.action {
Action::Dragging { pane, origin, .. } => Some((pane, origin)), Action::Dragging { pane, origin, .. } => Some((pane, origin)),
@ -405,17 +314,10 @@ impl Internal {
self.layout.split_regions(spacing, size) self.layout.split_regions(spacing, size)
} }
pub fn focus(&mut self, pane: &Pane) {
self.action = Action::Idle { focus: Some(*pane) };
}
pub fn pick_pane(&mut self, pane: &Pane, origin: Point) { pub fn pick_pane(&mut self, pane: &Pane, origin: Point) {
let focus = self.focused_pane();
self.action = Action::Dragging { self.action = Action::Dragging {
pane: *pane, pane: *pane,
origin, origin,
focus,
}; };
} }
@ -426,26 +328,14 @@ impl Internal {
return; return;
} }
let focus = self.action.focus().map(|(pane, _)| pane);
self.action = Action::Resizing { self.action = Action::Resizing {
split: *split, split: *split,
axis, axis,
focus,
}; };
} }
pub fn drop_split(&mut self) { pub fn idle(&mut self) {
match self.action { self.action = Action::Idle;
Action::Resizing { focus, .. } => {
self.action = Action::Idle { focus };
}
_ => {}
}
}
pub fn unfocus(&mut self) {
self.action = Action::Idle { focus: None };
} }
pub fn hash_layout(&self, hasher: &mut Hasher) { pub fn hash_layout(&self, hasher: &mut Hasher) {