From 5681c83d3c30cfb6940de734b60e61da9571ed0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 10 Nov 2020 01:14:19 +0100 Subject: [PATCH 1/6] Remove focus concept from `pane_grid` --- examples/pane_grid/src/main.rs | 44 +++++----- native/src/widget/pane_grid.rs | 113 ++---------------------- native/src/widget/pane_grid/state.rs | 124 ++------------------------- 3 files changed, 37 insertions(+), 244 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index c4946645..42184114 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -11,6 +11,7 @@ pub fn main() -> iced::Result { struct Example { panes: pane_grid::State, panes_created: usize, + focus: Option, } #[derive(Debug, Clone, Copy)] @@ -33,6 +34,7 @@ impl Sandbox for Example { Example { panes, panes_created: 1, + focus: None, } } @@ -52,7 +54,7 @@ impl Sandbox for Example { self.panes_created += 1; } Message::SplitFocused(axis) => { - if let Some(pane) = self.panes.active() { + if let Some(pane) = self.focus { let _ = self.panes.split( axis, &pane, @@ -63,11 +65,11 @@ impl Sandbox for Example { } } Message::FocusAdjacent(direction) => { - if let Some(pane) = self.panes.active() { + if let Some(pane) = self.focus { if let Some(adjacent) = 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); } Message::CloseFocused => { - if let Some(pane) = self.panes.active() { + if let Some(pane) = self.focus { let _ = self.panes.close(&pane); } } @@ -93,26 +95,26 @@ impl Sandbox for Example { } fn view(&mut self) -> Element { + let focus = self.focus; let total_panes = self.panes.len(); - let pane_grid = - PaneGrid::new(&mut self.panes, |pane, content, focus| { - let is_focused = focus.is_some(); - let title_bar = - pane_grid::TitleBar::new(format!("Pane {}", content.id)) - .padding(10) - .style(style::TitleBar { is_focused }); + let pane_grid = PaneGrid::new(&mut self.panes, |pane, content| { + let is_focused = focus == Some(pane); - pane_grid::Content::new(content.view(pane, total_panes)) - .title_bar(title_bar) - .style(style::Pane { is_focused }) - }) - .width(Length::Fill) - .height(Length::Fill) - .spacing(10) - .on_drag(Message::Dragged) - .on_resize(10, Message::Resized) - .on_key_press(handle_hotkey); + 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)) + .title_bar(title_bar) + .style(style::Pane { is_focused }) + }) + .width(Length::Fill) + .height(Length::Fill) + .spacing(10) + .on_drag(Message::Dragged) + .on_resize(10, Message::Resized); Container::new(pane_grid) .width(Length::Fill) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 276bfae3..43d57e19 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -73,7 +73,7 @@ use crate::{ /// let (mut state, _) = pane_grid::State::new(PaneState::SomePane); /// /// let pane_grid = -/// PaneGrid::new(&mut state, |pane, state, focus| { +/// PaneGrid::new(&mut state, |pane, state| { /// pane_grid::Content::new(match state { /// PaneState::SomePane => Text::new("This is some 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, height: Length, spacing: u16, - modifier_keys: keyboard::ModifiersState, on_drag: Option Message + 'a>>, on_resize: Option<(u16, Box Message + 'a>)>, - on_key_press: Option Option + 'a>>, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> @@ -112,31 +110,13 @@ where /// [`Pane`]: struct.Pane.html pub fn new( state: &'a mut State, - view: impl Fn( - Pane, - &'a mut T, - Option, - ) -> Content<'a, Message, Renderer>, + view: impl Fn(Pane, &'a mut T) -> Content<'a, Message, Renderer>, ) -> Self { let elements = { - let action = state.internal.action(); - let current_focus = action.focus(); - state .panes .iter_mut() - .map(move |(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)) - }) + .map(move |(pane, pane_state)| (*pane, view(*pane, pane_state))) .collect() }; @@ -146,13 +126,8 @@ where width: Length::Fill, height: Length::Fill, spacing: 0, - modifier_keys: keyboard::ModifiersState { - control: true, - ..Default::default() - }, on_drag: None, on_resize: None, - on_key_press: None, } } @@ -180,21 +155,6 @@ where 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 /// use the provided function to produce messages. /// @@ -225,31 +185,6 @@ where self.on_resize = Some((leeway, Box::new(f))); 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(mut self, f: F) -> Self - where - F: 'a + Fn(KeyPressEvent) -> Option, - { - self.on_key_press = Some(Box::new(f)); - self - } } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> @@ -280,13 +215,9 @@ where messages .push(on_drag(DragEvent::Picked { pane: *pane })); - } else { - self.state.focus(pane); } } - None => { - self.state.focus(pane); - } + None => {} } } } @@ -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) => { if let Some((pane, _)) = self.state.picked_pane() { - self.state.focus(&pane); - if let Some(on_drag) = &self.on_drag { let mut dropped_region = self .elements @@ -527,8 +451,10 @@ where messages.push(on_drag(event)); } + + self.state.idle(); } else if self.state.picked_split().is_some() { - self.state.drop_split(); + self.state.idle(); } } 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); - } - } - } - } - } - _ => {} - } - } _ => {} } diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index fb59c846..e2793641 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -72,7 +72,7 @@ impl State { internal: Internal { layout, last_id, - action: Action::Idle { focus: None }, + action: Action::Idle, }, } } @@ -122,45 +122,10 @@ impl State { &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 { - 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 { - self.internal.active_pane() - } - /// Returns the adjacent [`Pane`] of another [`Pane`] in the given /// 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 - /// [`State::active`]: struct.State.html#method.active pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option { let regions = self .internal @@ -194,20 +159,6 @@ impl State { 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 /// initializing the new [`Pane`] with the provided internal state. /// @@ -236,7 +187,6 @@ impl State { node.split(new_split, axis, new_pane); let _ = self.panes.insert(new_pane, state); - self.focus(&new_pane); Some((new_pane, new_split)) } @@ -281,8 +231,7 @@ impl State { /// /// [`Pane`]: struct.Pane.html pub fn close(&mut self, pane: &Pane) -> Option { - if let Some(sibling) = self.internal.layout.remove(pane) { - self.focus(&sibling); + if let Some(_) = self.internal.layout.remove(pane) { self.panes.remove(pane) } else { None @@ -329,52 +278,12 @@ pub struct Internal { #[derive(Debug, Clone, Copy, PartialEq)] pub enum Action { - Idle { - focus: Option, - }, - Dragging { - pane: Pane, - origin: Point, - focus: Option, - }, - Resizing { - split: Split, - axis: Axis, - focus: Option, - }, -} - -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)), - } - } + Idle, + Dragging { pane: Pane, origin: Point }, + Resizing { split: Split, axis: Axis }, } impl Internal { - pub fn action(&self) -> Action { - self.action - } - - pub fn focused_pane(&self) -> Option { - match self.action { - Action::Idle { focus } => focus, - Action::Dragging { focus, .. } => focus, - Action::Resizing { focus, .. } => focus, - } - } - - pub fn active_pane(&self) -> Option { - match self.action { - Action::Idle { focus } => focus, - _ => None, - } - } - pub fn picked_pane(&self) -> Option<(Pane, Point)> { match self.action { Action::Dragging { pane, origin, .. } => Some((pane, origin)), @@ -405,17 +314,10 @@ impl Internal { 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) { - let focus = self.focused_pane(); - self.action = Action::Dragging { pane: *pane, origin, - focus, }; } @@ -426,26 +328,14 @@ impl Internal { return; } - let focus = self.action.focus().map(|(pane, _)| pane); - self.action = Action::Resizing { split: *split, axis, - focus, }; } - pub fn drop_split(&mut self) { - match self.action { - Action::Resizing { focus, .. } => { - self.action = Action::Idle { focus }; - } - _ => {} - } - } - - pub fn unfocus(&mut self) { - self.action = Action::Idle { focus: None }; + pub fn idle(&mut self) { + self.action = Action::Idle; } pub fn hash_layout(&self, hasher: &mut Hasher) { From 8008ea52862735aae326c8833355b3ecacb8fed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 10 Nov 2020 01:48:11 +0100 Subject: [PATCH 2/6] Introduce `on_click` handler in `PaneGrid` --- examples/pane_grid/Cargo.toml | 2 +- examples/pane_grid/src/main.rs | 17 ++++++++++++++-- native/src/widget/pane_grid.rs | 37 ++++++++++++++++++++++++---------- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index 3ed912ac..3d1631f1 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -6,4 +6,4 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../.." } +iced = { path = "../..", features = ["debug"] } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 42184114..36485e9a 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -19,6 +19,7 @@ enum Message { Split(pane_grid::Axis, pane_grid::Pane), SplitFocused(pane_grid::Axis), FocusAdjacent(pane_grid::Direction), + Clicked(pane_grid::Pane), Dragged(pane_grid::DragEvent), Resized(pane_grid::ResizeEvent), Close(pane_grid::Pane), @@ -45,22 +46,30 @@ impl Sandbox for Example { fn update(&mut self, message: Message) { match message { Message::Split(axis, pane) => { - let _ = self.panes.split( + let result = self.panes.split( axis, &pane, Content::new(self.panes_created), ); + if let Some((pane, _)) = result { + self.focus = Some(pane); + } + self.panes_created += 1; } Message::SplitFocused(axis) => { if let Some(pane) = self.focus { - let _ = self.panes.split( + let result = self.panes.split( axis, &pane, Content::new(self.panes_created), ); + if let Some((pane, _)) = result { + self.focus = Some(pane); + } + self.panes_created += 1; } } @@ -73,6 +82,9 @@ impl Sandbox for Example { } } } + Message::Clicked(pane) => { + self.focus = Some(pane); + } Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { self.panes.resize(&split, ratio); } @@ -113,6 +125,7 @@ impl Sandbox for Example { .width(Length::Fill) .height(Length::Fill) .spacing(10) + .on_click(Message::Clicked) .on_drag(Message::Dragged) .on_resize(10, Message::Resized); diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 43d57e19..584b2ba4 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -92,6 +92,7 @@ pub struct PaneGrid<'a, Message, Renderer: self::Renderer> { width: Length, height: Length, spacing: u16, + on_click: Option Message + 'a>>, on_drag: Option Message + 'a>>, on_resize: Option<(u16, Box Message + 'a>)>, } @@ -126,6 +127,7 @@ where width: Length::Fill, height: Length::Fill, spacing: 0, + on_click: None, on_drag: None, on_resize: None, } @@ -155,6 +157,19 @@ where self } + /// Sets the message that will be produced when a [`Pane`] of the + /// [`PaneGrid`] is clicked. + /// + /// [`Pane`]: struct.Pane.html + /// [`PaneGrid`]: struct.PaneGrid.html + pub fn on_click(mut self, f: F) -> Self + where + F: 'a + Fn(Pane) -> Message, + { + self.on_click = Some(Box::new(f)); + self + } + /// Enables the drag and drop interactions of the [`PaneGrid`], which will /// use the provided function to produce messages. /// @@ -203,21 +218,21 @@ where ); if let Some(((pane, content), layout)) = clicked_region.next() { - match &self.on_drag { - Some(on_drag) => { - if content.can_be_picked_at(layout, cursor_position) { - let pane_position = layout.position(); + if let Some(on_click) = &self.on_click { + messages.push(on_click(*pane)); + } - let origin = cursor_position - - Vector::new(pane_position.x, pane_position.y); + if let Some(on_drag) = &self.on_drag { + if content.can_be_picked_at(layout, cursor_position) { + let pane_position = layout.position(); - self.state.pick_pane(pane, origin); + let origin = cursor_position + - Vector::new(pane_position.x, pane_position.y); - messages - .push(on_drag(DragEvent::Picked { pane: *pane })); - } + self.state.pick_pane(pane, origin); + + messages.push(on_drag(DragEvent::Picked { pane: *pane })); } - None => {} } } } From c53022e8dff6fc6286b60bc897232fe4cb6e6202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 10 Nov 2020 01:51:27 +0100 Subject: [PATCH 3/6] Fix typo in documentation of `pane_grid::Content` --- native/src/widget/pane_grid/content.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 1d339b75..5bfbb9ed 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -41,9 +41,9 @@ where self } - /// Sets the style of the [`TitleBar`]. + /// Sets the style of the [`Content`]. /// - /// [`TitleBar`]: struct.TitleBar.html + /// [`Content`]: struct.Content.html pub fn style(mut self, style: impl Into) -> Self { self.style = style.into(); self From d6d5cf0294b1231f4909a782e90b491cc6838fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 10 Nov 2020 02:32:57 +0100 Subject: [PATCH 4/6] Restore hotkeys in `pane_grid` example - Implement `subscription::events_with` - Remove `pane_grid::KeyPressEvent` - Return closest sibling in `pane_grid::State::close` --- examples/pane_grid/Cargo.toml | 1 + examples/pane_grid/src/main.rs | 54 +++++++++++++++++++--------- glow/src/widget/pane_grid.rs | 4 +-- graphics/src/widget/pane_grid.rs | 4 +-- native/src/subscription.rs | 22 +++++++++++- native/src/subscription/events.rs | 19 +++++++--- native/src/widget/pane_grid.rs | 16 ++------- native/src/widget/pane_grid/state.rs | 9 ++--- wgpu/src/widget/pane_grid.rs | 4 +-- 9 files changed, 88 insertions(+), 45 deletions(-) diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index 3d1631f1..e489f210 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -7,3 +7,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["debug"] } +iced_native = { path = "../../native" } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 36485e9a..d149f9c6 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,8 +1,9 @@ use iced::{ - button, keyboard, pane_grid, scrollable, Align, Button, Column, Container, - Element, HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable, - Settings, Text, + button, executor, keyboard, pane_grid, scrollable, Align, Application, + Button, Column, Command, Container, Element, HorizontalAlignment, Length, + PaneGrid, Scrollable, Settings, Subscription, Text, }; +use iced_native::{subscription, Event}; pub fn main() -> iced::Result { Example::run(Settings::default()) @@ -26,24 +27,29 @@ enum Message { CloseFocused, } -impl Sandbox for Example { +impl Application for Example { type Message = Message; + type Executor = executor::Default; + type Flags = (); - fn new() -> Self { + fn new(_flags: ()) -> (Self, Command) { let (panes, _) = pane_grid::State::new(Content::new(0)); - Example { - panes, - panes_created: 1, - focus: None, - } + ( + Example { + panes, + panes_created: 1, + focus: None, + }, + Command::none(), + ) } fn title(&self) -> String { String::from("Pane grid - Iced") } - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Command { match message { Message::Split(axis, pane) => { let result = self.panes.split( @@ -96,14 +102,30 @@ impl Sandbox for Example { } Message::Dragged(_) => {} Message::Close(pane) => { - let _ = self.panes.close(&pane); + if let Some((_, sibling)) = self.panes.close(&pane) { + self.focus = Some(sibling); + } } Message::CloseFocused => { if let Some(pane) = self.focus { - let _ = self.panes.close(&pane); + if let Some((_, sibling)) = self.panes.close(&pane) { + self.focus = Some(sibling); + } } } } + + Command::none() + } + + fn subscription(&self) -> Subscription { + subscription::events_with(|event| match event { + Event::Keyboard(keyboard::Event::KeyPressed { + modifiers, + key_code, + }) if modifiers.control => handle_hotkey(key_code), + _ => None, + }) } fn view(&mut self) -> Element { @@ -137,11 +159,11 @@ impl Sandbox for Example { } } -fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option { +fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { use keyboard::KeyCode; use pane_grid::{Axis, Direction}; - let direction = match event.key_code { + let direction = match key_code { KeyCode::Up => Some(Direction::Up), KeyCode::Down => Some(Direction::Down), KeyCode::Left => Some(Direction::Left), @@ -149,7 +171,7 @@ fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option { _ => None, }; - match event.key_code { + match key_code { KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)), KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)), KeyCode::W => Some(Message::CloseFocused), diff --git a/glow/src/widget/pane_grid.rs b/glow/src/widget/pane_grid.rs index 3c47b562..9e6d27d0 100644 --- a/glow/src/widget/pane_grid.rs +++ b/glow/src/widget/pane_grid.rs @@ -11,8 +11,8 @@ use crate::Renderer; pub use iced_native::pane_grid::{ - Axis, Configuration, Direction, DragEvent, Focus, KeyPressEvent, Node, - Pane, ResizeEvent, Split, State, + Axis, Configuration, Direction, DragEvent, Focus, Node, Pane, ResizeEvent, + Split, State, }; /// A collection of panes distributed using either vertical or horizontal splits diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs index 5b0eb391..1bc01c03 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/src/widget/pane_grid.rs @@ -20,8 +20,8 @@ use iced_native::{ }; pub use iced_native::pane_grid::{ - Axis, Configuration, Content, Direction, DragEvent, Focus, KeyPressEvent, - Pane, ResizeEvent, Split, State, TitleBar, + Axis, Configuration, Content, Direction, DragEvent, Focus, Pane, + ResizeEvent, Split, State, TitleBar, }; /// A collection of panes distributed using either vertical or horizontal splits diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 0d002c6c..18750abf 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -43,5 +43,25 @@ use events::Events; /// [`Subscription`]: type.Subscription.html /// [`Event`]: ../enum.Event.html pub fn events() -> Subscription { - Subscription::from_recipe(Events) + Subscription::from_recipe(Events { f: Some }) +} + +/// Returns a [`Subscription`] that filters all the runtime events with the +/// provided function, producing messages accordingly. +/// +/// This subscription will call the provided function for every [`Event`] +/// handled by the runtime. If the function: +/// +/// - Returns `None`, the [`Event`] will be discarded. +/// - Returns `Some` message, the `Message` will be produced. +/// +/// [`Subscription`]: type.Subscription.html +/// [`Event`]: ../enum.Event.html +pub fn events_with( + f: fn(Event) -> Option, +) -> Subscription +where + Message: 'static + Send, +{ + Subscription::from_recipe(Events { f }) } diff --git a/native/src/subscription/events.rs b/native/src/subscription/events.rs index ceae467d..a1ae6051 100644 --- a/native/src/subscription/events.rs +++ b/native/src/subscription/events.rs @@ -2,17 +2,26 @@ use crate::{ subscription::{EventStream, Recipe}, Event, Hasher, }; +use iced_futures::futures::future; +use iced_futures::futures::StreamExt; use iced_futures::BoxStream; -pub struct Events; +pub struct Events { + pub(super) f: fn(Event) -> Option, +} -impl Recipe for Events { - type Output = Event; +impl Recipe for Events +where + Message: 'static + Send, +{ + type Output = Message; fn hash(&self, state: &mut Hasher) { use std::hash::Hash; - std::any::TypeId::of::().hash(state); + struct Marker; + std::any::TypeId::of::().hash(state); + self.f.hash(state); } fn stream( @@ -20,5 +29,7 @@ impl Recipe for Events { event_stream: EventStream, ) -> BoxStream { event_stream + .filter_map(move |event| future::ready((self.f)(event))) + .boxed() } } diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 584b2ba4..7d9659e9 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -29,8 +29,8 @@ pub use state::{Focus, State}; pub use title_bar::TitleBar; use crate::{ - container, keyboard, layout, mouse, overlay, row, text, Clipboard, Element, - Event, Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, + container, layout, mouse, overlay, row, text, Clipboard, Element, Event, + Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; /// A collection of panes distributed using either vertical or horizontal splits @@ -336,18 +336,6 @@ pub struct ResizeEvent { pub ratio: f32, } -/// An event produced during a key press interaction of a [`PaneGrid`]. -/// -/// [`PaneGrid`]: struct.PaneGrid.html -#[derive(Debug, Clone, Copy)] -pub struct KeyPressEvent { - /// The key that was pressed. - pub key_code: keyboard::KeyCode, - - /// The state of the modifier keys when the key was pressed. - pub modifiers: keyboard::ModifiersState, -} - impl<'a, Message, Renderer> Widget for PaneGrid<'a, Message, Renderer> where diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index e2793641..be36b070 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -227,12 +227,13 @@ impl State { let _ = self.internal.layout.resize(split, ratio); } - /// Closes the given [`Pane`] and returns its internal state, if it exists. + /// Closes the given [`Pane`] and returns its internal state and its closest + /// sibling, if it exists. /// /// [`Pane`]: struct.Pane.html - pub fn close(&mut self, pane: &Pane) -> Option { - if let Some(_) = self.internal.layout.remove(pane) { - self.panes.remove(pane) + pub fn close(&mut self, pane: &Pane) -> Option<(T, Pane)> { + if let Some(sibling) = self.internal.layout.remove(pane) { + self.panes.remove(pane).map(|state| (state, sibling)) } else { None } diff --git a/wgpu/src/widget/pane_grid.rs b/wgpu/src/widget/pane_grid.rs index 3c47b562..9e6d27d0 100644 --- a/wgpu/src/widget/pane_grid.rs +++ b/wgpu/src/widget/pane_grid.rs @@ -11,8 +11,8 @@ use crate::Renderer; pub use iced_native::pane_grid::{ - Axis, Configuration, Direction, DragEvent, Focus, KeyPressEvent, Node, - Pane, ResizeEvent, Split, State, + Axis, Configuration, Direction, DragEvent, Focus, Node, Pane, ResizeEvent, + Split, State, }; /// A collection of panes distributed using either vertical or horizontal splits From 3b2ed0d6f02ecacafd19a8cba7a5e54a54b163a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 10 Nov 2020 03:18:46 +0100 Subject: [PATCH 5/6] Remove unnecessary `move` in `PaneGrid::new` --- native/src/widget/pane_grid.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 7d9659e9..9d36bae6 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -117,7 +117,7 @@ where state .panes .iter_mut() - .map(move |(pane, pane_state)| (*pane, view(*pane, pane_state))) + .map(|(pane, pane_state)| (*pane, view(*pane, pane_state))) .collect() }; From 86fa12229e7117306b8297a09aa22c99802600c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 10 Nov 2020 21:18:21 +0100 Subject: [PATCH 6/6] Introduce `is_command_pressed` to `ModifiersState` --- core/src/keyboard/modifiers_state.rs | 20 ++++++++++++++++++++ examples/pane_grid/src/main.rs | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/core/src/keyboard/modifiers_state.rs b/core/src/keyboard/modifiers_state.rs index 4d24266f..254013c3 100644 --- a/core/src/keyboard/modifiers_state.rs +++ b/core/src/keyboard/modifiers_state.rs @@ -15,6 +15,26 @@ pub struct ModifiersState { } impl ModifiersState { + /// Returns true if the current [`ModifiersState`] has a "command key" + /// pressed. + /// + /// The "command key" is the main modifier key used to issue commands in the + /// current platform. Specifically: + /// + /// - It is the `logo` or command key (⌘) on macOS + /// - It is the `control` key on other platforms + /// + /// [`ModifiersState`]: struct.ModifiersState.html + pub fn is_command_pressed(self) -> bool { + #[cfg(target_os = "macos")] + let is_pressed = self.logo; + + #[cfg(not(target_os = "macos"))] + let is_pressed = self.control; + + is_pressed + } + /// Returns true if the current [`ModifiersState`] has at least the same /// modifiers enabled as the given value, and false otherwise. /// diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index d149f9c6..e786eadd 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -123,7 +123,7 @@ impl Application for Example { Event::Keyboard(keyboard::Event::KeyPressed { modifiers, key_code, - }) if modifiers.control => handle_hotkey(key_code), + }) if modifiers.is_command_pressed() => handle_hotkey(key_code), _ => None, }) }