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 {
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,17 +110,24 @@ mod bezier {
event: Event,
bounds: Rectangle,
cursor: Cursor,
) -> Option<Curve> {
let cursor_position = cursor.position_in(&bounds)?;
) -> (event::Status, Option<Curve>) {
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 {
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::One { from }) => {
@ -142,8 +150,11 @@ mod bezier {
}
}
_ => None,
},
_ => None,
};
(event::Status::Captured, message)
}
_ => (event::Status::Ignored, None),
}
}

View File

@ -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<Message> {
) -> (event::Status, Option<Message>) {
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,7 +350,8 @@ mod grid {
match event {
Event::Mouse(mouse_event) => match mouse_event {
mouse::Event::ButtonPressed(button) => match button {
mouse::Event::ButtonPressed(button) => {
let message = match button {
mouse::Button::Left => {
self.interaction = if is_populated {
Interaction::Erasing
@ -364,9 +370,12 @@ mod grid {
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),
}
}

View File

@ -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<Message> {
subscription::events_with(|event| match event {
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,
}
})
}

View File

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

View File

@ -7,18 +7,20 @@
//! [`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;
pub mod event;
pub mod path;
mod cache;
mod cursor;
mod event;
mod fill;
mod frame;
mod geometry;
@ -166,7 +168,7 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer<B>,
_clipboard: Option<&dyn Clipboard>,
) {
) -> event::Status {
let bounds = layout.bounds();
let canvas_event = match event {
@ -182,12 +184,17 @@ 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
}
fn draw(

View File

@ -1,6 +1,9 @@
//! Handle events of a canvas.
use iced_native::keyboard;
use iced_native::mouse;
pub use iced_native::event::Status;
/// A [`Canvas`] event.
///
/// [`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};
/// The state and logic of a [`Canvas`].
@ -27,8 +28,8 @@ pub trait Program<Message> {
_event: Event,
_bounds: Rectangle,
_cursor: Cursor,
) -> Option<Message> {
None
) -> (event::Status, Option<Message>) {
(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<Message> {
) -> (event::Status, Option<Message>) {
T::update(self, event, bounds, cursor)
}

View File

@ -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<Message>,
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<B>,
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<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) {
) -> event::Status {
self.element.widget.on_event(
event,
layout,

View File

@ -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,41 @@ pub enum Event {
/// A 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)]
#![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;

View File

@ -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<Message, Renderer>
@ -79,6 +81,7 @@ where
_messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
) {
) -> event::Status {
event::Status::Ignored
}
}

View File

@ -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<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) {
) -> event::Status {
self.overlay.on_event(
event,
layout,
@ -136,10 +138,10 @@ where
messages: &mut Vec<B>,
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(

View File

@ -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.
@ -235,7 +241,7 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) {
) -> event::Status {
self.container.on_event(
event.clone(),
layout,
@ -243,7 +249,7 @@ where
messages,
renderer,
clipboard,
);
)
}
fn draw(
@ -336,7 +342,7 @@ where
_messages: &mut Vec<Message>,
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(

View File

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

View File

@ -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<Executor, Receiver, Message> =
iced_futures::Runtime<Hasher, Event, Executor, Receiver, Message>;
pub type Runtime<Executor, Receiver, Message> = iced_futures::Runtime<
Hasher,
(Event, event::Status),
Executor,
Receiver,
Message,
>;

View File

@ -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<T> = iced_futures::Subscription<Hasher, Event, T>;
pub type Subscription<T> =
iced_futures::Subscription<Hasher, (Event, event::Status), T>;
/// 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<Hasher, Event>;
pub type Tracker =
iced_futures::subscription::Tracker<Hasher, (Event, event::Status)>;
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<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
@ -58,7 +66,7 @@ pub fn events() -> Subscription<Event> {
/// [`Subscription`]: type.Subscription.html
/// [`Event`]: ../enum.Event.html
pub fn events_with<Message>(
f: fn(Event) -> Option<Message>,
f: fn(Event, event::Status) -> Option<Message>,
) -> Subscription<Message>
where
Message: 'static + Send,

View File

@ -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<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
Message: 'static + Send,
{
@ -29,7 +28,9 @@ where
event_stream: EventStream,
) -> BoxStream<Self::Output> {
event_stream
.filter_map(move |event| future::ready((self.f)(event)))
.filter_map(move |(event, status)| {
future::ready((self.f)(event, status))
})
.boxed()
}
}

View File

@ -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(),
@ -181,17 +183,18 @@ where
/// );
///
/// // Update the user interface
/// let messages = user_interface.update(
/// let event_statuses = user_interface.update(
/// &events,
/// 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);
/// }
/// }
@ -202,10 +205,9 @@ where
cursor_position: Point,
clipboard: Option<&dyn Clipboard>,
renderer: &Renderer,
) -> Vec<Message> {
let mut messages = Vec::new();
let base_cursor = if let Some(mut overlay) =
messages: &mut Vec<Message>,
) -> Vec<event::Status> {
let (base_cursor, overlay_statuses) = if let Some(mut overlay) =
self.root.overlay(Layout::new(&self.base.layout))
{
let layer = Self::overlay_layer(
@ -215,16 +217,20 @@ where
renderer,
);
for event in events {
let event_statuses = events
.iter()
.cloned()
.map(|event| {
overlay.on_event(
event.clone(),
event,
Layout::new(&layer.layout),
cursor_position,
&mut messages,
messages,
renderer,
clipboard,
);
}
)
})
.collect();
let base_cursor = if layer.layout.bounds().contains(cursor_position)
{
@ -236,23 +242,28 @@ where
self.overlay = Some(layer);
base_cursor
(base_cursor, event_statuses)
} else {
cursor_position
(cursor_position, vec![event::Status::Ignored; events.len()])
};
for event in events {
self.root.widget.on_event(
event.clone(),
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,
&mut messages,
messages,
renderer,
clipboard,
);
}
messages
event_status.merge(overlay_status)
})
.collect()
}
/// Draws the [`UserInterface`] with the provided [`Renderer`].
@ -293,9 +304,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,11 +316,13 @@ where
/// &mut renderer,
/// );
///
/// let messages = user_interface.update(
/// // Update the user interface
/// let event_statuses = user_interface.update(
/// &events,
/// cursor_position,
/// None,
/// &renderer,
/// &mut messages
/// );
///
/// // Draw the user interface
@ -316,7 +330,7 @@ where
///
/// cache = user_interface.into_cache();
///
/// for message in messages {
/// for message in messages.drain(..) {
/// counter.update(message);
/// }
///

View File

@ -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<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
) {
) -> event::Status {
event::Status::Ignored
}
/// Returns the overlay of the [`Element`], if there is any.

View File

@ -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,31 +186,38 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
) {
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
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;
if is_clicked {
if bounds.contains(cursor_position) {
messages.push(on_press);
}
return event::Status::Captured;
}
}
}
_ => {}
}
event::Status::Ignored
}
fn draw(

View File

@ -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,17 +165,21 @@ where
messages: &mut Vec<Message>,
_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);
if mouse_over {
messages.push((self.on_toggle)(!self.is_checked));
return event::Status::Captured;
}
}
_ => {}
}
event::Status::Ignored
}
fn draw(

View File

@ -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,9 +162,11 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) {
self.children.iter_mut().zip(layout.children()).for_each(
|(child, layout)| {
) -> event::Status {
self.children
.iter_mut()
.zip(layout.children())
.map(|(child, layout)| {
child.widget.on_event(
event.clone(),
layout,
@ -173,8 +175,8 @@ where
renderer,
clipboard,
)
},
);
})
.fold(event::Status::Ignored, event::Status::merge)
}
fn draw(

View File

@ -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<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) {
) -> event::Status {
self.content.widget.on_event(
event,
layout.children().next().unwrap(),

View File

@ -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
@ -242,7 +249,7 @@ where
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
) {
) -> event::Status {
if let Some((_, on_resize)) = &self.on_resize {
if let Some((split, _)) = self.state.picked_split() {
let bounds = layout.bounds();
@ -269,9 +276,13 @@ where
};
messages.push(on_resize(ResizeEvent { split, ratio }));
return event::Status::Captured;
}
}
}
event::Status::Ignored
}
}
@ -386,13 +397,17 @@ where
messages: &mut Vec<Message>,
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(
@ -456,11 +471,16 @@ 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 { .. } => {
event_status =
self.trigger_resize(layout, cursor_position, messages);
}
_ => {}
@ -469,9 +489,10 @@ where
}
if self.state.picked_pane().is_none() {
{
self.elements.iter_mut().zip(layout.children()).for_each(
|((_, pane), layout)| {
self.elements
.iter_mut()
.zip(layout.children())
.map(|((_, pane), layout)| {
pane.on_event(
event.clone(),
layout,
@ -480,9 +501,10 @@ where
renderer,
clipboard,
)
},
);
}
})
.fold(event_status, event::Status::merge)
} else {
event::Status::Captured
}
}

