diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 7f3a5e1d5f..32d0da2c8c 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,9 +1,8 @@ use iced::alignment; +use iced::event::{self, Event}; use iced::executor; -use iced::subscription; use iced::widget::{button, checkbox, container, text, Column}; use iced::window; -use iced::Event; use iced::{ Alignment, Application, Command, Element, Length, Settings, Subscription, Theme, @@ -71,7 +70,7 @@ impl Application for Events { } fn subscription(&self) -> Subscription { - subscription::events().map(Message::EventOccurred) + event::listen().map(Message::EventOccurred) } fn view(&self) -> Element { diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 8a48f83087..4aa708869c 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -1,12 +1,14 @@ +use iced::event::{self, Event}; use iced::executor; use iced::keyboard; -use iced::subscription::{self, Subscription}; use iced::theme; use iced::widget::{ self, button, column, container, horizontal_space, pick_list, row, text, text_input, }; -use iced::{Alignment, Application, Command, Element, Event, Length, Settings}; +use iced::{ + Alignment, Application, Command, Element, Length, Settings, Subscription, +}; use modal::Modal; use std::fmt; @@ -49,7 +51,7 @@ impl Application for App { } fn subscription(&self) -> Subscription { - subscription::events().map(Message::Event) + event::listen().map(Message::Event) } fn update(&mut self, message: Message) -> Command { diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 04896e2063..af87e2c093 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,8 +1,6 @@ use iced::alignment::{self, Alignment}; -use iced::event::{self, Event}; use iced::executor; use iced::keyboard; -use iced::subscription; use iced::theme::{self, Theme}; use iced::widget::pane_grid::{self, PaneGrid}; use iced::widget::{ @@ -146,18 +144,12 @@ impl Application for Example { } fn subscription(&self) -> Subscription { - subscription::events_with(|event, status| { - if let event::Status::Captured = status { + keyboard::on_key_press(|key_code, modifiers| { + if !modifiers.command() { return None; } - match event { - Event::Keyboard(keyboard::Event::KeyPressed { - modifiers, - key_code, - }) if modifiers.command() => handle_hotkey(key_code), - _ => None, - } + handle_hotkey(key_code) }) } diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 838245350e..54446724fc 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -4,9 +4,8 @@ use iced::theme::{Button, Container}; use iced::widget::{button, column, container, image, row, text, text_input}; use iced::window::screenshot::{self, Screenshot}; use iced::{ - event, executor, keyboard, subscription, Alignment, Application, Command, - ContentFit, Element, Event, Length, Rectangle, Renderer, Subscription, - Theme, + event, executor, keyboard, Alignment, Application, Command, ContentFit, + Element, Event, Length, Rectangle, Renderer, Subscription, Theme, }; use ::image as img; @@ -254,7 +253,7 @@ impl Application for Example { } fn subscription(&self) -> Subscription { - subscription::events_with(|event, status| { + event::listen_with(|event, status| { if let event::Status::Captured = status { return None; } diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 842ba3d490..0b0f060727 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,5 +1,6 @@ use iced::alignment; use iced::executor; +use iced::keyboard; use iced::theme::{self, Theme}; use iced::time; use iced::widget::{button, column, container, row, text}; @@ -77,12 +78,25 @@ impl Application for Stopwatch { } fn subscription(&self) -> Subscription { - match self.state { + let tick = match self.state { State::Idle => Subscription::none(), State::Ticking { .. } => { time::every(Duration::from_millis(10)).map(Message::Tick) } + }; + + fn handle_hotkey( + key_code: keyboard::KeyCode, + _modifiers: keyboard::Modifiers, + ) -> Option { + match key_code { + keyboard::KeyCode::Space => Some(Message::Toggle), + keyboard::KeyCode::R => Some(Message::Reset), + _ => None, + } } + + Subscription::batch(vec![tick, keyboard::on_key_press(handle_hotkey)]) } fn view(&self) -> Element { diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 42f6c34840..47b272a95e 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -1,10 +1,12 @@ +use iced::event::{self, Event}; use iced::executor; use iced::keyboard; -use iced::subscription::{self, Subscription}; use iced::widget::{ self, button, column, container, pick_list, row, slider, text, text_input, }; -use iced::{Alignment, Application, Command, Element, Event, Length, Settings}; +use iced::{ + Alignment, Application, Command, Element, Length, Settings, Subscription, +}; use toast::{Status, Toast}; @@ -57,7 +59,7 @@ impl Application for App { } fn subscription(&self) -> Subscription { - subscription::events().map(Message::Event) + event::listen().map(Message::Event) } fn update(&mut self, message: Message) -> Command { diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 6ad7b4fbd3..62c179268d 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,8 +1,6 @@ use iced::alignment::{self, Alignment}; -use iced::event::{self, Event}; use iced::font::{self, Font}; -use iced::keyboard::{self, KeyCode, Modifiers}; -use iced::subscription; +use iced::keyboard; use iced::theme::{self, Theme}; use iced::widget::{ self, button, checkbox, column, container, row, scrollable, text, @@ -52,7 +50,7 @@ enum Message { FilterChanged(Filter), TaskMessage(usize, TaskMessage), TabPressed { shift: bool }, - ToggleFullscreen(window::Mode), + ChangeWindowMode(window::Mode), } impl Application for Todos { @@ -163,7 +161,7 @@ impl Application for Todos { widget::focus_next() } } - Message::ToggleFullscreen(mode) => { + Message::ChangeWindowMode(mode) => { window::change_mode(mode) } _ => Command::none(), @@ -262,33 +260,19 @@ impl Application for Todos { } fn subscription(&self) -> Subscription { - subscription::events_with(|event, status| match (event, status) { - ( - Event::Keyboard(keyboard::Event::KeyPressed { - key_code: keyboard::KeyCode::Tab, - modifiers, - .. + keyboard::on_key_press(|key_code, modifiers| { + match (key_code, modifiers) { + (keyboard::KeyCode::Tab, _) => Some(Message::TabPressed { + shift: modifiers.shift(), }), - event::Status::Ignored, - ) => Some(Message::TabPressed { - shift: modifiers.shift(), - }), - ( - Event::Keyboard(keyboard::Event::KeyPressed { - key_code, - modifiers: Modifiers::SHIFT, - }), - event::Status::Ignored, - ) => match key_code { - KeyCode::Up => { - Some(Message::ToggleFullscreen(window::Mode::Fullscreen)) + (keyboard::KeyCode::Up, keyboard::Modifiers::SHIFT) => { + Some(Message::ChangeWindowMode(window::Mode::Fullscreen)) } - KeyCode::Down => { - Some(Message::ToggleFullscreen(window::Mode::Windowed)) + (keyboard::KeyCode::Down, keyboard::Modifiers::SHIFT) => { + Some(Message::ChangeWindowMode(window::Mode::Windowed)) } _ => None, - }, - _ => None, + } }) } } diff --git a/examples/url_handler/src/main.rs b/examples/url_handler/src/main.rs index f63fa06a24..bf57012319 100644 --- a/examples/url_handler/src/main.rs +++ b/examples/url_handler/src/main.rs @@ -1,6 +1,5 @@ -use iced::event::{Event, MacOS, PlatformSpecific}; +use iced::event::{self, Event}; use iced::executor; -use iced::subscription; use iced::widget::{container, text}; use iced::{ Application, Command, Element, Length, Settings, Subscription, Theme, @@ -37,9 +36,11 @@ impl Application for App { fn update(&mut self, message: Message) -> Command { match message { Message::EventOccurred(event) => { - if let Event::PlatformSpecific(PlatformSpecific::MacOS( - MacOS::ReceivedUrl(url), - )) = event + if let Event::PlatformSpecific( + event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( + url, + )), + ) = event { self.url = Some(url); } @@ -50,7 +51,7 @@ impl Application for App { } fn subscription(&self) -> Subscription { - subscription::events().map(Message::EventOccurred) + event::listen().map(Message::EventOccurred) } fn view(&self) -> Element { diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index 8b68451438..42dfc24c76 100644 --- a/examples/visible_bounds/src/main.rs +++ b/examples/visible_bounds/src/main.rs @@ -1,14 +1,14 @@ +use iced::event::{self, Event}; use iced::executor; use iced::mouse; -use iced::subscription::{self, Subscription}; use iced::theme::{self, Theme}; use iced::widget::{ column, container, horizontal_space, row, scrollable, text, vertical_space, }; use iced::window; use iced::{ - Alignment, Application, Color, Command, Element, Event, Font, Length, - Point, Rectangle, Settings, + Alignment, Application, Color, Command, Element, Font, Length, Point, + Rectangle, Settings, Subscription, }; pub fn main() -> iced::Result { @@ -163,7 +163,7 @@ impl Application for Example { } fn subscription(&self) -> Subscription { - subscription::events_with(|event, _| match event { + event::listen_with(|event, _| match event { Event::Mouse(mouse::Event::CursorMoved { position }) => { Some(Message::MouseMoved(position)) } diff --git a/futures/src/event.rs b/futures/src/event.rs new file mode 100644 index 0000000000..214d2d406d --- /dev/null +++ b/futures/src/event.rs @@ -0,0 +1,59 @@ +//! Listen to runtime events. +use crate::core::event::{self, Event}; +use crate::core::window; +use crate::subscription::{self, Subscription}; +use crate::MaybeSend; + +/// Returns a [`Subscription`] to all the ignored runtime events. +/// +/// This subscription will notify your application of any [`Event`] that was +/// not captured by any widget. +pub fn listen() -> Subscription { + listen_with(|event, status| match status { + event::Status::Ignored => Some(event), + event::Status::Captured => None, + }) +} + +/// Creates a [`Subscription`] that listens and filters all the runtime events +/// with the provided function, producing messages accordingly. +/// +/// This subscription will call the provided function for every [`Event`] +/// handled by the runtime. If the function: +/// +/// - Returns `None`, the [`Event`] will be discarded. +/// - Returns `Some` message, the `Message` will be produced. +pub fn listen_with( + f: fn(Event, event::Status) -> Option, +) -> Subscription +where + Message: 'static + MaybeSend, +{ + #[derive(Hash)] + struct EventsWith; + + subscription::filter_map( + (EventsWith, f), + move |event, status| match event { + Event::Window(window::Event::RedrawRequested(_)) => None, + _ => f(event, status), + }, + ) +} + +/// Creates a [`Subscription`] that produces a message for every runtime event, +/// including the redraw request events. +/// +/// **Warning:** This [`Subscription`], if unfiltered, may produce messages in +/// an infinite loop. +pub fn listen_raw( + f: fn(Event, event::Status) -> Option, +) -> Subscription +where + Message: 'static + MaybeSend, +{ + #[derive(Hash)] + struct RawEvents; + + subscription::filter_map((RawEvents, f), f) +} diff --git a/futures/src/keyboard.rs b/futures/src/keyboard.rs new file mode 100644 index 0000000000..af68e1f26e --- /dev/null +++ b/futures/src/keyboard.rs @@ -0,0 +1,61 @@ +//! Listen to keyboard events. +use crate::core; +use crate::core::keyboard::{Event, KeyCode, Modifiers}; +use crate::subscription::{self, Subscription}; +use crate::MaybeSend; + +/// Listens to keyboard key presses and calls the given function +/// map them into actual messages. +/// +/// If the function returns `None`, the key press will be simply +/// ignored. +pub fn on_key_press( + f: fn(KeyCode, Modifiers) -> Option, +) -> Subscription +where + Message: MaybeSend + 'static, +{ + #[derive(Hash)] + struct OnKeyPress; + + subscription::filter_map((OnKeyPress, f), move |event, status| { + match (event, status) { + ( + core::Event::Keyboard(Event::KeyPressed { + key_code, + modifiers, + }), + core::event::Status::Ignored, + ) => f(key_code, modifiers), + _ => None, + } + }) +} + +/// Listens to keyboard key releases and calls the given function +/// map them into actual messages. +/// +/// If the function returns `None`, the key release will be simply +/// ignored. +pub fn on_key_release( + f: fn(KeyCode, Modifiers) -> Option, +) -> Subscription +where + Message: MaybeSend + 'static, +{ + #[derive(Hash)] + struct OnKeyRelease; + + subscription::filter_map((OnKeyRelease, f), move |event, status| { + match (event, status) { + ( + core::Event::Keyboard(Event::KeyReleased { + key_code, + modifiers, + }), + core::event::Status::Ignored, + ) => f(key_code, modifiers), + _ => None, + } + }) +} diff --git a/futures/src/lib.rs b/futures/src/lib.rs index 34d81e1ece..8e75331f77 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -24,7 +24,9 @@ mod maybe_send; mod runtime; pub mod backend; +pub mod event; pub mod executor; +pub mod keyboard; pub mod subscription; pub use executor::Executor; diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 8f78ce3acb..759dd22322 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -4,7 +4,6 @@ mod tracker; pub use tracker::Tracker; use crate::core::event::{self, Event}; -use crate::core::window; use crate::core::Hasher; use crate::futures::{Future, Stream}; use crate::{BoxStream, MaybeSend}; @@ -215,77 +214,6 @@ where } } -/// Returns a [`Subscription`] to all the ignored runtime events. -/// -/// This subscription will notify your application of any [`Event`] that was -/// not captured by any widget. -pub fn events() -> Subscription { - events_with(|event, status| match status { - event::Status::Ignored => Some(event), - event::Status::Captured => None, - }) -} - -/// Returns a [`Subscription`] that filters all the runtime events with the -/// provided function, producing messages accordingly. -/// -/// This subscription will call the provided function for every [`Event`] -/// handled by the runtime. If the function: -/// -/// - Returns `None`, the [`Event`] will be discarded. -/// - Returns `Some` message, the `Message` will be produced. -pub fn events_with( - f: fn(Event, event::Status) -> Option, -) -> Subscription -where - Message: 'static + MaybeSend, -{ - #[derive(Hash)] - struct EventsWith; - - Subscription::from_recipe(Runner { - id: (EventsWith, f), - spawn: move |events| { - use futures::future; - use futures::stream::StreamExt; - - events.filter_map(move |(event, status)| { - future::ready(match event { - Event::Window(window::Event::RedrawRequested(_)) => None, - _ => f(event, status), - }) - }) - }, - }) -} - -/// Returns a [`Subscription`] that produces a message for every runtime event, -/// including the redraw request events. -/// -/// **Warning:** This [`Subscription`], if unfiltered, may produce messages in -/// an infinite loop. -pub fn raw_events( - f: fn(Event, event::Status) -> Option, -) -> Subscription -where - Message: 'static + MaybeSend, -{ - #[derive(Hash)] - struct RawEvents; - - Subscription::from_recipe(Runner { - id: (RawEvents, f), - spawn: move |events| { - use futures::future; - use futures::stream::StreamExt; - - events.filter_map(move |(event, status)| { - future::ready(f(event, status)) - }) - }, - }) -} - /// Returns a [`Subscription`] that will call the given function to create and /// asynchronously run the given [`Stream`]. pub fn run(builder: fn() -> S) -> Subscription @@ -338,6 +266,25 @@ where ) } +pub(crate) fn filter_map(id: I, f: F) -> Subscription +where + I: Hash + 'static, + F: Fn(Event, event::Status) -> Option + MaybeSend + 'static, + Message: 'static + MaybeSend, +{ + Subscription::from_recipe(Runner { + id, + spawn: |events| { + use futures::future; + use futures::stream::StreamExt; + + events.filter_map(move |(event, status)| { + future::ready(f(event, status)) + }) + }, + }) +} + /// Creates a [`Subscription`] that publishes the events sent from a [`Future`] /// to an [`mpsc::Sender`] with the given bounds. /// diff --git a/runtime/src/window.rs b/runtime/src/window.rs index 5219fbfdec..41816967d7 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -10,7 +10,8 @@ use crate::command::{self, Command}; use crate::core::time::Instant; use crate::core::window::{Event, Icon, Level, Mode, UserAttention}; use crate::core::Size; -use crate::futures::subscription::{self, Subscription}; +use crate::futures::event; +use crate::futures::Subscription; /// Subscribes to the frames of the window of the running application. /// @@ -21,7 +22,7 @@ use crate::futures::subscription::{self, Subscription}; /// In any case, this [`Subscription`] is useful to smoothly draw application-driven /// animations without missing any frames. pub fn frames() -> Subscription { - subscription::raw_events(|event, _status| match event { + event::listen_raw(|event, _status| match event { iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at), _ => None, }) diff --git a/src/lib.rs b/src/lib.rs index 36f48ba283..840802cc0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,7 +187,6 @@ pub mod advanced; pub use style::theme; pub use crate::core::alignment; -pub use crate::core::event; pub use crate::core::gradient; pub use crate::core::{ color, Alignment, Background, BorderRadius, Color, ContentFit, Degrees, @@ -223,9 +222,16 @@ pub mod font { pub use crate::runtime::font::*; } +pub mod event { + //! Handle events of a user interface. + pub use crate::core::event::{Event, MacOS, PlatformSpecific, Status}; + pub use iced_futures::event::{listen, listen_raw, listen_with}; +} + pub mod keyboard { //! Listen and react to keyboard events. pub use crate::core::keyboard::{Event, KeyCode, Modifiers}; + pub use iced_futures::keyboard::{on_key_press, on_key_release}; } pub mod mouse { @@ -238,7 +244,7 @@ pub mod mouse { pub mod subscription { //! Listen to external events in your application. pub use iced_futures::subscription::{ - channel, events, events_with, run, run_with_id, unfold, Subscription, + channel, run, run_with_id, unfold, Subscription, }; }