Merge pull request #614 from hecrj/feature/event-capturing

Event capturing
This commit is contained in:
Héctor Ramón 2020-11-14 02:17:21 +01:00 committed by GitHub
commit 62295f554b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 722 additions and 436 deletions

View File

@ -69,7 +69,8 @@ impl Sandbox for Example {
mod bezier { mod bezier {
use iced::{ 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, mouse, Element, Length, Point, Rectangle,
}; };
@ -109,41 +110,51 @@ mod bezier {
event: Event, event: Event,
bounds: Rectangle, bounds: Rectangle,
cursor: Cursor, cursor: Cursor,
) -> Option<Curve> { ) -> (event::Status, Option<Curve>) {
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);
};
match event { match event {
Event::Mouse(mouse_event) => match mouse_event { Event::Mouse(mouse_event) => {
mouse::Event::ButtonPressed(mouse::Button::Left) => { let message = match mouse_event {
match self.state.pending { mouse::Event::ButtonPressed(mouse::Button::Left) => {
None => { match self.state.pending {
self.state.pending = Some(Pending::One { None => {
from: cursor_position, self.state.pending = Some(Pending::One {
}); from: cursor_position,
None });
}
Some(Pending::One { from }) => {
self.state.pending = Some(Pending::Two {
from,
to: cursor_position,
});
None None
} }
Some(Pending::Two { from, to }) => { Some(Pending::One { from }) => {
self.state.pending = None; self.state.pending = Some(Pending::Two {
from,
to: cursor_position,
});
Some(Curve { None
from, }
to, Some(Pending::Two { from, to }) => {
control: cursor_position, self.state.pending = None;
})
Some(Curve {
from,
to,
control: cursor_position,
})
}
} }
} }
} _ => None,
_ => None, };
},
_ => None, (event::Status::Captured, message)
}
_ => (event::Status::Ignored, None),
} }
} }

View File