View File

@ -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<Message>,
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
};
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) {

View File

@ -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<Message>,
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();
@ -261,7 +260,9 @@ where
messages,
renderer,
clipboard,
);
)
} else {
event::Status::Ignored
}
}
}

View File

@ -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,13 +227,15 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
) {
) -> 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();
@ -238,15 +244,23 @@ 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,
}
}

View File

@ -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,15 +170,19 @@ where
messages: &mut Vec<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
) {
) -> event::Status {
match event {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
if layout.bounds().contains(cursor_position) {
messages.push(self.on_click.clone());
return event::Status::Captured;
}
}
_ => {}
}
event::Status::Ignored
}
fn draw(

View File

@ -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,9 +162,11 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) {
self.children.iter_mut().zip(layout.children()).for_each(
|(child, layout)| {
) -> event::Status {
self.children
.iter_mut()
.zip(layout.children())
.map(|(child, layout)| {
child.widget.on_event(
event.clone(),
layout,
@ -173,8 +175,8 @@ where
renderer,
clipboard,
)
},
);
})
.fold(event::Status::Ignored, event::Status::merge)
}
fn draw(

View File

@ -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,31 +189,13 @@ where
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) {
) -> event::Status {
let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position);
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,
@ -223,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<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() {
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)) =
@ -242,6 +279,8 @@ where
bounds,
content_bounds,
);
return event::Status::Captured;
}
}
_ => {}
@ -266,6 +305,8 @@ where
self.state.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 {
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,
)
event::Status::Ignored
}
fn draw(

View File

@ -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<Message>,
_renderer: &Renderer,
_clipboard: Option<&dyn Clipboard>,
) {
) -> event::Status {
let mut change = || {
let bounds = layout.bounds();
if cursor_position.x <= bounds.x {
@ -232,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) => {
@ -240,17 +244,23 @@ 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;
}
}
_ => {}
},
_ => {}
}
event::Status::Ignored
}
fn draw(

View File

@ -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,11 +220,14 @@ where
messages: &mut Vec<Message>,
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);
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;
@ -280,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;
@ -314,6 +318,8 @@ where
position,
);
}
return event::Status::Captured;
}
}
Event::Keyboard(keyboard::Event::CharacterReceived(c))
@ -328,11 +334,14 @@ 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 {
}) if self.state.is_focused => {
match key_code {
keyboard::KeyCode::Enter => {
if let Some(on_submit) = self.on_submit.clone() {
messages.push(on_submit);
@ -340,18 +349,27 @@ where
}
keyboard::KeyCode::Backspace => {
if platform::is_jump_modifier_pressed(modifiers)
&& self.state.cursor.selection(&self.value).is_none()
&& self
.state
.cursor
.selection(&self.value)
.is_none()
{
if self.is_secure {
let cursor_pos = self.state.cursor.end(&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);
self.state
.cursor
.select_left_by_words(&self.value);
}
}
let mut editor =
Editor::new(&mut self.value, &mut self.state.cursor);
let mut editor = Editor::new(
&mut self.value,
&mut self.state.cursor,
);
editor.backspace();
@ -360,10 +378,15 @@ where
}
keyboard::KeyCode::Delete => {
if platform::is_jump_modifier_pressed(modifiers)
&& self.state.cursor.selection(&self.value).is_none()
&& self
.state
.cursor
.selection(&self.value)
.is_none()
{
if self.is_secure {
let cursor_pos = self.state.cursor.end(&self.value);
let cursor_pos =
self.state.cursor.end(&self.value);
self.state
.cursor
.select_range(cursor_pos, self.value.len());
@ -374,8 +397,10 @@ where
}
}
let mut editor =
Editor::new(&mut self.value, &mut self.state.cursor);
let mut editor = Editor::new(
&mut self.value,
&mut self.state.cursor,
);
editor.delete();
@ -387,9 +412,13 @@ where
&& !self.is_secure
{
if modifiers.shift {
self.state.cursor.select_left_by_words(&self.value);
self.state
.cursor
.select_left_by_words(&self.value);
} else {
self.state.cursor.move_left_by_words(&self.value);
self.state
.cursor
.move_left_by_words(&self.value);
}
} else if modifiers.shift {
self.state.cursor.select_left(&self.value)
@ -406,7 +435,9 @@ where
.cursor
.select_right_by_words(&self.value);
} else {
self.state.cursor.move_right_by_words(&self.value);
self.state
.cursor
.move_right_by_words(&self.value);
}
} else if modifiers.shift {
self.state.cursor.select_right(&self.value)
@ -437,7 +468,8 @@ where
keyboard::KeyCode::V => {
if platform::is_copy_paste_modifier_pressed(modifiers) {
if let Some(clipboard) = clipboard {
let content = match self.state.is_pasting.take() {
let content = match self.state.is_pasting.take()
{
Some(content) => content,
None => {
let content: String = clipboard
@ -458,7 +490,8 @@ where
editor.paste(content.clone());
let message = (self.on_change)(editor.contents());
let message =
(self.on_change)(editor.contents());
messages.push(message);
self.state.is_pasting = Some(content);
@ -478,17 +511,26 @@ where
self.state.is_pasting = None;
}
_ => {}
},
}
return event::Status::Captured;
}
Event::Keyboard(keyboard::Event::KeyReleased {
key_code, ..
}) => match key_code {
}) if self.state.is_focused => {
match key_code {
keyboard::KeyCode::V => {
self.state.is_pasting = None;
}
_ => {}
},
}
return event::Status::Captured;
}
_ => {}
}
event::Status::Ignored
}
fn draw(

View File

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