From 1db11ba69a3183924a1f4cae91031f4c5051b6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 11 Nov 2020 23:54:59 +0100 Subject: [PATCH 01/17] Introduce `event::Status` in `iced_native` --- native/src/event.rs | 23 ++++++++++++++++++++++- native/src/lib.rs | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/native/src/event.rs b/native/src/event.rs index 606a71d6..160b5ce7 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -1,3 +1,4 @@ +//! Handle events of a user interface. use crate::{keyboard, mouse, window}; /// A user interface event. @@ -6,7 +7,7 @@ use crate::{keyboard, mouse, window}; /// additional events, feel free to [open an issue] and share your use case!_ /// /// [open an issue]: https://github.com/hecrj/iced/issues -#[derive(PartialEq, Clone, Debug)] +#[derive(Debug, Clone, PartialEq)] pub enum Event { /// A keyboard event Keyboard(keyboard::Event), @@ -17,3 +18,23 @@ pub enum Event { /// A window event Window(window::Event), } + +/// The status of an [`Event`] after being processed by a [`UserInterface`]. +/// +/// [`Event`]: enum.Event.html +/// [`UserInterface`]: ../struct.UserInterface.html +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`Event`] was _NOT_ handled by any widget in the [`UserInterface`]. + /// + /// [`Event`]: enum.Event.html + /// [`UserInterface`]: ../struct.UserInterface.html + Ignored, + + /// The [`Event`] was handled and processed by a widget in the + /// [`UserInterface`]. + /// + /// [`Event`]: enum.Event.html + /// [`UserInterface`]: ../struct.UserInterface.html + Captured, +} diff --git a/native/src/lib.rs b/native/src/lib.rs index 067e3c0a..d1252eaf 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -35,6 +35,7 @@ #![deny(unused_results)] #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] +pub mod event; pub mod keyboard; pub mod layout; pub mod mouse; @@ -47,7 +48,6 @@ pub mod window; mod clipboard; mod element; -mod event; mod hasher; mod runtime; mod user_interface; From 3f968b8c876b7c2351232856887fb9c3e3db3130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 00:09:52 +0100 Subject: [PATCH 02/17] Make `Widget::on_event` return an `event::Status` --- graphics/src/widget/canvas.rs | 10 +++++++--- graphics/src/widget/canvas/event.rs | 2 ++ native/src/element.rs | 15 +++++++++------ native/src/event.rs | 9 +++------ native/src/overlay/menu.rs | 18 +++++++++++++----- native/src/user_interface.rs | 2 +- native/src/widget.rs | 10 ++++++---- native/src/widget/button.rs | 10 +++++++--- native/src/widget/checkbox.rs | 14 ++++++++++---- native/src/widget/column.rs | 12 +++++++----- native/src/widget/container.rs | 8 +++++--- native/src/widget/pane_grid.rs | 15 ++++++++++++--- native/src/widget/pane_grid/content.rs | 2 +- native/src/widget/pane_grid/title_bar.rs | 2 +- native/src/widget/pick_list.rs | 16 +++++++++++----- native/src/widget/radio.rs | 14 ++++++++++---- native/src/widget/row.rs | 12 +++++++----- native/src/widget/scrollable.rs | 11 ++++++++--- native/src/widget/slider.rs | 10 +++++++--- native/src/widget/text_input.rs | 14 +++++++++----- 20 files changed, 136 insertions(+), 70 deletions(-) diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index 73778d16..4478bca8 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -7,9 +7,11 @@ //! [`Canvas`]: struct.Canvas.html //! [`Frame`]: struct.Frame.html use crate::{Backend, Defaults, Primitive, Renderer}; +use iced_native::layout; +use iced_native::mouse; use iced_native::{ - layout, mouse, Clipboard, Element, Hasher, Layout, Length, Point, - Rectangle, Size, Vector, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, + Widget, }; use std::hash::Hash; use std::marker::PhantomData; @@ -166,7 +168,7 @@ where messages: &mut Vec, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { let bounds = layout.bounds(); let canvas_event = match event { @@ -188,6 +190,8 @@ where messages.push(message); } } + + event::Status::Ignored } fn draw( diff --git a/graphics/src/widget/canvas/event.rs b/graphics/src/widget/canvas/event.rs index 0e66f0ff..67a5d3bc 100644 --- a/graphics/src/widget/canvas/event.rs +++ b/graphics/src/widget/canvas/event.rs @@ -1,6 +1,8 @@ use iced_native::keyboard; use iced_native::mouse; +pub use iced_native::event::Status; + /// A [`Canvas`] event. /// /// [`Canvas`]: struct.Event.html diff --git a/native/src/element.rs b/native/src/element.rs index 10e1b5fb..9703a7db 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -1,7 +1,8 @@ +use crate::event::{self, Event}; use crate::layout; use crate::overlay; use crate::{ - Clipboard, Color, Event, Hasher, Layout, Length, Point, Rectangle, Widget, + Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget, }; /// A generic [`Widget`]. @@ -240,7 +241,7 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { self.widget.on_event( event, layout, @@ -248,7 +249,7 @@ where messages, renderer, clipboard, - ); + ) } /// Draws the [`Element`] and its children using the given [`Layout`]. @@ -335,10 +336,10 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { let mut original_messages = Vec::new(); - self.widget.on_event( + let status = self.widget.on_event( event, layout, cursor_position, @@ -350,6 +351,8 @@ where original_messages .drain(..) .for_each(|message| messages.push((self.mapper)(message))); + + status } fn draw( @@ -423,7 +426,7 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { self.element.widget.on_event( event, layout, diff --git a/native/src/event.rs b/native/src/event.rs index 160b5ce7..91f33bd5 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -19,22 +19,19 @@ pub enum Event { Window(window::Event), } -/// The status of an [`Event`] after being processed by a [`UserInterface`]. +/// The status of an [`Event`] after being processed. /// /// [`Event`]: enum.Event.html /// [`UserInterface`]: ../struct.UserInterface.html #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { - /// The [`Event`] was _NOT_ handled by any widget in the [`UserInterface`]. + /// The [`Event`] was _NOT_ handled by any widget. /// /// [`Event`]: enum.Event.html - /// [`UserInterface`]: ../struct.UserInterface.html Ignored, - /// The [`Event`] was handled and processed by a widget in the - /// [`UserInterface`]. + /// The [`Event`] was handled and processed by a widget. /// /// [`Event`]: enum.Event.html - /// [`UserInterface`]: ../struct.UserInterface.html Captured, } diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 4b392a8e..3577629b 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -1,8 +1,14 @@ //! Build and show dropdown menus. +use crate::container; +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::overlay; +use crate::scrollable; +use crate::text; use crate::{ - container, layout, mouse, overlay, scrollable, text, Clipboard, Container, - Element, Event, Hasher, Layout, Length, Point, Rectangle, Scrollable, Size, - Vector, Widget, + Clipboard, Container, Element, Hasher, Layout, Length, Point, Rectangle, + Scrollable, Size, Vector, Widget, }; /// A list of selectable options. @@ -236,7 +242,7 @@ where renderer: &Renderer, clipboard: Option<&dyn Clipboard>, ) { - self.container.on_event( + let _ = self.container.on_event( event.clone(), layout, cursor_position, @@ -336,7 +342,7 @@ where _messages: &mut Vec, renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let bounds = layout.bounds(); @@ -364,6 +370,8 @@ where } _ => {} } + + event::Status::Ignored } fn draw( diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 504dbe0f..2ac4db0f 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -242,7 +242,7 @@ where }; for event in events { - self.root.widget.on_event( + let _ = self.root.widget.on_event( event.clone(), Layout::new(&self.base.layout), base_cursor, diff --git a/native/src/widget.rs b/native/src/widget.rs index 8687ce6f..d3ffe9c2 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -73,9 +73,10 @@ pub use text::Text; #[doc(no_inline)] pub use text_input::TextInput; -use crate::{ - layout, overlay, Clipboard, Event, Hasher, Layout, Length, Point, Rectangle, -}; +use crate::event::{self, Event}; +use crate::layout; +use crate::overlay; +use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle}; /// A component that displays information and allows interaction. /// @@ -182,7 +183,8 @@ where _messages: &mut Vec, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { + event::Status::Ignored } /// Returns the overlay of the [`Element`], if there is any. diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 995ba7bc..4a2d82e9 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -4,9 +4,11 @@ //! //! [`Button`]: struct.Button.html //! [`State`]: struct.State.html +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; use crate::{ - layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, }; use std::hash::Hash; @@ -184,7 +186,7 @@ where messages: &mut Vec, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if self.on_press.is_some() { @@ -209,6 +211,8 @@ where } _ => {} } + + event::Status::Ignored } fn draw( diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index e389427e..16a6a648 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -1,10 +1,14 @@ //! Show toggle controls using checkboxes. use std::hash::Hash; +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::row; +use crate::text; use crate::{ - layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher, - HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, - VerticalAlignment, Widget, + Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length, + Point, Rectangle, Row, Text, VerticalAlignment, Widget, }; /// A box that can be checked. @@ -161,7 +165,7 @@ where messages: &mut Vec, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let mouse_over = layout.bounds().contains(cursor_position); @@ -172,6 +176,8 @@ where } _ => {} } + + event::Status::Ignored } fn draw( diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index e874ad42..425bd33f 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -1,11 +1,11 @@ //! Distribute content vertically. use std::hash::Hash; +use crate::event::{self, Event}; use crate::layout; use crate::overlay; use crate::{ - Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, - Widget, + Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, }; use std::u32; @@ -162,19 +162,21 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { self.children.iter_mut().zip(layout.children()).for_each( |(child, layout)| { - child.widget.on_event( + let _ = child.widget.on_event( event.clone(), layout, cursor_position, messages, renderer, clipboard, - ) + ); }, ); + + event::Status::Ignored } fn draw( diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 5b04d699..419060db 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -1,9 +1,11 @@ //! Decorate content and apply alignment. use std::hash::Hash; +use crate::event::{self, Event}; +use crate::layout; +use crate::overlay; use crate::{ - layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length, - Point, Rectangle, Widget, + Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, }; use std::u32; @@ -174,7 +176,7 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { self.content.widget.on_event( event, layout.children().next().unwrap(), diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 9d36bae6..aeeee299 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -28,9 +28,16 @@ pub use split::Split; pub use state::{Focus, State}; pub use title_bar::TitleBar; +use crate::container; +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::overlay; +use crate::row; +use crate::text; use crate::{ - container, layout, mouse, overlay, row, text, Clipboard, Element, Event, - Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, + Widget, }; /// A collection of panes distributed using either vertical or horizontal splits @@ -386,7 +393,7 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse_event) => match mouse_event { mouse::Event::ButtonPressed(mouse::Button::Left) => { @@ -484,6 +491,8 @@ where ); } } + + event::Status::Ignored } fn draw( diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 5bfbb9ed..dffc3a73 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -172,7 +172,7 @@ where layout }; - self.body.on_event( + let _ = self.body.on_event( event, body_layout, cursor_position, diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 9dfb9ae4..eed7590d 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -254,7 +254,7 @@ where let _ = children.next(); let controls_layout = children.next().unwrap(); - controls.on_event( + let _ = controls.on_event( event, controls_layout, cursor_position, diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index e086e367..ee113e5e 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -1,9 +1,13 @@ //! Display a dropdown list of selectable values. +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::overlay; +use crate::overlay::menu::{self, Menu}; +use crate::scrollable; +use crate::text; use crate::{ - layout, mouse, overlay, - overlay::menu::{self, Menu}, - scrollable, text, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; use std::borrow::Cow; @@ -223,7 +227,7 @@ where messages: &mut Vec, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if *self.is_open { @@ -248,6 +252,8 @@ where } _ => {} } + + event::Status::Ignored } fn draw( diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 06d3f846..6e74b404 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -1,8 +1,12 @@ //! Create choices using radio buttons. +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::row; +use crate::text; use crate::{ - layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher, - HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, - VerticalAlignment, Widget, + Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length, + Point, Rectangle, Row, Text, VerticalAlignment, Widget, }; use std::hash::Hash; @@ -166,7 +170,7 @@ where messages: &mut Vec, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if layout.bounds().contains(cursor_position) { @@ -175,6 +179,8 @@ where } _ => {} } + + event::Status::Ignored } fn draw( diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index bc8a3df1..7d7595f7 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -1,9 +1,9 @@ //! Distribute content horizontally. +use crate::event::{self, Event}; use crate::layout; use crate::overlay; use crate::{ - Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, - Widget, + Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget, }; use std::hash::Hash; @@ -162,19 +162,21 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { self.children.iter_mut().zip(layout.children()).for_each( |(child, layout)| { - child.widget.on_event( + let _ = child.widget.on_event( event.clone(), layout, cursor_position, messages, renderer, clipboard, - ) + ); }, ); + + event::Status::Ignored } fn draw( diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 60ec2d7d..19c3e582 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,7 +1,12 @@ //! Navigate an endless amount of content with a scrollbar. +use crate::column; +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; +use crate::overlay; use crate::{ - column, layout, mouse, overlay, Align, Clipboard, Column, Element, Event, - Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, + Align, Clipboard, Column, Element, Hasher, Layout, Length, Point, + Rectangle, Size, Vector, Widget, }; use std::{f32, hash::Hash, u32}; @@ -184,7 +189,7 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index d6e366aa..51edd56d 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -4,9 +4,11 @@ //! //! [`Slider`]: struct.Slider.html //! [`State`]: struct.State.html +use crate::event::{self, Event}; +use crate::layout; +use crate::mouse; use crate::{ - layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; use std::{hash::Hash, ops::RangeInclusive}; @@ -202,7 +204,7 @@ where messages: &mut Vec, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { let mut change = || { let bounds = layout.bounds(); if cursor_position.x <= bounds.x { @@ -251,6 +253,8 @@ where }, _ => {} } + + event::Status::Ignored } fn draw( diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 470e92ed..436f01ab 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -14,11 +14,13 @@ pub use value::Value; use editor::Editor; +use crate::event::{self, Event}; +use crate::keyboard; +use crate::layout; +use crate::mouse::{self, click}; +use crate::text; use crate::{ - keyboard, layout, - mouse::{self, click}, - text, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, - Size, Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; use std::u32; @@ -218,7 +220,7 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let is_clicked = layout.bounds().contains(cursor_position); @@ -489,6 +491,8 @@ where }, _ => {} } + + event::Status::Ignored } fn draw( From a44cd072120cc059e8dc4633b33d902817f89834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 00:19:12 +0100 Subject: [PATCH 03/17] Implement event capturing for `Button` --- native/src/event.rs | 2 +- native/src/widget/button.rs | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/native/src/event.rs b/native/src/event.rs index 91f33bd5..689c8f5d 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -25,7 +25,7 @@ pub enum Event { /// [`UserInterface`]: ../struct.UserInterface.html #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { - /// The [`Event`] was _NOT_ handled by any widget. + /// The [`Event`] was **NOT** handled by any widget. /// /// [`Event`]: enum.Event.html Ignored, diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 4a2d82e9..466f6ac5 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -192,20 +192,25 @@ where if self.on_press.is_some() { let bounds = layout.bounds(); - self.state.is_pressed = bounds.contains(cursor_position); + if bounds.contains(cursor_position) { + self.state.is_pressed = true; + + return event::Status::Captured; + } } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { if let Some(on_press) = self.on_press.clone() { let bounds = layout.bounds(); - let is_clicked = self.state.is_pressed - && bounds.contains(cursor_position); + if self.state.is_pressed { + self.state.is_pressed = false; - self.state.is_pressed = false; + if bounds.contains(cursor_position) { + messages.push(on_press); + } - if is_clicked { - messages.push(on_press); + return event::Status::Captured; } } } From 04468a7147c38cd363cb545de0d9a9881ce071dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 00:20:09 +0100 Subject: [PATCH 04/17] Implement event capturing for `Checkbox` --- native/src/widget/checkbox.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 16a6a648..42e52aef 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -172,6 +172,8 @@ where if mouse_over { messages.push((self.on_toggle)(!self.is_checked)); + + return event::Status::Captured; } } _ => {} From 3bcee62beb36d9e186d8716c7660433fac071ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 00:30:06 +0100 Subject: [PATCH 05/17] Implement event capturing for `Column` --- native/src/event.rs | 21 +++++++++++++++++++++ native/src/widget/column.rs | 16 ++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/native/src/event.rs b/native/src/event.rs index 689c8f5d..9c079151 100644 --- a/native/src/event.rs +++ b/native/src/event.rs @@ -35,3 +35,24 @@ pub enum Status { /// [`Event`]: enum.Event.html Captured, } + +impl Status { + /// Merges two [`Status`] into one. + /// + /// `Captured` takes precedence over `Ignored`: + /// + /// ``` + /// use iced_native::event::Status; + /// + /// assert_eq!(Status::Ignored.merge(Status::Ignored), Status::Ignored); + /// assert_eq!(Status::Ignored.merge(Status::Captured), Status::Captured); + /// assert_eq!(Status::Captured.merge(Status::Ignored), Status::Captured); + /// assert_eq!(Status::Captured.merge(Status::Captured), Status::Captured); + /// ``` + pub fn merge(self, b: Self) -> Self { + match self { + Status::Ignored => b, + Status::Captured => Status::Captured, + } + } +} diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 425bd33f..42a9e734 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -163,20 +163,20 @@ where renderer: &Renderer, clipboard: Option<&dyn Clipboard>, ) -> event::Status { - self.children.iter_mut().zip(layout.children()).for_each( - |(child, layout)| { - let _ = child.widget.on_event( + self.children + .iter_mut() + .zip(layout.children()) + .map(|(child, layout)| { + child.widget.on_event( event.clone(), layout, cursor_position, messages, renderer, clipboard, - ); - }, - ); - - event::Status::Ignored + ) + }) + .fold(event::Status::Ignored, event::Status::merge) } fn draw( From 31c509b2063907ad4907f248f82724c6fd641032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 00:40:00 +0100 Subject: [PATCH 06/17] Implement event capturing for `PaneGrid` --- native/src/widget/pane_grid.rs | 49 +++++++++++++++--------- native/src/widget/pane_grid/content.rs | 13 +++++-- native/src/widget/pane_grid/title_bar.rs | 13 ++++--- 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index aeeee299..43e752cb 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -249,7 +249,7 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec, - ) { + ) -> event::Status { if let Some((_, on_resize)) = &self.on_resize { if let Some((split, _)) = self.state.picked_split() { let bounds = layout.bounds(); @@ -276,9 +276,13 @@ where }; messages.push(on_resize(ResizeEvent { split, ratio })); + + return event::Status::Captured; } } } + + event::Status::Ignored } } @@ -394,12 +398,16 @@ where renderer: &Renderer, clipboard: Option<&dyn Clipboard>, ) -> event::Status { + let mut event_status = event::Status::Ignored; + match event { Event::Mouse(mouse_event) => match mouse_event { mouse::Event::ButtonPressed(mouse::Button::Left) => { let bounds = layout.bounds(); if bounds.contains(cursor_position) { + event_status = event::Status::Captured; + match self.on_resize { Some((leeway, _)) => { let relative_cursor = Point::new( @@ -463,12 +471,17 @@ where } self.state.idle(); + + event_status = event::Status::Captured; } else if self.state.picked_split().is_some() { self.state.idle(); + + event_status = event::Status::Captured; } } mouse::Event::CursorMoved { .. } => { - self.trigger_resize(layout, cursor_position, messages); + event_status = + self.trigger_resize(layout, cursor_position, messages); } _ => {} }, @@ -476,23 +489,23 @@ where } if self.state.picked_pane().is_none() { - { - self.elements.iter_mut().zip(layout.children()).for_each( - |((_, pane), layout)| { - pane.on_event( - event.clone(), - layout, - cursor_position, - messages, - renderer, - clipboard, - ) - }, - ); - } + self.elements + .iter_mut() + .zip(layout.children()) + .map(|((_, pane), layout)| { + pane.on_event( + event.clone(), + layout, + cursor_position, + messages, + renderer, + clipboard, + ) + }) + .fold(event_status, event::Status::merge) + } else { + event::Status::Captured } - - event::Status::Ignored } fn draw( diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index dffc3a73..2dac7060 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -1,8 +1,9 @@ use crate::container; +use crate::event::{self, Event}; use crate::layout; use crate::overlay; use crate::pane_grid::{self, TitleBar}; -use crate::{Clipboard, Element, Event, Hasher, Layout, Point, Size}; +use crate::{Clipboard, Element, Hasher, Layout, Point, Size}; /// The content of a [`Pane`]. /// @@ -154,11 +155,13 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { + let mut event_status = event::Status::Ignored; + let body_layout = if let Some(title_bar) = &mut self.title_bar { let mut children = layout.children(); - title_bar.on_event( + event_status = title_bar.on_event( event.clone(), children.next().unwrap(), cursor_position, @@ -172,7 +175,7 @@ where layout }; - let _ = self.body.on_event( + let body_status = self.body.on_event( event, body_layout, cursor_position, @@ -180,6 +183,8 @@ where renderer, clipboard, ); + + event_status.merge(body_status) } pub(crate) fn hash_layout(&self, state: &mut Hasher) { diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index eed7590d..f8ff43eb 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -1,8 +1,7 @@ +use crate::event::{self, Event}; use crate::layout; use crate::pane_grid; -use crate::{ - Clipboard, Element, Event, Hasher, Layout, Point, Rectangle, Size, -}; +use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size}; /// The title bar of a [`Pane`]. /// @@ -245,7 +244,7 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { if let Some(controls) = &mut self.controls { let mut children = layout.children(); let padded = children.next().unwrap(); @@ -254,14 +253,16 @@ where let _ = children.next(); let controls_layout = children.next().unwrap(); - let _ = controls.on_event( + controls.on_event( event, controls_layout, cursor_position, messages, renderer, clipboard, - ); + ) + } else { + event::Status::Ignored } } } From 7ff95f3a884fc5f7ca7417408bd6d4ed47c4e9cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 00:47:21 +0100 Subject: [PATCH 07/17] Implement event capturing for `PickList` --- native/src/widget/pick_list.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index ee113e5e..113197f7 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -230,10 +230,12 @@ where ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - if *self.is_open { + let event_status = if *self.is_open { // TODO: Encode cursor availability in the type system *self.is_open = cursor_position.x < 0.0 || cursor_position.y < 0.0; + + event::Status::Captured } else if layout.bounds().contains(cursor_position) { let selected = self.selected.as_ref(); @@ -242,18 +244,24 @@ where .options .iter() .position(|option| Some(option) == selected); - } + + event::Status::Captured + } else { + event::Status::Ignored + }; if let Some(last_selection) = self.last_selection.take() { messages.push((self.on_selected)(last_selection)); *self.is_open = false; + + event::Status::Captured + } else { + event_status } } - _ => {} + _ => event::Status::Ignored, } - - event::Status::Ignored } fn draw( From 18172f80c96f48cc915b4c8281b9157acaa74b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 00:47:58 +0100 Subject: [PATCH 08/17] Implement event capturing for `Radio` --- native/src/widget/radio.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 6e74b404..781fffb1 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -175,6 +175,8 @@ where Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if layout.bounds().contains(cursor_position) { messages.push(self.on_click.clone()); + + return event::Status::Captured; } } _ => {} From 451bf8dc841b8f60f2a5664b0e8000ad40dfe114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 00:48:40 +0100 Subject: [PATCH 09/17] Implement event capturing for `Row` --- native/src/widget/row.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 7d7595f7..6b09d0c8 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -163,20 +163,20 @@ where renderer: &Renderer, clipboard: Option<&dyn Clipboard>, ) -> event::Status { - self.children.iter_mut().zip(layout.children()).for_each( - |(child, layout)| { - let _ = child.widget.on_event( + self.children + .iter_mut() + .zip(layout.children()) + .map(|(child, layout)| { + child.widget.on_event( event.clone(), layout, cursor_position, messages, renderer, clipboard, - ); - }, - ); - - event::Status::Ignored + ) + }) + .fold(event::Status::Ignored, event::Status::merge) } fn draw( From fd275a2fee4c7bbea0dbefc0dc68b340cc003ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 00:53:39 +0100 Subject: [PATCH 10/17] Implement event capturing for `Scrollable` --- native/src/widget/scrollable.rs | 95 +++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 19c3e582..92671ddd 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -196,24 +196,6 @@ where let content = layout.children().next().unwrap(); let content_bounds = content.bounds(); - // TODO: Event capture. Nested scrollables should capture scroll events. - if is_mouse_over { - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - match delta { - mouse::ScrollDelta::Lines { y, .. } => { - // TODO: Configurable speed (?) - self.state.scroll(y * 60.0, bounds, content_bounds); - } - mouse::ScrollDelta::Pixels { y, .. } => { - self.state.scroll(y, bounds, content_bounds); - } - } - } - _ => {} - } - } - let offset = self.state.offset(bounds, content_bounds); let scrollbar = renderer.scrollbar( bounds, @@ -228,12 +210,62 @@ where .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) .unwrap_or(false); + let event_status = { + let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { + Point::new( + cursor_position.x, + cursor_position.y + + self.state.offset(bounds, content_bounds) as f32, + ) + } else { + // TODO: Make `cursor_position` an `Option` so we can encode + // cursor availability. + // This will probably happen naturally once we add multi-window + // support. + Point::new(cursor_position.x, -1.0) + }; + + self.content.on_event( + event.clone(), + content, + cursor_position, + messages, + renderer, + clipboard, + ) + }; + + if let event::Status::Captured = event_status { + return event::Status::Captured; + } + + if is_mouse_over { + match event { + Event::Mouse(mouse::Event::WheelScrolled { delta }) => { + match delta { + mouse::ScrollDelta::Lines { y, .. } => { + // TODO: Configurable speed (?) + self.state.scroll(y * 60.0, bounds, content_bounds); + } + mouse::ScrollDelta::Pixels { y, .. } => { + self.state.scroll(y, bounds, content_bounds); + } + } + + return event::Status::Captured; + } + _ => {} + } + } + if self.state.is_scroller_grabbed() { match event { Event::Mouse(mouse::Event::ButtonReleased( mouse::Button::Left, )) => { self.state.scroller_grabbed_at = None; + + return event::Status::Captured; } Event::Mouse(mouse::Event::CursorMoved { .. }) => { if let (Some(scrollbar), Some(scroller_grabbed_at)) = @@ -247,6 +279,8 @@ where bounds, content_bounds, ); + + return event::Status::Captured; } } _ => {} @@ -271,6 +305,8 @@ where self.state.scroller_grabbed_at = Some(scroller_grabbed_at); + + return event::Status::Captured; } } } @@ -278,28 +314,7 @@ where } } - let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { - Point::new( - cursor_position.x, - cursor_position.y - + self.state.offset(bounds, content_bounds) as f32, - ) - } else { - // TODO: Make `cursor_position` an `Option` so we can encode - // cursor availability. - // This will probably happen naturally once we add multi-window - // support. - Point::new(cursor_position.x, -1.0) - }; - - self.content.on_event( - event, - content, - cursor_position, - messages, - renderer, - clipboard, - ) + event::Status::Ignored } fn draw( From c361fe48c7a92662046dd13cb08cb2157e0577be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 00:56:50 +0100 Subject: [PATCH 11/17] Implement event capturing for `Slider` --- native/src/widget/slider.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 51edd56d..4e38fb86 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -234,6 +234,8 @@ where if layout.bounds().contains(cursor_position) { change(); self.state.is_dragging = true; + + return event::Status::Captured; } } mouse::Event::ButtonReleased(mouse::Button::Left) => { @@ -242,11 +244,15 @@ where messages.push(on_release); } self.state.is_dragging = false; + + return event::Status::Captured; } } mouse::Event::CursorMoved { .. } => { if self.state.is_dragging { change(); + + return event::Status::Captured; } } _ => {} From bf6c65b5ad24595bbb12570e38118321f4b572ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 01:11:09 +0100 Subject: [PATCH 12/17] Implement event capturing for `TextInput` --- native/src/widget/text_input.rs | 320 ++++++++++++++++++-------------- 1 file changed, 179 insertions(+), 141 deletions(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 436f01ab..cf95e7e8 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -225,6 +225,9 @@ where Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let is_clicked = layout.bounds().contains(cursor_position); + self.state.is_dragging = is_clicked; + self.state.is_focused = is_clicked; + if is_clicked { let text_layout = layout.children().next().unwrap(); let target = cursor_position.x - text_layout.bounds().x; @@ -282,10 +285,9 @@ where } self.state.last_click = Some(click); - } - self.state.is_dragging = is_clicked; - self.state.is_focused = is_clicked; + return event::Status::Captured; + } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { self.state.is_dragging = false; @@ -316,6 +318,8 @@ where position, ); } + + return event::Status::Captured; } } Event::Keyboard(keyboard::Event::CharacterReceived(c)) @@ -330,165 +334,199 @@ where let message = (self.on_change)(editor.contents()); messages.push(message); + + return event::Status::Captured; } Event::Keyboard(keyboard::Event::KeyPressed { key_code, modifiers, - }) if self.state.is_focused => match key_code { - keyboard::KeyCode::Enter => { - if let Some(on_submit) = self.on_submit.clone() { - messages.push(on_submit); - } - } - keyboard::KeyCode::Backspace => { - if platform::is_jump_modifier_pressed(modifiers) - && self.state.cursor.selection(&self.value).is_none() - { - if self.is_secure { - let cursor_pos = self.state.cursor.end(&self.value); - self.state.cursor.select_range(0, cursor_pos); - } else { - self.state.cursor.select_left_by_words(&self.value); + }) if self.state.is_focused => { + match key_code { + keyboard::KeyCode::Enter => { + if let Some(on_submit) = self.on_submit.clone() { + messages.push(on_submit); } } - - let mut editor = - Editor::new(&mut self.value, &mut self.state.cursor); - - editor.backspace(); - - let message = (self.on_change)(editor.contents()); - messages.push(message); - } - keyboard::KeyCode::Delete => { - if platform::is_jump_modifier_pressed(modifiers) - && self.state.cursor.selection(&self.value).is_none() - { - if self.is_secure { - let cursor_pos = self.state.cursor.end(&self.value); - self.state + keyboard::KeyCode::Backspace => { + if platform::is_jump_modifier_pressed(modifiers) + && self + .state .cursor - .select_range(cursor_pos, self.value.len()); - } else { - self.state - .cursor - .select_right_by_words(&self.value); + .selection(&self.value) + .is_none() + { + if self.is_secure { + let cursor_pos = + self.state.cursor.end(&self.value); + self.state.cursor.select_range(0, cursor_pos); + } else { + self.state + .cursor + .select_left_by_words(&self.value); + } } - } - let mut editor = - Editor::new(&mut self.value, &mut self.state.cursor); - - editor.delete(); - - let message = (self.on_change)(editor.contents()); - messages.push(message); - } - keyboard::KeyCode::Left => { - if platform::is_jump_modifier_pressed(modifiers) - && !self.is_secure - { - if modifiers.shift { - self.state.cursor.select_left_by_words(&self.value); - } else { - self.state.cursor.move_left_by_words(&self.value); - } - } else if modifiers.shift { - self.state.cursor.select_left(&self.value) - } else { - self.state.cursor.move_left(&self.value); - } - } - keyboard::KeyCode::Right => { - if platform::is_jump_modifier_pressed(modifiers) - && !self.is_secure - { - if modifiers.shift { - self.state - .cursor - .select_right_by_words(&self.value); - } else { - self.state.cursor.move_right_by_words(&self.value); - } - } else if modifiers.shift { - self.state.cursor.select_right(&self.value) - } else { - self.state.cursor.move_right(&self.value); - } - } - keyboard::KeyCode::Home => { - if modifiers.shift { - self.state.cursor.select_range( - self.state.cursor.start(&self.value), - 0, + let mut editor = Editor::new( + &mut self.value, + &mut self.state.cursor, ); - } else { - self.state.cursor.move_to(0); + + editor.backspace(); + + let message = (self.on_change)(editor.contents()); + messages.push(message); } - } - keyboard::KeyCode::End => { - if modifiers.shift { - self.state.cursor.select_range( - self.state.cursor.start(&self.value), - self.value.len(), + keyboard::KeyCode::Delete => { + if platform::is_jump_modifier_pressed(modifiers) + && self + .state + .cursor + .selection(&self.value) + .is_none() + { + if self.is_secure { + let cursor_pos = + self.state.cursor.end(&self.value); + self.state + .cursor + .select_range(cursor_pos, self.value.len()); + } else { + self.state + .cursor + .select_right_by_words(&self.value); + } + } + + let mut editor = Editor::new( + &mut self.value, + &mut self.state.cursor, ); - } else { - self.state.cursor.move_to(self.value.len()); + + editor.delete(); + + let message = (self.on_change)(editor.contents()); + messages.push(message); } - } - keyboard::KeyCode::V => { - if platform::is_copy_paste_modifier_pressed(modifiers) { - if let Some(clipboard) = clipboard { - let content = match self.state.is_pasting.take() { - Some(content) => content, - None => { - let content: String = clipboard - .content() - .unwrap_or(String::new()) - .chars() - .filter(|c| !c.is_control()) - .collect(); - - Value::new(&content) - } - }; - - let mut editor = Editor::new( - &mut self.value, - &mut self.state.cursor, + keyboard::KeyCode::Left => { + if platform::is_jump_modifier_pressed(modifiers) + && !self.is_secure + { + if modifiers.shift { + self.state + .cursor + .select_left_by_words(&self.value); + } else { + self.state + .cursor + .move_left_by_words(&self.value); + } + } else if modifiers.shift { + self.state.cursor.select_left(&self.value) + } else { + self.state.cursor.move_left(&self.value); + } + } + keyboard::KeyCode::Right => { + if platform::is_jump_modifier_pressed(modifiers) + && !self.is_secure + { + if modifiers.shift { + self.state + .cursor + .select_right_by_words(&self.value); + } else { + self.state + .cursor + .move_right_by_words(&self.value); + } + } else if modifiers.shift { + self.state.cursor.select_right(&self.value) + } else { + self.state.cursor.move_right(&self.value); + } + } + keyboard::KeyCode::Home => { + if modifiers.shift { + self.state.cursor.select_range( + self.state.cursor.start(&self.value), + 0, ); - - editor.paste(content.clone()); - - let message = (self.on_change)(editor.contents()); - messages.push(message); - - self.state.is_pasting = Some(content); + } else { + self.state.cursor.move_to(0); } - } else { + } + keyboard::KeyCode::End => { + if modifiers.shift { + self.state.cursor.select_range( + self.state.cursor.start(&self.value), + self.value.len(), + ); + } else { + self.state.cursor.move_to(self.value.len()); + } + } + keyboard::KeyCode::V => { + if platform::is_copy_paste_modifier_pressed(modifiers) { + if let Some(clipboard) = clipboard { + let content = match self.state.is_pasting.take() + { + Some(content) => content, + None => { + let content: String = clipboard + .content() + .unwrap_or(String::new()) + .chars() + .filter(|c| !c.is_control()) + .collect(); + + Value::new(&content) + } + }; + + let mut editor = Editor::new( + &mut self.value, + &mut self.state.cursor, + ); + + editor.paste(content.clone()); + + let message = + (self.on_change)(editor.contents()); + messages.push(message); + + self.state.is_pasting = Some(content); + } + } else { + self.state.is_pasting = None; + } + } + keyboard::KeyCode::A => { + if platform::is_copy_paste_modifier_pressed(modifiers) { + self.state.cursor.select_all(&self.value); + } + } + keyboard::KeyCode::Escape => { + self.state.is_focused = false; + self.state.is_dragging = false; self.state.is_pasting = None; } + _ => {} } - keyboard::KeyCode::A => { - if platform::is_copy_paste_modifier_pressed(modifiers) { - self.state.cursor.select_all(&self.value); - } - } - keyboard::KeyCode::Escape => { - self.state.is_focused = false; - self.state.is_dragging = false; - self.state.is_pasting = None; - } - _ => {} - }, + + return event::Status::Captured; + } Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. - }) => match key_code { - keyboard::KeyCode::V => { - self.state.is_pasting = None; + }) if self.state.is_focused => { + match key_code { + keyboard::KeyCode::V => { + self.state.is_pasting = None; + } + _ => {} } - _ => {} - }, + + return event::Status::Captured; + } _ => {} } From 3aca1771329cf7845cbc6c98e536cfb6c0e7c3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 01:24:59 +0100 Subject: [PATCH 13/17] Implement event capturing for `Canvas` --- examples/bezier_tool/src/main.rs | 71 +++++++++++++++----------- examples/game_of_life/src/main.rs | 72 ++++++++++++++++----------- graphics/src/widget/canvas.rs | 11 ++-- graphics/src/widget/canvas/event.rs | 1 + graphics/src/widget/canvas/program.rs | 9 ++-- 5 files changed, 98 insertions(+), 66 deletions(-) diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index cb5247aa..97832e01 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -69,7 +69,8 @@ impl Sandbox for Example { mod bezier { use iced::{ - canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path, Stroke}, + canvas::event::{self, Event}, + canvas::{self, Canvas, Cursor, Frame, Geometry, Path, Stroke}, mouse, Element, Length, Point, Rectangle, }; @@ -109,41 +110,51 @@ mod bezier { event: Event, bounds: Rectangle, cursor: Cursor, - ) -> Option { - let cursor_position = cursor.position_in(&bounds)?; + ) -> (event::Status, Option) { + let cursor_position = + if let Some(position) = cursor.position_in(&bounds) { + position + } else { + return (event::Status::Ignored, None); + }; match event { - Event::Mouse(mouse_event) => match mouse_event { - mouse::Event::ButtonPressed(mouse::Button::Left) => { - match self.state.pending { - None => { - self.state.pending = Some(Pending::One { - from: cursor_position, - }); - None - } - Some(Pending::One { from }) => { - self.state.pending = Some(Pending::Two { - from, - to: cursor_position, - }); + Event::Mouse(mouse_event) => { + let message = match mouse_event { + mouse::Event::ButtonPressed(mouse::Button::Left) => { + match self.state.pending { + None => { + self.state.pending = Some(Pending::One { + from: cursor_position, + }); - None - } - Some(Pending::Two { from, to }) => { - self.state.pending = None; + None + } + Some(Pending::One { from }) => { + self.state.pending = Some(Pending::Two { + from, + to: cursor_position, + }); - Some(Curve { - from, - to, - control: cursor_position, - }) + None + } + Some(Pending::Two { from, to }) => { + self.state.pending = None; + + Some(Curve { + from, + to, + control: cursor_position, + }) + } } } - } - _ => None, - }, - _ => None, + _ => None, + }; + + (event::Status::Captured, message) + } + _ => (event::Status::Ignored, None), } } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 3f087f88..e18bd6e0 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -153,9 +153,8 @@ impl Application for GameOfLife { mod grid { use crate::Preset; use iced::{ - canvas::{ - self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path, Text, - }, + canvas::event::{self, Event}, + canvas::{self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text}, mouse, Color, Element, HorizontalAlignment, Length, Point, Rectangle, Size, Vector, VerticalAlignment, }; @@ -328,12 +327,18 @@ mod grid { event: Event, bounds: Rectangle, cursor: Cursor, - ) -> Option { + ) -> (event::Status, Option) { if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event { self.interaction = Interaction::None; } - let cursor_position = cursor.position_in(&bounds)?; + let cursor_position = + if let Some(position) = cursor.position_in(&bounds) { + position + } else { + return (event::Status::Ignored, None); + }; + let cell = Cell::at(self.project(cursor_position, bounds.size())); let is_populated = self.state.contains(&cell); @@ -345,28 +350,32 @@ mod grid { match event { Event::Mouse(mouse_event) => match mouse_event { - mouse::Event::ButtonPressed(button) => match button { - mouse::Button::Left => { - self.interaction = if is_populated { - Interaction::Erasing - } else { - Interaction::Drawing - }; + mouse::Event::ButtonPressed(button) => { + let message = match button { + mouse::Button::Left => { + self.interaction = if is_populated { + Interaction::Erasing + } else { + Interaction::Drawing + }; - populate.or(unpopulate) - } - mouse::Button::Right => { - self.interaction = Interaction::Panning { - translation: self.translation, - start: cursor_position, - }; + populate.or(unpopulate) + } + mouse::Button::Right => { + self.interaction = Interaction::Panning { + translation: self.translation, + start: cursor_position, + }; - None - } - _ => None, - }, + None + } + _ => None, + }; + + (event::Status::Captured, message) + } mouse::Event::CursorMoved { .. } => { - match self.interaction { + let message = match self.interaction { Interaction::Drawing => populate, Interaction::Erasing => unpopulate, Interaction::Panning { translation, start } => { @@ -380,7 +389,14 @@ mod grid { None } _ => None, - } + }; + + let event_status = match self.interaction { + Interaction::None => event::Status::Ignored, + _ => event::Status::Captured, + }; + + (event_status, message) } mouse::Event::WheelScrolled { delta } => match delta { mouse::ScrollDelta::Lines { y, .. } @@ -413,12 +429,12 @@ mod grid { self.grid_cache.clear(); } - None + (event::Status::Captured, None) } }, - _ => None, + _ => (event::Status::Ignored, None), }, - _ => None, + _ => (event::Status::Ignored, None), } } diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index 4478bca8..ae0d87a4 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -16,11 +16,11 @@ use iced_native::{ use std::hash::Hash; use std::marker::PhantomData; +pub mod event; pub mod path; mod cache; mod cursor; -mod event; mod fill; mod frame; mod geometry; @@ -184,11 +184,14 @@ where let cursor = Cursor::from_window_position(cursor_position); if let Some(canvas_event) = canvas_event { - if let Some(message) = - self.program.update(canvas_event, bounds, cursor) - { + let (event_status, message) = + self.program.update(canvas_event, bounds, cursor); + + if let Some(message) = message { messages.push(message); } + + return event_status; } event::Status::Ignored diff --git a/graphics/src/widget/canvas/event.rs b/graphics/src/widget/canvas/event.rs index 67a5d3bc..ede2fd73 100644 --- a/graphics/src/widget/canvas/event.rs +++ b/graphics/src/widget/canvas/event.rs @@ -1,3 +1,4 @@ +//! Handle events of a canvas. use iced_native::keyboard; use iced_native::mouse; diff --git a/graphics/src/widget/canvas/program.rs b/graphics/src/widget/canvas/program.rs index 725d9d72..e8f43380 100644 --- a/graphics/src/widget/canvas/program.rs +++ b/graphics/src/widget/canvas/program.rs @@ -1,4 +1,5 @@ -use crate::canvas::{Cursor, Event, Geometry}; +use crate::canvas::event::{self, Event}; +use crate::canvas::{Cursor, Geometry}; use iced_native::{mouse, Rectangle}; /// The state and logic of a [`Canvas`]. @@ -27,8 +28,8 @@ pub trait Program { _event: Event, _bounds: Rectangle, _cursor: Cursor, - ) -> Option { - None + ) -> (event::Status, Option) { + (event::Status::Ignored, None) } /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. @@ -67,7 +68,7 @@ where event: Event, bounds: Rectangle, cursor: Cursor, - ) -> Option { + ) -> (event::Status, Option) { T::update(self, event, bounds, cursor) } From 6e9bd0d9d1dc9a98645d427d62b8413273dc6efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 01:29:11 +0100 Subject: [PATCH 14/17] Make `Overlay::on_event` return `event::Status` --- native/src/overlay.rs | 7 +++++-- native/src/overlay/element.rs | 12 ++++++++---- native/src/overlay/menu.rs | 6 +++--- native/src/user_interface.rs | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 7c3bec32..56d055d3 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -6,7 +6,9 @@ pub mod menu; pub use element::Element; pub use menu::Menu; -use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size}; +use crate::event::{self, Event}; +use crate::layout; +use crate::{Clipboard, Hasher, Layout, Point, Size}; /// An interactive component that can be displayed on top of other widgets. pub trait Overlay @@ -79,6 +81,7 @@ where _messages: &mut Vec, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { + event::Status::Ignored } } diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index e1fd9b88..3f346695 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -1,6 +1,8 @@ pub use crate::Overlay; -use crate::{layout, Clipboard, Event, Hasher, Layout, Point, Size, Vector}; +use crate::event::{self, Event}; +use crate::layout; +use crate::{Clipboard, Hasher, Layout, Point, Size, Vector}; /// A generic [`Overlay`]. /// @@ -67,7 +69,7 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { self.overlay.on_event( event, layout, @@ -136,10 +138,10 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { + ) -> event::Status { let mut original_messages = Vec::new(); - self.content.on_event( + let event_status = self.content.on_event( event, layout, cursor_position, @@ -151,6 +153,8 @@ where original_messages .drain(..) .for_each(|message| messages.push((self.mapper)(message))); + + event_status } fn draw( diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 3577629b..d99b5940 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -241,15 +241,15 @@ where messages: &mut Vec, renderer: &Renderer, clipboard: Option<&dyn Clipboard>, - ) { - let _ = self.container.on_event( + ) -> event::Status { + self.container.on_event( event.clone(), layout, cursor_position, messages, renderer, clipboard, - ); + ) } fn draw( diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 2ac4db0f..6da46115 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -216,7 +216,7 @@ where ); for event in events { - overlay.on_event( + let _ = overlay.on_event( event.clone(), Layout::new(&layer.layout), cursor_position, From 33d80b5a0b8b5b2837c99be2a152bdeb73ca60c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 02:00:08 +0100 Subject: [PATCH 15/17] Return `event::Status` in `UserInterface::update` --- glutin/src/application.rs | 39 ++++++++------- native/src/program/state.rs | 20 +++++--- native/src/user_interface.rs | 97 +++++++++++++++++++----------------- winit/src/application.rs | 43 ++++++++-------- 4 files changed, 107 insertions(+), 92 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 68612978..bc590912 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -155,30 +155,23 @@ async fn run_instance( user_interface.draw(&mut renderer, state.cursor_position()); let mut mouse_interaction = mouse::Interaction::default(); - let mut events = Vec::new(); - let mut external_messages = Vec::new(); + let mut messages = Vec::new(); + let mut is_clean = true; debug.startup_finished(); while let Some(event) = receiver.next().await { match event { + event::Event::NewEvents(_) => { + debug.event_processing_started(); + } event::Event::MainEventsCleared => { - if events.is_empty() && external_messages.is_empty() { + debug.event_processing_finished(); + + if is_clean && messages.is_empty() { continue; } - debug.event_processing_started(); - let mut messages = user_interface.update( - &events, - state.cursor_position(), - clipboard.as_ref().map(|c| c as _), - &mut renderer, - ); - - messages.extend(external_messages.drain(..)); - events.clear(); - debug.event_processing_finished(); - if !messages.is_empty() { let cache = ManuallyDrop::into_inner(user_interface).into_cache(); @@ -188,7 +181,7 @@ async fn run_instance( &mut application, &mut runtime, &mut debug, - messages, + &mut messages, ); // Update window @@ -210,9 +203,10 @@ async fn run_instance( debug.draw_finished(); context.window().request_redraw(); + is_clean = true; } event::Event::UserEvent(message) => { - external_messages.push(message); + messages.push(message); } event::Event::RedrawRequested(_) => { debug.render_started(); @@ -283,8 +277,17 @@ async fn run_instance( state.scale_factor(), state.modifiers(), ) { - events.push(event.clone()); + let _ = user_interface.update( + event.clone(), + state.cursor_position(), + clipboard.as_ref().map(|c| c as _), + &mut renderer, + &mut messages, + ); + runtime.broadcast(event); + + is_clean = false; } } _ => {} diff --git a/native/src/program/state.rs b/native/src/program/state.rs index 95e6b60c..5557347b 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -120,15 +120,19 @@ where ); debug.event_processing_started(); - let mut messages = user_interface.update( - &self.queued_events, - cursor_position, - clipboard, - renderer, - ); - messages.extend(self.queued_messages.drain(..)); + let mut messages = Vec::new(); - self.queued_events.clear(); + for event in self.queued_events.drain(..) { + let _ = user_interface.update( + event, + cursor_position, + clipboard, + renderer, + &mut messages, + ); + } + + messages.extend(self.queued_messages.drain(..)); debug.event_processing_finished(); if messages.is_empty() { diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 6da46115..793d341c 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,6 +1,7 @@ +use crate::event::{self, Event}; use crate::layout; use crate::overlay; -use crate::{Clipboard, Element, Event, Layout, Point, Rectangle, Size}; +use crate::{Clipboard, Element, Layout, Point, Rectangle, Size}; use std::hash::Hasher; @@ -169,9 +170,10 @@ where /// /// // Initialize our event storage /// let mut events = Vec::new(); + /// let mut messages = Vec::new(); /// /// loop { - /// // Process system events... + /// // Obtain system events... /// /// let mut user_interface = UserInterface::build( /// counter.view(), @@ -180,32 +182,34 @@ where /// &mut renderer, /// ); /// - /// // Update the user interface - /// let messages = user_interface.update( - /// &events, - /// cursor_position, - /// None, - /// &renderer, - /// ); + /// for event in events.drain(..) { + /// // Update the user interface + /// let _event_status = user_interface.update( + /// event, + /// cursor_position, + /// None, + /// &renderer, + /// &mut messages + /// ); + /// } /// /// cache = user_interface.into_cache(); /// /// // Process the produced messages - /// for message in messages { + /// for message in messages.drain(..) { /// counter.update(message); /// } /// } /// ``` pub fn update( &mut self, - events: &[Event], + event: Event, cursor_position: Point, clipboard: Option<&dyn Clipboard>, renderer: &Renderer, - ) -> Vec { - let mut messages = Vec::new(); - - let base_cursor = if let Some(mut overlay) = + messages: &mut Vec, + ) -> event::Status { + let (base_cursor, overlay_status) = if let Some(mut overlay) = self.root.overlay(Layout::new(&self.base.layout)) { let layer = Self::overlay_layer( @@ -215,16 +219,14 @@ where renderer, ); - for event in events { - let _ = overlay.on_event( - event.clone(), - Layout::new(&layer.layout), - cursor_position, - &mut messages, - renderer, - clipboard, - ); - } + let event_status = overlay.on_event( + event.clone(), + Layout::new(&layer.layout), + cursor_position, + messages, + renderer, + clipboard, + ); let base_cursor = if layer.layout.bounds().contains(cursor_position) { @@ -236,23 +238,21 @@ where self.overlay = Some(layer); - base_cursor + (base_cursor, event_status) } else { - cursor_position + (cursor_position, event::Status::Ignored) }; - for event in events { - let _ = self.root.widget.on_event( - event.clone(), - Layout::new(&self.base.layout), - base_cursor, - &mut messages, - renderer, - clipboard, - ); - } + let event_status = self.root.widget.on_event( + event, + Layout::new(&self.base.layout), + base_cursor, + messages, + renderer, + clipboard, + ); - messages + event_status.merge(overlay_status) } /// Draws the [`UserInterface`] with the provided [`Renderer`]. @@ -293,9 +293,10 @@ where /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor_position = Point::default(); /// let mut events = Vec::new(); + /// let mut messages = Vec::new(); /// /// loop { - /// // Process system events... + /// // Obtain system events... /// /// let mut user_interface = UserInterface::build( /// counter.view(), @@ -304,19 +305,23 @@ where /// &mut renderer, /// ); /// - /// let messages = user_interface.update( - /// &events, - /// cursor_position, - /// None, - /// &renderer, - /// ); + /// for event in events.drain(..) { + /// // Update the user interface + /// let _event_status = user_interface.update( + /// event, + /// cursor_position, + /// None, + /// &renderer, + /// &mut messages + /// ); + /// } /// /// // Draw the user interface /// let mouse_cursor = user_interface.draw(&mut renderer, cursor_position); /// /// cache = user_interface.into_cache(); /// - /// for message in messages { + /// for message in messages.drain(..) { /// counter.update(message); /// } /// diff --git a/winit/src/application.rs b/winit/src/application.rs index c1d86471..5f51a0cb 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -241,30 +241,23 @@ async fn run_instance( user_interface.draw(&mut renderer, state.cursor_position()); let mut mouse_interaction = mouse::Interaction::default(); - let mut events = Vec::new(); - let mut external_messages = Vec::new(); + let mut messages = Vec::new(); + let mut is_clean = true; debug.startup_finished(); while let Some(event) = receiver.next().await { match event { + event::Event::NewEvents(_) => { + debug.event_processing_started(); + } event::Event::MainEventsCleared => { - if events.is_empty() && external_messages.is_empty() { + debug.event_processing_finished(); + + if is_clean && messages.is_empty() { continue; } - debug.event_processing_started(); - let mut messages = user_interface.update( - &events, - state.cursor_position(), - clipboard.as_ref().map(|c| c as _), - &mut renderer, - ); - - messages.extend(external_messages.drain(..)); - events.clear(); - debug.event_processing_finished(); - if !messages.is_empty() { let cache = ManuallyDrop::into_inner(user_interface).into_cache(); @@ -274,7 +267,7 @@ async fn run_instance( &mut application, &mut runtime, &mut debug, - messages, + &mut messages, ); // Update window @@ -295,9 +288,10 @@ async fn run_instance( debug.draw_finished(); window.request_redraw(); + is_clean = true; } event::Event::UserEvent(message) => { - external_messages.push(message); + messages.push(message); } event::Event::RedrawRequested(_) => { debug.render_started(); @@ -365,8 +359,17 @@ async fn run_instance( state.scale_factor(), state.modifiers(), ) { - events.push(event.clone()); + let _ = user_interface.update( + event.clone(), + state.cursor_position(), + clipboard.as_ref().map(|c| c as _), + &mut renderer, + &mut messages, + ); + runtime.broadcast(event); + + is_clean = false; } } _ => {} @@ -437,9 +440,9 @@ pub fn update( application: &mut A, runtime: &mut Runtime, A::Message>, debug: &mut Debug, - messages: Vec, + messages: &mut Vec, ) { - for message in messages { + for message in messages.drain(..) { debug.log_message(&message); debug.update_started(); From 69c50c851193348ed3aab746678741f3cdda9fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 02:22:22 +0100 Subject: [PATCH 16/17] Introduce `event::Status` to `Subscription` --- examples/pane_grid/src/main.rs | 20 +++++++++++++------- glutin/src/application.rs | 4 ++-- native/src/runtime.rs | 12 +++++++++--- native/src/subscription.rs | 24 ++++++++++++++++-------- native/src/subscription/events.rs | 15 ++++++++------- winit/src/application.rs | 4 ++-- 6 files changed, 50 insertions(+), 29 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index e786eadd..f986cc8d 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -3,7 +3,7 @@ use iced::{ Button, Column, Command, Container, Element, HorizontalAlignment, Length, PaneGrid, Scrollable, Settings, Subscription, Text, }; -use iced_native::{subscription, Event}; +use iced_native::{event, subscription, Event}; pub fn main() -> iced::Result { Example::run(Settings::default()) @@ -119,12 +119,18 @@ impl Application for Example { } fn subscription(&self) -> Subscription { - subscription::events_with(|event| match event { - Event::Keyboard(keyboard::Event::KeyPressed { - modifiers, - key_code, - }) if modifiers.is_command_pressed() => handle_hotkey(key_code), - _ => None, + subscription::events_with(|event, status| { + if let event::Status::Captured = status { + return None; + } + + match event { + Event::Keyboard(keyboard::Event::KeyPressed { + modifiers, + key_code, + }) if modifiers.is_command_pressed() => handle_hotkey(key_code), + _ => None, + } }) } diff --git a/glutin/src/application.rs b/glutin/src/application.rs index bc590912..663654b1 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -277,7 +277,7 @@ async fn run_instance( state.scale_factor(), state.modifiers(), ) { - let _ = user_interface.update( + let event_status = user_interface.update( event.clone(), state.cursor_position(), clipboard.as_ref().map(|c| c as _), @@ -285,7 +285,7 @@ async fn run_instance( &mut messages, ); - runtime.broadcast(event); + runtime.broadcast((event, event_status)); is_clean = false; } diff --git a/native/src/runtime.rs b/native/src/runtime.rs index 9fa031f4..bd814a0b 100644 --- a/native/src/runtime.rs +++ b/native/src/runtime.rs @@ -1,5 +1,6 @@ //! Run commands and subscriptions. -use crate::{Event, Hasher}; +use crate::event::{self, Event}; +use crate::Hasher; /// A native runtime with a generic executor and receiver of results. /// @@ -8,5 +9,10 @@ use crate::{Event, Hasher}; /// /// [`Command`]: ../struct.Command.html /// [`Subscription`]: ../struct.Subscription.html -pub type Runtime = - iced_futures::Runtime; +pub type Runtime = iced_futures::Runtime< + Hasher, + (Event, event::Status), + Executor, + Receiver, + Message, +>; diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 18750abf..3cc04188 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -1,5 +1,6 @@ //! Listen to external events in your application. -use crate::{Event, Hasher}; +use crate::event::{self, Event}; +use crate::Hasher; use iced_futures::futures::stream::BoxStream; /// A request to listen to external events. @@ -15,19 +16,21 @@ use iced_futures::futures::stream::BoxStream; /// /// [`Command`]: ../struct.Command.html /// [`Subscription`]: struct.Subscription.html -pub type Subscription = iced_futures::Subscription; +pub type Subscription = + iced_futures::Subscription; /// A stream of runtime events. /// /// It is the input of a [`Subscription`] in the native runtime. /// /// [`Subscription`]: type.Subscription.html -pub type EventStream = BoxStream<'static, Event>; +pub type EventStream = BoxStream<'static, (Event, event::Status)>; /// A native [`Subscription`] tracker. /// /// [`Subscription`]: type.Subscription.html -pub type Tracker = iced_futures::subscription::Tracker; +pub type Tracker = + iced_futures::subscription::Tracker; pub use iced_futures::subscription::Recipe; @@ -37,13 +40,18 @@ use events::Events; /// Returns a [`Subscription`] to all the runtime events. /// -/// This subscription will notify your application of any [`Event`] handled by -/// the runtime. +/// This subscription will notify your application of any [`Event`] that was +/// not captured by any widget. /// /// [`Subscription`]: type.Subscription.html /// [`Event`]: ../enum.Event.html pub fn events() -> Subscription { - Subscription::from_recipe(Events { f: Some }) + Subscription::from_recipe(Events { + f: |event, status| match status { + event::Status::Ignored => Some(event), + event::Status::Captured => None, + }, + }) } /// Returns a [`Subscription`] that filters all the runtime events with the @@ -58,7 +66,7 @@ pub fn events() -> Subscription { /// [`Subscription`]: type.Subscription.html /// [`Event`]: ../enum.Event.html pub fn events_with( - f: fn(Event) -> Option, + f: fn(Event, event::Status) -> Option, ) -> Subscription where Message: 'static + Send, diff --git a/native/src/subscription/events.rs b/native/src/subscription/events.rs index a1ae6051..f689f3af 100644 --- a/native/src/subscription/events.rs +++ b/native/src/subscription/events.rs @@ -1,16 +1,15 @@ -use crate::{ - subscription::{EventStream, Recipe}, - Event, Hasher, -}; +use crate::event::{self, Event}; +use crate::subscription::{EventStream, Recipe}; +use crate::Hasher; use iced_futures::futures::future; use iced_futures::futures::StreamExt; use iced_futures::BoxStream; pub struct Events { - pub(super) f: fn(Event) -> Option, + pub(super) f: fn(Event, event::Status) -> Option, } -impl Recipe for Events +impl Recipe for Events where Message: 'static + Send, { @@ -29,7 +28,9 @@ where event_stream: EventStream, ) -> BoxStream { event_stream - .filter_map(move |event| future::ready((self.f)(event))) + .filter_map(move |(event, status)| { + future::ready((self.f)(event, status)) + }) .boxed() } } diff --git a/winit/src/application.rs b/winit/src/application.rs index 5f51a0cb..5c48fb32 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -359,7 +359,7 @@ async fn run_instance( state.scale_factor(), state.modifiers(), ) { - let _ = user_interface.update( + let event_status = user_interface.update( event.clone(), state.cursor_position(), clipboard.as_ref().map(|c| c as _), @@ -367,7 +367,7 @@ async fn run_instance( &mut messages, ); - runtime.broadcast(event); + runtime.broadcast((event, event_status)); is_clean = false; } From bf2d2561b8dde3e160438428b59c03c38a5f752a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Nov 2020 02:51:26 +0100 Subject: [PATCH 17/17] Batch event processing in `UserInterface::update` --- glutin/src/application.rs | 36 +++++++------- native/src/program/state.rs | 17 ++++--- native/src/user_interface.rs | 93 ++++++++++++++++++++---------------- winit/src/application.rs | 36 +++++++------- 4 files changed, 95 insertions(+), 87 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 663654b1..e593f0ae 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -155,21 +155,32 @@ async fn run_instance( user_interface.draw(&mut renderer, state.cursor_position()); let mut mouse_interaction = mouse::Interaction::default(); + let mut events = Vec::new(); let mut messages = Vec::new(); - let mut is_clean = true; debug.startup_finished(); while let Some(event) = receiver.next().await { match event { - event::Event::NewEvents(_) => { - debug.event_processing_started(); - } event::Event::MainEventsCleared => { + if events.is_empty() && messages.is_empty() { + continue; + } + + debug.event_processing_started(); + + let statuses = user_interface.update( + &events, + state.cursor_position(), + clipboard.as_ref().map(|c| c as _), + &mut renderer, + &mut messages, + ); + debug.event_processing_finished(); - if is_clean && messages.is_empty() { - continue; + for event in events.drain(..).zip(statuses.into_iter()) { + runtime.broadcast(event); } if !messages.is_empty() { @@ -203,7 +214,6 @@ async fn run_instance( debug.draw_finished(); context.window().request_redraw(); - is_clean = true; } event::Event::UserEvent(message) => { messages.push(message); @@ -277,17 +287,7 @@ async fn run_instance( state.scale_factor(), state.modifiers(), ) { - let event_status = user_interface.update( - event.clone(), - state.cursor_position(), - clipboard.as_ref().map(|c| c as _), - &mut renderer, - &mut messages, - ); - - runtime.broadcast((event, event_status)); - - is_clean = false; + events.push(event); } } _ => {} diff --git a/native/src/program/state.rs b/native/src/program/state.rs index 5557347b..76283e30 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -122,17 +122,16 @@ where debug.event_processing_started(); let mut messages = Vec::new(); - for event in self.queued_events.drain(..) { - let _ = user_interface.update( - event, - cursor_position, - clipboard, - renderer, - &mut messages, - ); - } + let _ = user_interface.update( + &self.queued_events, + cursor_position, + clipboard, + renderer, + &mut messages, + ); messages.extend(self.queued_messages.drain(..)); + self.queued_events.clear(); debug.event_processing_finished(); if messages.is_empty() { diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 793d341c..31bb6b99 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -182,16 +182,14 @@ where /// &mut renderer, /// ); /// - /// for event in events.drain(..) { - /// // Update the user interface - /// let _event_status = user_interface.update( - /// event, - /// cursor_position, - /// None, - /// &renderer, - /// &mut messages - /// ); - /// } + /// // Update the user interface + /// let event_statuses = user_interface.update( + /// &events, + /// cursor_position, + /// None, + /// &renderer, + /// &mut messages + /// ); /// /// cache = user_interface.into_cache(); /// @@ -203,13 +201,13 @@ where /// ``` pub fn update( &mut self, - event: Event, + events: &[Event], cursor_position: Point, clipboard: Option<&dyn Clipboard>, renderer: &Renderer, messages: &mut Vec, - ) -> event::Status { - let (base_cursor, overlay_status) = if let Some(mut overlay) = + ) -> Vec { + let (base_cursor, overlay_statuses) = if let Some(mut overlay) = self.root.overlay(Layout::new(&self.base.layout)) { let layer = Self::overlay_layer( @@ -219,14 +217,20 @@ where renderer, ); - let event_status = overlay.on_event( - event.clone(), - Layout::new(&layer.layout), - cursor_position, - messages, - renderer, - clipboard, - ); + let event_statuses = events + .iter() + .cloned() + .map(|event| { + overlay.on_event( + event, + Layout::new(&layer.layout), + cursor_position, + messages, + renderer, + clipboard, + ) + }) + .collect(); let base_cursor = if layer.layout.bounds().contains(cursor_position) { @@ -238,21 +242,28 @@ where self.overlay = Some(layer); - (base_cursor, event_status) + (base_cursor, event_statuses) } else { - (cursor_position, event::Status::Ignored) + (cursor_position, vec![event::Status::Ignored; events.len()]) }; - let event_status = self.root.widget.on_event( - event, - Layout::new(&self.base.layout), - base_cursor, - messages, - renderer, - clipboard, - ); + events + .iter() + .cloned() + .zip(overlay_statuses.into_iter()) + .map(|(event, overlay_status)| { + let event_status = self.root.widget.on_event( + event, + Layout::new(&self.base.layout), + base_cursor, + messages, + renderer, + clipboard, + ); - event_status.merge(overlay_status) + event_status.merge(overlay_status) + }) + .collect() } /// Draws the [`UserInterface`] with the provided [`Renderer`]. @@ -305,16 +316,14 @@ where /// &mut renderer, /// ); /// - /// for event in events.drain(..) { - /// // Update the user interface - /// let _event_status = user_interface.update( - /// event, - /// cursor_position, - /// None, - /// &renderer, - /// &mut messages - /// ); - /// } + /// // Update the user interface + /// let event_statuses = user_interface.update( + /// &events, + /// cursor_position, + /// None, + /// &renderer, + /// &mut messages + /// ); /// /// // Draw the user interface /// let mouse_cursor = user_interface.draw(&mut renderer, cursor_position); diff --git a/winit/src/application.rs b/winit/src/application.rs index 5c48fb32..ded60366 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -241,21 +241,32 @@ async fn run_instance( user_interface.draw(&mut renderer, state.cursor_position()); let mut mouse_interaction = mouse::Interaction::default(); + let mut events = Vec::new(); let mut messages = Vec::new(); - let mut is_clean = true; debug.startup_finished(); while let Some(event) = receiver.next().await { match event { - event::Event::NewEvents(_) => { - debug.event_processing_started(); - } event::Event::MainEventsCleared => { + if events.is_empty() && messages.is_empty() { + continue; + } + + debug.event_processing_started(); + + let statuses = user_interface.update( + &events, + state.cursor_position(), + clipboard.as_ref().map(|c| c as _), + &mut renderer, + &mut messages, + ); + debug.event_processing_finished(); - if is_clean && messages.is_empty() { - continue; + for event in events.drain(..).zip(statuses.into_iter()) { + runtime.broadcast(event); } if !messages.is_empty() { @@ -288,7 +299,6 @@ async fn run_instance( debug.draw_finished(); window.request_redraw(); - is_clean = true; } event::Event::UserEvent(message) => { messages.push(message); @@ -359,17 +369,7 @@ async fn run_instance( state.scale_factor(), state.modifiers(), ) { - let event_status = user_interface.update( - event.clone(), - state.cursor_position(), - clipboard.as_ref().map(|c| c as _), - &mut renderer, - &mut messages, - ); - - runtime.broadcast((event, event_status)); - - is_clean = false; + events.push(event); } } _ => {}