@ -153,9 +153,8 @@ impl Application for GameOfLife {
mod grid { mod grid {
use crate::Preset; use crate::Preset;
use iced::{ use iced::{
canvas::{ canvas::event::{self, Event},
self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path, Text, canvas::{self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text},
},
mouse, Color, Element, HorizontalAlignment, Length, Point, Rectangle, mouse, Color, Element, HorizontalAlignment, Length, Point, Rectangle,
Size, Vector, VerticalAlignment, Size, Vector, VerticalAlignment,
}; };
@ -328,12 +327,18 @@ mod grid {
event: Event, event: Event,
bounds: Rectangle, bounds: Rectangle,
cursor: Cursor, cursor: Cursor,
) -> Option<Message> { ) -> (event::Status, Option<Message>) {
if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event { if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
self.interaction = Interaction::None; 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 cell = Cell::at(self.project(cursor_position, bounds.size()));
let is_populated = self.state.contains(&cell); let is_populated = self.state.contains(&cell);
@ -345,28 +350,32 @@ mod grid {
match event { match event {
Event::Mouse(mouse_event) => match mouse_event { Event::Mouse(mouse_event) => match mouse_event {
mouse::Event::ButtonPressed(button) => match button { mouse::Event::ButtonPressed(button) => {
mouse::Button::Left => { let message = match button {
self.interaction = if is_populated { mouse::Button::Left => {
Interaction::Erasing self.interaction = if is_populated {
} else { Interaction::Erasing
Interaction::Drawing } else {
}; Interaction::Drawing
};
populate.or(unpopulate) populate.or(unpopulate)
} }
mouse::Button::Right => { mouse::Button::Right => {
self.interaction = Interaction::Panning { self.interaction = Interaction::Panning {
translation: self.translation, translation: self.translation,
start: cursor_position, start: cursor_position,
}; };
None None
} }
_ => None, _ => None,
}, };
(event::Status::Captured, message)
}
mouse::Event::CursorMoved { .. } => { mouse::Event::CursorMoved { .. } => {
match self.interaction { let message = match self.interaction {
Interaction::Drawing => populate, Interaction::Drawing => populate,
Interaction::Erasing => unpopulate, Interaction::Erasing => unpopulate,
Interaction::Panning { translation, start } => { Interaction::Panning { translation, start } => {
@ -380,7 +389,14 @@ mod grid {
None None
} }
_ => 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::Event::WheelScrolled { delta } => match delta {
mouse::ScrollDelta::Lines { y, .. } mouse::ScrollDelta::Lines { y, .. }
@ -413,12 +429,12 @@ mod grid {
self.grid_cache.clear(); self.grid_cache.clear();
} }
None (event::Status::Captured, None)
} }
}, },
_ => None, _ => (event::Status::Ignored, None),
}, },
_ => None, _ => (event::Status::Ignored, None),
} }
} }

View File

@ -3,7 +3,7 @@ use iced::{
Button, Column, Command, Container, Element, HorizontalAlignment, Length, Button, Column, Command, Container, Element, HorizontalAlignment, Length,
PaneGrid, Scrollable, Settings, Subscription, Text, PaneGrid, Scrollable, Settings, Subscription, Text,
}; };
use iced_native::{subscription, Event}; use iced_native::{event, subscription, Event};
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
Example::run(Settings::default()) Example::run(Settings::default())
@ -119,12 +119,18 @@ impl Application for Example {
} }
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
subscription::events_with(|event| match event { subscription::events_with(|event, status| {
Event::Keyboard(keyboard::Event::KeyPressed { if let event::Status::Captured = status {
modifiers, return None;
key_code, }
}) if modifiers.is_command_pressed() => handle_hotkey(key_code),
_ => None, match event {
Event::Keyboard(keyboard::Event::KeyPressed {
modifiers,
key_code,
}) if modifiers.is_command_pressed() => handle_hotkey(key_code),
_ => None,
}
}) })
} }

View File

@ -156,29 +156,33 @@ async fn run_instance<A, E, C>(
let mut mouse_interaction = mouse::Interaction::default(); let mut mouse_interaction = mouse::Interaction::default();
let mut events = Vec::new(); let mut events = Vec::new();
let mut external_messages = Vec::new(); let mut messages = Vec::new();
debug.startup_finished(); debug.startup_finished();
while let Some(event) = receiver.next().await { while let Some(event) = receiver.next().await {
match event { match event {
event::Event::MainEventsCleared => { event::Event::MainEventsCleared => {
if events.is_empty() && external_messages.is_empty() { if events.is_empty() && messages.is_empty() {
continue; continue;
} }
debug.event_processing_started(); debug.event_processing_started();
let mut messages = user_interface.update(
let statuses = user_interface.update(
&events, &events,
state.cursor_position(), state.cursor_position(),
clipboard.as_ref().map(|c| c as _), clipboard.as_ref().map(|c| c as _),
&mut renderer, &mut renderer,
&mut messages,
); );
messages.extend(external_messages.drain(..));
events.clear();
debug.event_processing_finished(); debug.event_processing_finished();
for event in events.drain(..).zip(statuses.into_iter()) {
runtime.broadcast(event);
}
if !messages.is_empty() { if !messages.is_empty() {
let cache = let cache =
ManuallyDrop::into_inner(user_interface).into_cache(); ManuallyDrop::into_inner(user_interface).into_cache();
@ -188,7 +192,7 @@ async fn run_instance<A, E, C>(
&mut application, &mut application,
&mut runtime, &mut runtime,
&mut debug, &mut debug,
messages, &mut messages,
); );
// Update window // Update window
@ -212,7 +216,7 @@ async fn run_instance<A, E, C>(
context.window().request_redraw(); context.window().request_redraw();
} }
event::Event::UserEvent(message) => { event::Event::UserEvent(message) => {
external_messages.push(message); messages.push(message);
} }
event::Event::RedrawRequested(_) => { event::Event::RedrawRequested(_) => {
debug.render_started(); debug.render_started();
@ -283,8 +287,7 @@ async fn run_instance<A, E, C>(
state.scale_factor(), state.scale_factor(),
state.modifiers(), state.modifiers(),
) { ) {
events.push(event.clone()); events.push(event);
runtime.broadcast(event);
} }
} }
_ => {} _ => {}

View File

@ -7,18 +7,20 @@
//! [`Canvas`]: struct.Canvas.html //! [`Canvas`]: struct.Canvas.html
//! [`Frame`]: struct.Frame.html //! [`Frame`]: struct.Frame.html
use crate::{Backend, Defaults, Primitive, Renderer}; use crate::{Backend, Defaults, Primitive, Renderer};
use iced_native::layout;
use iced_native::mouse;
use iced_native::{ use iced_native::{
layout, mouse, Clipboard, Element, Hasher, Layout, Length, Point, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
Rectangle, Size, Vector, Widget, Widget,
}; };
use std::hash::Hash; use std::hash::Hash;
use std::marker::PhantomData; use std::marker::PhantomData;
pub mod event;
pub mod path; pub mod path;
mod cache; mod cache;
mod cursor; mod cursor;
mod event;
mod fill; mod fill;
mod frame; mod frame;
mod geometry; mod geometry;
@ -166,7 +168,7 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
_renderer: &Renderer<B>, _renderer: &Renderer<B>,
_clipboard: Option<&dyn Clipboard>, _clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
let bounds = layout.bounds(); let bounds = layout.bounds();
let canvas_event = match event { let canvas_event = match event {
@ -182,12 +184,17 @@ where
let cursor = Cursor::from_window_position(cursor_position); let cursor = Cursor::from_window_position(cursor_position);
if let Some(canvas_event) = canvas_event { if let Some(canvas_event) = canvas_event {
if let Some(message) = let (event_status, message) =
self.program.update(canvas_event, bounds, cursor) self.program.update(canvas_event, bounds, cursor);
{
if let Some(message) = message {
messages.push(message); messages.push(message);
} }
return event_status;
} }
event::Status::Ignored
} }
fn draw( fn draw(

View File

@ -1,6 +1,9 @@
//! Handle events of a canvas.
use iced_native::keyboard; use iced_native::keyboard;
use iced_native::mouse; use iced_native::mouse;
pub use iced_native::event::Status;
/// A [`Canvas`] event. /// A [`Canvas`] event.
/// ///
/// [`Canvas`]: struct.Event.html /// [`Canvas`]: struct.Event.html

View File

@ -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}; use iced_native::{mouse, Rectangle};
/// The state and logic of a [`Canvas`]. /// The state and logic of a [`Canvas`].
@ -27,8 +28,8 @@ pub trait Program<Message> {
_event: Event, _event: Event,
_bounds: Rectangle, _bounds: Rectangle,
_cursor: Cursor, _cursor: Cursor,
) -> Option<Message> { ) -> (event::Status, Option<Message>) {
None (event::Status::Ignored, None)
} }
/// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. /// Draws the state of the [`Program`], producing a bunch of [`Geometry`].
@ -67,7 +68,7 @@ where
event: Event, event: Event,
bounds: Rectangle, bounds: Rectangle,
cursor: Cursor, cursor: Cursor,
) -> Option<Message> { ) -> (event::Status, Option<Message>) {
T::update(self, event, bounds, cursor) T::update(self, event, bounds, cursor)
} }

View File

@ -1,7 +1,8 @@
use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::overlay; use crate::overlay;
use crate::{ use crate::{
Clipboard, Color, Event, Hasher, Layout, Length, Point, Rectangle, Widget, Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget,
}; };
/// A generic [`Widget`]. /// A generic [`Widget`].
@ -240,7 +241,7 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
self.widget.on_event( self.widget.on_event(
event, event,
layout, layout,
@ -248,7 +249,7 @@ where
messages, messages,
renderer, renderer,
clipboard, clipboard,
); )
} }
/// Draws the [`Element`] and its children using the given [`Layout`]. /// Draws the [`Element`] and its children using the given [`Layout`].
@ -335,10 +336,10 @@ where
messages: &mut Vec<B>, messages: &mut Vec<B>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
let mut original_messages = Vec::new(); let mut original_messages = Vec::new();
self.widget.on_event( let status = self.widget.on_event(
event, event,
layout, layout,
cursor_position, cursor_position,
@ -350,6 +351,8 @@ where
original_messages original_messages
.drain(..) .drain(..)
.for_each(|message| messages.push((self.mapper)(message))); .for_each(|message| messages.push((self.mapper)(message)));
status
} }
fn draw( fn draw(
@ -423,7 +426,7 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
self.element.widget.on_event( self.element.widget.on_event(
event, event,
layout, layout,

View File

@ -1,3 +1,4 @@
//! Handle events of a user interface.
use crate::{keyboard, mouse, window}; use crate::{keyboard, mouse, window};
/// A user interface event. /// 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!_ /// additional events, feel free to [open an issue] and share your use case!_
/// ///
/// [open an issue]: https://github.com/hecrj/iced/issues /// [open an issue]: https://github.com/hecrj/iced/issues
#[derive(PartialEq, Clone, Debug)] #[derive(Debug, Clone, PartialEq)]
pub enum Event { pub enum Event {
/// A keyboard event /// A keyboard event
Keyboard(keyboard::Event), Keyboard(keyboard::Event),
@ -17,3 +18,41 @@ pub enum Event {
/// A window event /// A window event
Window(window::Event), Window(window::Event),
} }
/// 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.
///
/// [`Event`]: enum.Event.html
Ignored,
/// The [`Event`] was handled and processed by a widget.
///
/// [`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,
}
}
}

View File

@ -35,6 +35,7 @@
#![deny(unused_results)] #![deny(unused_results)]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![forbid(rust_2018_idioms)] #![forbid(rust_2018_idioms)]
pub mod event;
pub mod keyboard; pub mod keyboard;
pub mod layout; pub mod layout;
pub mod mouse; pub mod mouse;
@ -47,7 +48,6 @@ pub mod window;
mod clipboard; mod clipboard;
mod element; mod element;
mod event;
mod hasher; mod hasher;
mod runtime; mod runtime;
mod user_interface; mod user_interface;

View File

@ -6,7 +6,9 @@ pub mod menu;
pub use element::Element; pub use element::Element;
pub use menu::Menu; 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. /// An interactive component that can be displayed on top of other widgets.
pub trait Overlay<Message, Renderer> pub trait Overlay<Message, Renderer>
@ -79,6 +81,7 @@ where
_messages: &mut Vec<Message>, _messages: &mut Vec<Message>,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>, _clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
event::Status::Ignored
} }
} }

View File

@ -1,6 +1,8 @@
pub use crate::Overlay; 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`]. /// A generic [`Overlay`].
/// ///
@ -67,7 +69,7 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
self.overlay.on_event( self.overlay.on_event(
event, event,
layout, layout,
@ -136,10 +138,10 @@ where
messages: &mut Vec<B>, messages: &mut Vec<B>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
let mut original_messages = Vec::new(); let mut original_messages = Vec::new();
self.content.on_event( let event_status = self.content.on_event(
event, event,
layout, layout,
cursor_position, cursor_position,
@ -151,6 +153,8 @@ where
original_messages original_messages
.drain(..) .drain(..)
.for_each(|message| messages.push((self.mapper)(message))); .for_each(|message| messages.push((self.mapper)(message)));
event_status
} }
fn draw( fn draw(

View File

@ -1,8 +1,14 @@
//! Build and show dropdown menus. //! 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::{ use crate::{
container, layout, mouse, overlay, scrollable, text, Clipboard, Container, Clipboard, Container, Element, Hasher, Layout, Length, Point, Rectangle,
Element, Event, Hasher, Layout, Length, Point, Rectangle, Scrollable, Size, Scrollable, Size, Vector, Widget,
Vector, Widget,
}; };
/// A list of selectable options. /// A list of selectable options.
@ -235,7 +241,7 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
self.container.on_event( self.container.on_event(
event.clone(), event.clone(),
layout, layout,
@ -243,7 +249,7 @@ where
messages, messages,
renderer, renderer,
clipboard, clipboard,
); )
} }
fn draw( fn draw(
@ -336,7 +342,7 @@ where
_messages: &mut Vec<Message>, _messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>, _clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
match event { match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let bounds = layout.bounds(); let bounds = layout.bounds();
@ -364,6 +370,8 @@ where
} }
_ => {} _ => {}
} }
event::Status::Ignored
} }
fn draw( fn draw(

View File

@ -120,14 +120,17 @@ where
); );
debug.event_processing_started(); debug.event_processing_started();
let mut messages = user_interface.update( let mut messages = Vec::new();
let _ = user_interface.update(
&self.queued_events, &self.queued_events,
cursor_position, cursor_position,
clipboard, clipboard,
renderer, renderer,
&mut messages,
); );
messages.extend(self.queued_messages.drain(..));
messages.extend(self.queued_messages.drain(..));
self.queued_events.clear(); self.queued_events.clear();
debug.event_processing_finished(); debug.event_processing_finished();

View File

@ -1,5 +1,6 @@
//! Run commands and subscriptions. //! 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. /// A native runtime with a generic executor and receiver of results.
/// ///
@ -8,5 +9,10 @@ use crate::{Event, Hasher};
/// ///
/// [`Command`]: ../struct.Command.html /// [`Command`]: ../struct.Command.html
/// [`Subscription`]: ../struct.Subscription.html /// [`Subscription`]: ../struct.Subscription.html
pub type Runtime<Executor, Receiver, Message> = pub type Runtime<Executor, Receiver, Message> = iced_futures::Runtime<
iced_futures::Runtime<Hasher, Event, Executor, Receiver, Message>; Hasher,
(Event, event::Status),
Executor,
Receiver,
Message,
>;

View File

@ -1,5 +1,6 @@
//! Listen to external events in your application. //! 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; use iced_futures::futures::stream::BoxStream;
/// A request to listen to external events. /// A request to listen to external events.
@ -15,19 +16,21 @@ use iced_futures::futures::stream::BoxStream;
/// ///
/// [`Command`]: ../struct.Command.html /// [`Command`]: ../struct.Command.html
/// [`Subscription`]: struct.Subscription.html /// [`Subscription`]: struct.Subscription.html
pub type Subscription<T> = iced_futures::Subscription<Hasher, Event, T>; pub type Subscription<T> =
iced_futures::Subscription<Hasher, (Event, event::Status), T>;
/// A stream of runtime events. /// A stream of runtime events.
/// ///
/// It is the input of a [`Subscription`] in the native runtime. /// It is the input of a [`Subscription`] in the native runtime.
/// ///
/// [`Subscription`]: type.Subscription.html /// [`Subscription`]: type.Subscription.html
pub type EventStream = BoxStream<'static, Event>; pub type EventStream = BoxStream<'static, (Event, event::Status)>;
/// A native [`Subscription`] tracker. /// A native [`Subscription`] tracker.
/// ///
/// [`Subscription`]: type.Subscription.html /// [`Subscription`]: type.Subscription.html
pub type Tracker = iced_futures::subscription::Tracker<Hasher, Event>; pub type Tracker =
iced_futures::subscription::Tracker<Hasher, (Event, event::Status)>;
pub use iced_futures::subscription::Recipe; pub use iced_futures::subscription::Recipe;
@ -37,13 +40,18 @@ use events::Events;
/// Returns a [`Subscription`] to all the runtime events. /// Returns a [`Subscription`] to all the runtime events.
/// ///
/// This subscription will notify your application of any [`Event`] handled by /// This subscription will notify your application of any [`Event`] that was
/// the runtime. /// not captured by any widget.
/// ///
/// [`Subscription`]: type.Subscription.html /// [`Subscription`]: type.Subscription.html
/// [`Event`]: ../enum.Event.html /// [`Event`]: ../enum.Event.html
pub fn events() -> Subscription<Event> { pub fn events() -> Subscription<Event> {
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 /// Returns a [`Subscription`] that filters all the runtime events with the
@ -58,7 +66,7 @@ pub fn events() -> Subscription<Event> {
/// [`Subscription`]: type.Subscription.html /// [`Subscription`]: type.Subscription.html
/// [`Event`]: ../enum.Event.html /// [`Event`]: ../enum.Event.html
pub fn events_with<Message>( pub fn events_with<Message>(
f: fn(Event) -> Option<Message>, f: fn(Event, event::Status) -> Option<Message>,
) -> Subscription<Message> ) -> Subscription<Message>
where where
Message: 'static + Send, Message: 'static + Send,

View File

@ -1,16 +1,15 @@
use crate::{ use crate::event::{self, Event};
subscription::{EventStream, Recipe}, use crate::subscription::{EventStream, Recipe};
Event, Hasher, use crate::Hasher;
};
use iced_futures::futures::future; use iced_futures::futures::future;
use iced_futures::futures::StreamExt; use iced_futures::futures::StreamExt;
use iced_futures::BoxStream; use iced_futures::BoxStream;
pub struct Events<Message> { pub struct Events<Message> {
pub(super) f: fn(Event) -> Option<Message>, pub(super) f: fn(Event, event::Status) -> Option<Message>,
} }
impl<Message> Recipe<Hasher, Event> for Events<Message> impl<Message> Recipe<Hasher, (Event, event::Status)> for Events<Message>
where where
Message: 'static + Send, Message: 'static + Send,
{ {
@ -29,7 +28,9 @@ where
event_stream: EventStream, event_stream: EventStream,
) -> BoxStream<Self::Output> { ) -> BoxStream<Self::Output> {
event_stream event_stream
.filter_map(move |event| future::ready((self.f)(event))) .filter_map(move |(event, status)| {
future::ready((self.f)(event, status))
})
.boxed() .boxed()
} }
} }

View File

@ -1,6 +1,7 @@
use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::overlay; use crate::overlay;
use crate::{Clipboard, Element, Event, Layout, Point, Rectangle, Size}; use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
use std::hash::Hasher; use std::hash::Hasher;
@ -169,9 +170,10 @@ where
/// ///
/// // Initialize our event storage /// // Initialize our event storage
/// let mut events = Vec::new(); /// let mut events = Vec::new();
/// let mut messages = Vec::new();
/// ///
/// loop { /// loop {
/// // Process system events... /// // Obtain system events...
/// ///
/// let mut user_interface = UserInterface::build( /// let mut user_interface = UserInterface::build(
/// counter.view(), /// counter.view(),
@ -181,17 +183,18 @@ where
/// ); /// );
/// ///
/// // Update the user interface /// // Update the user interface
/// let messages = user_interface.update( /// let event_statuses = user_interface.update(
/// &events, /// &events,
/// cursor_position, /// cursor_position,
/// None, /// None,
/// &renderer, /// &renderer,
/// &mut messages
/// ); /// );
/// ///
/// cache = user_interface.into_cache(); /// cache = user_interface.into_cache();
/// ///
/// // Process the produced messages /// // Process the produced messages
/// for message in messages { /// for message in messages.drain(..) {
/// counter.update(message); /// counter.update(message);
/// } /// }
/// } /// }
@ -202,10 +205,9 @@ where
cursor_position: Point, cursor_position: Point,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
renderer: &Renderer, renderer: &Renderer,
) -> Vec<Message> { messages: &mut Vec<Message>,
let mut messages = Vec::new(); ) -> Vec<event::Status> {
let (base_cursor, overlay_statuses) = if let Some(mut overlay) =
let base_cursor = if let Some(mut overlay) =
self.root.overlay(Layout::new(&self.base.layout)) self.root.overlay(Layout::new(&self.base.layout))
{ {
let layer = Self::overlay_layer( let layer = Self::overlay_layer(
@ -215,16 +217,20 @@ where
renderer, renderer,
); );
for event in events { let event_statuses = events
overlay.on_event( .iter()
event.clone(), .cloned()
Layout::new(&layer.layout), .map(|event| {
cursor_position, overlay.on_event(
&mut messages, event,
renderer, Layout::new(&layer.layout),
clipboard, cursor_position,
); messages,
} renderer,
clipboard,
)
})
.collect();
let base_cursor = if layer.layout.bounds().contains(cursor_position) let base_cursor = if layer.layout.bounds().contains(cursor_position)
{ {
@ -236,23 +242,28 @@ where
self.overlay = Some(layer); self.overlay = Some(layer);
base_cursor (base_cursor, event_statuses)
} else { } else {
cursor_position (cursor_position, vec![event::Status::Ignored; events.len()])
}; };
for event in events { events
self.root.widget.on_event( .iter()
event.clone(), .cloned()
Layout::new(&self.base.layout), .zip(overlay_statuses.into_iter())
base_cursor, .map(|(event, overlay_status)| {
&mut messages, let event_status = self.root.widget.on_event(
renderer, event,
clipboard, Layout::new(&self.base.layout),
); base_cursor,
} messages,
renderer,
clipboard,
);
messages event_status.merge(overlay_status)
})
.collect()
} }
/// Draws the [`UserInterface`] with the provided [`Renderer`]. /// Draws the [`UserInterface`] with the provided [`Renderer`].
@ -293,9 +304,10 @@ where
/// let mut window_size = Size::new(1024.0, 768.0); /// let mut window_size = Size::new(1024.0, 768.0);
/// let mut cursor_position = Point::default(); /// let mut cursor_position = Point::default();
/// let mut events = Vec::new(); /// let mut events = Vec::new();
/// let mut messages = Vec::new();
/// ///
/// loop { /// loop {
/// // Process system events... /// // Obtain system events...
/// ///
/// let mut user_interface = UserInterface::build( /// let mut user_interface = UserInterface::build(
/// counter.view(), /// counter.view(),
@ -304,11 +316,13 @@ where
/// &mut renderer, /// &mut renderer,
/// ); /// );
/// ///
/// let messages = user_interface.update( /// // Update the user interface
/// let event_statuses = user_interface.update(
/// &events, /// &events,
/// cursor_position, /// cursor_position,
/// None, /// None,
/// &renderer, /// &renderer,
/// &mut messages
/// ); /// );
/// ///
/// // Draw the user interface /// // Draw the user interface
@ -316,7 +330,7 @@ where
/// ///
/// cache = user_interface.into_cache(); /// cache = user_interface.into_cache();
/// ///
/// for message in messages { /// for message in messages.drain(..) {
/// counter.update(message); /// counter.update(message);
/// } /// }
/// ///

View File

@ -73,9 +73,10 @@ pub use text::Text;
#[doc(no_inline)] #[doc(no_inline)]
pub use text_input::TextInput; pub use text_input::TextInput;
use crate::{ use crate::event::{self, Event};
layout, overlay, Clipboard, Event, Hasher, Layout, Length, Point, Rectangle, use crate::layout;
}; use crate::overlay;
use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
/// A component that displays information and allows interaction. /// A component that displays information and allows interaction.
/// ///
@ -182,7 +183,8 @@ where
_messages: &mut Vec<Message>, _messages: &mut Vec<Message>,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>, _clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
event::Status::Ignored
} }
/// Returns the overlay of the [`Element`], if there is any. /// Returns the overlay of the [`Element`], if there is any.

View File

@ -4,9 +4,11 @@
//! //!
//! [`Button`]: struct.Button.html //! [`Button`]: struct.Button.html
//! [`State`]: struct.State.html //! [`State`]: struct.State.html
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::{ use crate::{
layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
Rectangle, Widget,
}; };
use std::hash::Hash; use std::hash::Hash;
@ -184,31 +186,38 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>, _clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
match event { match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
if self.on_press.is_some() { if self.on_press.is_some() {
let bounds = layout.bounds(); 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)) => { Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
if let Some(on_press) = self.on_press.clone() { if let Some(on_press) = self.on_press.clone() {
let bounds = layout.bounds(); let bounds = layout.bounds();
let is_clicked = self.state.is_pressed if self.state.is_pressed {
&& bounds.contains(cursor_position); self.state.is_pressed = false;
self.state.is_pressed = false; if bounds.contains(cursor_position) {
messages.push(on_press);
}
if is_clicked { return event::Status::Captured;
messages.push(on_press);
} }
} }
} }
_ => {} _ => {}
} }
event::Status::Ignored
} }
fn draw( fn draw(

View File

@ -1,10 +1,14 @@
//! Show toggle controls using checkboxes. //! Show toggle controls using checkboxes.
use std::hash::Hash; use std::hash::Hash;
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::row;
use crate::text;
use crate::{ use crate::{
layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher, Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length,
HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, Point, Rectangle, Row, Text, VerticalAlignment, Widget,
VerticalAlignment, Widget,
}; };
/// A box that can be checked. /// A box that can be checked.
@ -161,17 +165,21 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>, _clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
match event { match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let mouse_over = layout.bounds().contains(cursor_position); let mouse_over = layout.bounds().contains(cursor_position);
if mouse_over { if mouse_over {
messages.push((self.on_toggle)(!self.is_checked)); messages.push((self.on_toggle)(!self.is_checked));
return event::Status::Captured;
} }
} }
_ => {} _ => {}
} }
event::Status::Ignored
} }
fn draw( fn draw(

View File

@ -1,11 +1,11 @@
//! Distribute content vertically. //! Distribute content vertically.
use std::hash::Hash; use std::hash::Hash;
use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::overlay; use crate::overlay;
use crate::{ use crate::{
Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
Widget,
}; };
use std::u32; use std::u32;
@ -162,9 +162,11 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
self.children.iter_mut().zip(layout.children()).for_each( self.children
|(child, layout)| { .iter_mut()
.zip(layout.children())
.map(|(child, layout)| {
child.widget.on_event( child.widget.on_event(
event.clone(), event.clone(),
layout, layout,
@ -173,8 +175,8 @@ where
renderer, renderer,
clipboard, clipboard,
) )
}, })
); .fold(event::Status::Ignored, event::Status::merge)
} }
fn draw( fn draw(

View File

@ -1,9 +1,11 @@
//! Decorate content and apply alignment. //! Decorate content and apply alignment.
use std::hash::Hash; use std::hash::Hash;
use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::{ use crate::{
layout, overlay, Align, Clipboard, Element, Event, Hasher, Layout, Length, Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
Point, Rectangle, Widget,
}; };
use std::u32; use std::u32;
@ -174,7 +176,7 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
self.content.widget.on_event( self.content.widget.on_event(
event, event,
layout.children().next().unwrap(), layout.children().next().unwrap(),

View File

@ -28,9 +28,16 @@ pub use split::Split;
pub use state::{Focus, State}; pub use state::{Focus, State};
pub use title_bar::TitleBar; 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::{ use crate::{
container, layout, mouse, overlay, row, text, Clipboard, Element, Event, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector,
Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, Widget,
}; };
/// A collection of panes distributed using either vertical or horizontal splits /// A collection of panes distributed using either vertical or horizontal splits
@ -242,7 +249,7 @@ where
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
) { ) -> event::Status {
if let Some((_, on_resize)) = &self.on_resize { if let Some((_, on_resize)) = &self.on_resize {
if let Some((split, _)) = self.state.picked_split() { if let Some((split, _)) = self.state.picked_split() {
let bounds = layout.bounds(); let bounds = layout.bounds();
@ -269,9 +276,13 @@ where
}; };
messages.push(on_resize(ResizeEvent { split, ratio })); messages.push(on_resize(ResizeEvent { split, ratio }));
return event::Status::Captured;
} }
} }
} }
event::Status::Ignored
} }
} }
@ -386,13 +397,17 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
let mut event_status = event::Status::Ignored;
match event { match event {
Event::Mouse(mouse_event) => match mouse_event { Event::Mouse(mouse_event) => match mouse_event {
mouse::Event::ButtonPressed(mouse::Button::Left) => { mouse::Event::ButtonPressed(mouse::Button::Left) => {
let bounds = layout.bounds(); let bounds = layout.bounds();
if bounds.contains(cursor_position) { if bounds.contains(cursor_position) {
event_status = event::Status::Captured;
match self.on_resize { match self.on_resize {
Some((leeway, _)) => { Some((leeway, _)) => {
let relative_cursor = Point::new( let relative_cursor = Point::new(
@ -456,12 +471,17 @@ where
} }
self.state.idle(); self.state.idle();
event_status = event::Status::Captured;
} else if self.state.picked_split().is_some() { } else if self.state.picked_split().is_some() {
self.state.idle(); self.state.idle();
event_status = event::Status::Captured;
} }
} }
mouse::Event::CursorMoved { .. } => { mouse::Event::CursorMoved { .. } => {
self.trigger_resize(layout, cursor_position, messages); event_status =
self.trigger_resize(layout, cursor_position, messages);
} }
_ => {} _ => {}
}, },
@ -469,20 +489,22 @@ where
} }
if self.state.picked_pane().is_none() { if self.state.picked_pane().is_none() {
{ self.elements
self.elements.iter_mut().zip(layout.children()).for_each( .iter_mut()
|((_, pane), layout)| { .zip(layout.children())
pane.on_event( .map(|((_, pane), layout)| {
event.clone(), pane.on_event(
layout, event.clone(),
cursor_position, layout,
messages, cursor_position,
renderer, messages,
clipboard, renderer,
) clipboard,
}, )
); })
} .fold(event_status, event::Status::merge)
} else {
event::Status::Captured
} }
} }

View File

@ -1,8 +1,9 @@
use crate::container; use crate::container;
use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::overlay; use crate::overlay;
use crate::pane_grid::{self, TitleBar}; 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`]. /// The content of a [`Pane`].
/// ///
@ -154,11 +155,13 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, 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 body_layout = if let Some(title_bar) = &mut self.title_bar {
let mut children = layout.children(); let mut children = layout.children();
title_bar.on_event( event_status = title_bar.on_event(
event.clone(), event.clone(),
children.next().unwrap(), children.next().unwrap(),
cursor_position, cursor_position,
@ -172,7 +175,7 @@ where
layout layout
}; };
self.body.on_event( let body_status = self.body.on_event(
event, event,
body_layout, body_layout,
cursor_position, cursor_position,
@ -180,6 +183,8 @@ where
renderer, renderer,
clipboard, clipboard,
); );
event_status.merge(body_status)
} }
pub(crate) fn hash_layout(&self, state: &mut Hasher) { pub(crate) fn hash_layout(&self, state: &mut Hasher) {

View File

@ -1,8 +1,7 @@
use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::pane_grid; use crate::pane_grid;
use crate::{ use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
Clipboard, Element, Event, Hasher, Layout, Point, Rectangle, Size,
};
/// The title bar of a [`Pane`]. /// The title bar of a [`Pane`].
/// ///
@ -245,7 +244,7 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
if let Some(controls) = &mut self.controls { if let Some(controls) = &mut self.controls {
let mut children = layout.children(); let mut children = layout.children();
let padded = children.next().unwrap(); let padded = children.next().unwrap();
@ -261,7 +260,9 @@ where
messages, messages,
renderer, renderer,
clipboard, clipboard,
); )
} else {
event::Status::Ignored
} }
} }
} }

View File

@ -1,9 +1,13 @@
//! Display a dropdown list of selectable values. //! 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::{ use crate::{
layout, mouse, overlay, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
overlay::menu::{self, Menu},
scrollable, text, Clipboard, Element, Event, Hasher, Layout, Length, Point,
Rectangle, Size, Widget,
}; };
use std::borrow::Cow; use std::borrow::Cow;
@ -223,13 +227,15 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>, _clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
match event { match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { 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 // TODO: Encode cursor availability in the type system
*self.is_open = *self.is_open =
cursor_position.x < 0.0 || cursor_position.y < 0.0; cursor_position.x < 0.0 || cursor_position.y < 0.0;
event::Status::Captured
} else if layout.bounds().contains(cursor_position) { } else if layout.bounds().contains(cursor_position) {
let selected = self.selected.as_ref(); let selected = self.selected.as_ref();
@ -238,15 +244,23 @@ where
.options .options
.iter() .iter()
.position(|option| Some(option) == selected); .position(|option| Some(option) == selected);
}
event::Status::Captured
} else {
event::Status::Ignored
};
if let Some(last_selection) = self.last_selection.take() { if let Some(last_selection) = self.last_selection.take() {
messages.push((self.on_selected)(last_selection)); messages.push((self.on_selected)(last_selection));
*self.is_open = false; *self.is_open = false;
event::Status::Captured
} else {
event_status
} }
} }
_ => {} _ => event::Status::Ignored,
} }
} }

View File

@ -1,8 +1,12 @@
//! Create choices using radio buttons. //! Create choices using radio buttons.
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::row;
use crate::text;
use crate::{ use crate::{
layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher, Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length,
HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, Point, Rectangle, Row, Text, VerticalAlignment, Widget,
VerticalAlignment, Widget,
}; };
use std::hash::Hash; use std::hash::Hash;
@ -166,15 +170,19 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>, _clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
match event { match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
if layout.bounds().contains(cursor_position) { if layout.bounds().contains(cursor_position) {
messages.push(self.on_click.clone()); messages.push(self.on_click.clone());
return event::Status::Captured;
} }
} }
_ => {} _ => {}
} }
event::Status::Ignored
} }
fn draw( fn draw(

View File

@ -1,9 +1,9 @@
//! Distribute content horizontally. //! Distribute content horizontally.
use crate::event::{self, Event};
use crate::layout; use crate::layout;
use crate::overlay; use crate::overlay;
use crate::{ use crate::{
Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
Widget,
}; };
use std::hash::Hash; use std::hash::Hash;
@ -162,9 +162,11 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
self.children.iter_mut().zip(layout.children()).for_each( self.children
|(child, layout)| { .iter_mut()
.zip(layout.children())
.map(|(child, layout)| {
child.widget.on_event( child.widget.on_event(
event.clone(), event.clone(),
layout, layout,
@ -173,8 +175,8 @@ where
renderer, renderer,
clipboard, clipboard,
) )
}, })
); .fold(event::Status::Ignored, event::Status::merge)
} }
fn draw( fn draw(

View File

@ -1,7 +1,12 @@
//! Navigate an endless amount of content with a scrollbar. //! 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::{ use crate::{
column, layout, mouse, overlay, Align, Clipboard, Column, Element, Event, Align, Clipboard, Column, Element, Hasher, Layout, Length, Point,
Hasher, Layout, Length, Point, Rectangle, Size, Vector, Widget, Rectangle, Size, Vector, Widget,
}; };
use std::{f32, hash::Hash, u32}; use std::{f32, hash::Hash, u32};
@ -184,31 +189,13 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
let bounds = layout.bounds(); let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position); let is_mouse_over = bounds.contains(cursor_position);
let content = layout.children().next().unwrap(); let content = layout.children().next().unwrap();
let content_bounds = content.bounds(); 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 offset = self.state.offset(bounds, content_bounds);
let scrollbar = renderer.scrollbar( let scrollbar = renderer.scrollbar(
bounds, bounds,
@ -223,12 +210,62 @@ where
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
.unwrap_or(false); .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<Point>` 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() { if self.state.is_scroller_grabbed() {
match event { match event {
Event::Mouse(mouse::Event::ButtonReleased( Event::Mouse(mouse::Event::ButtonReleased(
mouse::Button::Left, mouse::Button::Left,
)) => { )) => {
self.state.scroller_grabbed_at = None; self.state.scroller_grabbed_at = None;
return event::Status::Captured;
} }
Event::Mouse(mouse::Event::CursorMoved { .. }) => { Event::Mouse(mouse::Event::CursorMoved { .. }) => {
if let (Some(scrollbar), Some(scroller_grabbed_at)) = if let (Some(scrollbar), Some(scroller_grabbed_at)) =
@ -242,6 +279,8 @@ where
bounds, bounds,
content_bounds, content_bounds,
); );
return event::Status::Captured;
} }
} }
_ => {} _ => {}
@ -266,6 +305,8 @@ where
self.state.scroller_grabbed_at = self.state.scroller_grabbed_at =
Some(scroller_grabbed_at); Some(scroller_grabbed_at);
return event::Status::Captured;
} }
} }
} }
@ -273,28 +314,7 @@ where
} }
} }
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { event::Status::Ignored
Point::new(
cursor_position.x,
cursor_position.y
+ self.state.offset(bounds, content_bounds) as f32,
)
} else {
// TODO: Make `cursor_position` an `Option<Point>` 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,
)
} }
fn draw( fn draw(

View File

@ -4,9 +4,11 @@
//! //!
//! [`Slider`]: struct.Slider.html //! [`Slider`]: struct.Slider.html
//! [`State`]: struct.State.html //! [`State`]: struct.State.html
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::{ use crate::{
layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, Point, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
Rectangle, Size, Widget,
}; };
use std::{hash::Hash, ops::RangeInclusive}; use std::{hash::Hash, ops::RangeInclusive};
@ -202,7 +204,7 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
_renderer: &Renderer, _renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>, _clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
let mut change = || { let mut change = || {
let bounds = layout.bounds(); let bounds = layout.bounds();
if cursor_position.x <= bounds.x { if cursor_position.x <= bounds.x {
@ -232,6 +234,8 @@ where
if layout.bounds().contains(cursor_position) { if layout.bounds().contains(cursor_position) {
change(); change();
self.state.is_dragging = true; self.state.is_dragging = true;
return event::Status::Captured;
} }
} }
mouse::Event::ButtonReleased(mouse::Button::Left) => { mouse::Event::ButtonReleased(mouse::Button::Left) => {
@ -240,17 +244,23 @@ where
messages.push(on_release); messages.push(on_release);
} }
self.state.is_dragging = false; self.state.is_dragging = false;
return event::Status::Captured;
} }
} }
mouse::Event::CursorMoved { .. } => { mouse::Event::CursorMoved { .. } => {
if self.state.is_dragging { if self.state.is_dragging {
change(); change();
return event::Status::Captured;
} }
} }
_ => {} _ => {}
}, },
_ => {} _ => {}
} }
event::Status::Ignored
} }
fn draw( fn draw(

View File

@ -14,11 +14,13 @@ pub use value::Value;
use editor::Editor; use editor::Editor;
use crate::event::{self, Event};
use crate::keyboard;
use crate::layout;
use crate::mouse::{self, click};
use crate::text;
use crate::{ use crate::{
keyboard, layout, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
mouse::{self, click},
text, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle,
Size, Widget,
}; };
use std::u32; use std::u32;
@ -218,11 +220,14 @@ where
messages: &mut Vec<Message>, messages: &mut Vec<Message>,
renderer: &Renderer, renderer: &Renderer,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
) { ) -> event::Status {
match event { match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
let is_clicked = layout.bounds().contains(cursor_position); let is_clicked = layout.bounds().contains(cursor_position);
self.state.is_dragging = is_clicked;
self.state.is_focused = is_clicked;
if is_clicked { if is_clicked {
let text_layout = layout.children().next().unwrap(); let text_layout = layout.children().next().unwrap();
let target = cursor_position.x - text_layout.bounds().x; let target = cursor_position.x - text_layout.bounds().x;
@ -280,10 +285,9 @@ where
} }
self.state.last_click = Some(click); self.state.last_click = Some(click);
}
self.state.is_dragging = is_clicked; return event::Status::Captured;
self.state.is_focused = is_clicked; }
} }
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
self.state.is_dragging = false; self.state.is_dragging = false;
@ -314,6 +318,8 @@ where
position, position,
); );
} }
return event::Status::Captured;
} }
} }
Event::Keyboard(keyboard::Event::CharacterReceived(c)) Event::Keyboard(keyboard::Event::CharacterReceived(c))
@ -328,167 +334,203 @@ where
let message = (self.on_change)(editor.contents()); let message = (self.on_change)(editor.contents());
messages.push(message); messages.push(message);
return event::Status::Captured;
} }
Event::Keyboard(keyboard::Event::KeyPressed { Event::Keyboard(keyboard::Event::KeyPressed {
key_code, key_code,
modifiers, modifiers,
}) if self.state.is_focused => match key_code { }) if self.state.is_focused => {
keyboard::KeyCode::Enter => { match key_code {
if let Some(on_submit) = self.on_submit.clone() { keyboard::KeyCode::Enter => {
messages.push(on_submit); 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);
} }
} }
keyboard::KeyCode::Backspace => {
let mut editor = if platform::is_jump_modifier_pressed(modifiers)
Editor::new(&mut self.value, &mut self.state.cursor); && self
.state
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
.cursor .cursor
.select_range(cursor_pos, self.value.len()); .selection(&self.value)
} else { .is_none()
self.state {
.cursor if self.is_secure {
.select_right_by_words(&self.value); 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 = let mut editor = Editor::new(
Editor::new(&mut self.value, &mut self.state.cursor); &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,
); );
} else {
self.state.cursor.move_to(0); editor.backspace();
let message = (self.on_change)(editor.contents());
messages.push(message);
} }
} keyboard::KeyCode::Delete => {
keyboard::KeyCode::End => { if platform::is_jump_modifier_pressed(modifiers)
if modifiers.shift { && self
self.state.cursor.select_range( .state
self.state.cursor.start(&self.value), .cursor
self.value.len(), .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::Left => {
keyboard::KeyCode::V => { if platform::is_jump_modifier_pressed(modifiers)
if platform::is_copy_paste_modifier_pressed(modifiers) { && !self.is_secure
if let Some(clipboard) = clipboard { {
let content = match self.state.is_pasting.take() { if modifiers.shift {
Some(content) => content, self.state
None => { .cursor
let content: String = clipboard .select_left_by_words(&self.value);
.content() } else {
.unwrap_or(String::new()) self.state
.chars() .cursor
.filter(|c| !c.is_control()) .move_left_by_words(&self.value);
.collect(); }
} else if modifiers.shift {
Value::new(&content) self.state.cursor.select_left(&self.value)
} } else {
}; self.state.cursor.move_left(&self.value);
}
let mut editor = Editor::new( }
&mut self.value, keyboard::KeyCode::Right => {
&mut self.state.cursor, 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,
); );
} else {
editor.paste(content.clone()); self.state.cursor.move_to(0);
let message = (self.on_change)(editor.contents());
messages.push(message);
self.state.is_pasting = Some(content);
} }
} 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; self.state.is_pasting = None;
} }
_ => {}
} }
keyboard::KeyCode::A => {
if platform::is_copy_paste_modifier_pressed(modifiers) { return event::Status::Captured;
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;
}
_ => {}
},
Event::Keyboard(keyboard::Event::KeyReleased { Event::Keyboard(keyboard::Event::KeyReleased {
key_code, .. key_code, ..
}) => match key_code { }) if self.state.is_focused => {
keyboard::KeyCode::V => { match key_code {
self.state.is_pasting = None; keyboard::KeyCode::V => {
self.state.is_pasting = None;
}
_ => {}
} }
_ => {}
}, return event::Status::Captured;
}
_ => {} _ => {}
} }
event::Status::Ignored
} }
fn draw( fn draw(

View File

@ -242,29 +242,33 @@ async fn run_instance<A, E, C>(
let mut mouse_interaction = mouse::Interaction::default(); let mut mouse_interaction = mouse::Interaction::default();
let mut events = Vec::new(); let mut events = Vec::new();
let mut external_messages = Vec::new(); let mut messages = Vec::new();
debug.startup_finished(); debug.startup_finished();
while let Some(event) = receiver.next().await { while let Some(event) = receiver.next().await {
match event { match event {
event::Event::MainEventsCleared => { event::Event::MainEventsCleared => {
if events.is_empty() && external_messages.is_empty() { if events.is_empty() && messages.is_empty() {
continue; continue;
} }
debug.event_processing_started(); debug.event_processing_started();
let mut messages = user_interface.update(
let statuses = user_interface.update(
&events, &events,
state.cursor_position(), state.cursor_position(),
clipboard.as_ref().map(|c| c as _), clipboard.as_ref().map(|c| c as _),
&mut renderer, &mut renderer,
&mut messages,
); );
messages.extend(external_messages.drain(..));
events.clear();
debug.event_processing_finished(); debug.event_processing_finished();
for event in events.drain(..).zip(statuses.into_iter()) {
runtime.broadcast(event);
}
if !messages.is_empty() { if !messages.is_empty() {
let cache = let cache =
ManuallyDrop::into_inner(user_interface).into_cache(); ManuallyDrop::into_inner(user_interface).into_cache();
@ -274,7 +278,7 @@ async fn run_instance<A, E, C>(
&mut application, &mut application,
&mut runtime, &mut runtime,
&mut debug, &mut debug,
messages, &mut messages,
); );
// Update window // Update window
@ -297,7 +301,7 @@ async fn run_instance<A, E, C>(
window.request_redraw(); window.request_redraw();
} }
event::Event::UserEvent(message) => { event::Event::UserEvent(message) => {
external_messages.push(message); messages.push(message);
} }
event::Event::RedrawRequested(_) => { event::Event::RedrawRequested(_) => {
debug.render_started(); debug.render_started();
@ -365,8 +369,7 @@ async fn run_instance<A, E, C>(
state.scale_factor(), state.scale_factor(),
state.modifiers(), state.modifiers(),
) { ) {
events.push(event.clone()); events.push(event);
runtime.broadcast(event);
} }
} }
_ => {} _ => {}
@ -437,9 +440,9 @@ pub fn update<A: Application, E: Executor>(
application: &mut A, application: &mut A,
runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>, runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>,
debug: &mut Debug, debug: &mut Debug,
messages: Vec<A::Message>, messages: &mut Vec<A::Message>,
) { ) {
for message in messages { for message in messages.drain(..) {
debug.log_message(&message); debug.log_message(&message);
debug.update_started(); debug.update_started();