From 7354f68b3ca345767de3f09dccddf168493977bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Jan 2023 02:59:08 +0100 Subject: [PATCH 01/11] Draft `Shell:request_redraw` API ... and implement `TextInput` cursor blink :tada: --- examples/events/Cargo.toml | 2 +- examples/events/src/main.rs | 24 ++++---- examples/exit/src/main.rs | 26 ++++---- native/src/renderer.rs | 6 +- native/src/shell.rs | 60 +++++++++++++------ native/src/subscription.rs | 8 ++- native/src/user_interface.rs | 46 ++++++++++++-- native/src/widget/text_input.rs | 102 ++++++++++++++++++++++++-------- native/src/window/event.rs | 6 ++ src/application.rs | 7 --- src/sandbox.rs | 11 ---- winit/src/application.rs | 72 ++++++++++++++++++---- 12 files changed, 263 insertions(+), 107 deletions(-) diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml index 8ad04a36a4..8c56e4711f 100644 --- a/examples/events/Cargo.toml +++ b/examples/events/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../.." } +iced = { path = "../..", features = ["debug"] } iced_native = { path = "../../native" } diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 234e14239d..4ae8d6fb5d 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -1,11 +1,12 @@ use iced::alignment; use iced::executor; use iced::widget::{button, checkbox, container, text, Column}; +use iced::window; use iced::{ Alignment, Application, Command, Element, Length, Settings, Subscription, Theme, }; -use iced_native::{window, Event}; +use iced_native::Event; pub fn main() -> iced::Result { Events::run(Settings { @@ -18,7 +19,6 @@ pub fn main() -> iced::Result { struct Events { last: Vec, enabled: bool, - should_exit: bool, } #[derive(Debug, Clone)] @@ -50,31 +50,29 @@ impl Application for Events { if self.last.len() > 5 { let _ = self.last.remove(0); } + + Command::none() } Message::EventOccurred(event) => { if let Event::Window(window::Event::CloseRequested) = event { - self.should_exit = true; + window::close() + } else { + Command::none() } } Message::Toggled(enabled) => { self.enabled = enabled; - } - Message::Exit => { - self.should_exit = true; - } - }; - Command::none() + Command::none() + } + Message::Exit => window::close(), + } } fn subscription(&self) -> Subscription { iced_native::subscription::events().map(Message::EventOccurred) } - fn should_exit(&self) -> bool { - self.should_exit - } - fn view(&self) -> Element { let events = Column::with_children( self.last diff --git a/examples/exit/src/main.rs b/examples/exit/src/main.rs index 5d518d2fd0..6152f62706 100644 --- a/examples/exit/src/main.rs +++ b/examples/exit/src/main.rs @@ -1,5 +1,7 @@ +use iced::executor; use iced::widget::{button, column, container}; -use iced::{Alignment, Element, Length, Sandbox, Settings}; +use iced::window; +use iced::{Alignment, Application, Command, Element, Length, Settings, Theme}; pub fn main() -> iced::Result { Exit::run(Settings::default()) @@ -8,7 +10,6 @@ pub fn main() -> iced::Result { #[derive(Default)] struct Exit { show_confirm: bool, - exit: bool, } #[derive(Debug, Clone, Copy)] @@ -17,28 +18,27 @@ enum Message { Exit, } -impl Sandbox for Exit { +impl Application for Exit { + type Executor = executor::Default; type Message = Message; + type Theme = Theme; + type Flags = (); - fn new() -> Self { - Self::default() + fn new(_flags: ()) -> (Self, Command) { + (Self::default(), Command::none()) } fn title(&self) -> String { String::from("Exit - Iced") } - fn should_exit(&self) -> bool { - self.exit - } - - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Command { match message { - Message::Confirm => { - self.exit = true; - } + Message::Confirm => window::close(), Message::Exit => { self.show_confirm = true; + + Command::none() } } } diff --git a/native/src/renderer.rs b/native/src/renderer.rs index 5e776be6cd..d5329acd8a 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -36,11 +36,11 @@ pub trait Renderer: Sized { f: impl FnOnce(&mut Self), ); - /// Clears all of the recorded primitives in the [`Renderer`]. - fn clear(&mut self); - /// Fills a [`Quad`] with the provided [`Background`]. fn fill_quad(&mut self, quad: Quad, background: impl Into); + + /// Clears all of the recorded primitives in the [`Renderer`]. + fn clear(&mut self); } /// A polygon with four sides. diff --git a/native/src/shell.rs b/native/src/shell.rs index b96d23e5aa..81d2a0e6ac 100644 --- a/native/src/shell.rs +++ b/native/src/shell.rs @@ -1,3 +1,5 @@ +use std::time::Instant; + /// A connection to the state of a shell. /// /// A [`Widget`] can leverage a [`Shell`] to trigger changes in an application, @@ -7,6 +9,7 @@ #[derive(Debug)] pub struct Shell<'a, Message> { messages: &'a mut Vec, + redraw_requested_at: Option, is_layout_invalid: bool, are_widgets_invalid: bool, } @@ -16,31 +19,40 @@ impl<'a, Message> Shell<'a, Message> { pub fn new(messages: &'a mut Vec) -> Self { Self { messages, + redraw_requested_at: None, is_layout_invalid: false, are_widgets_invalid: false, } } - /// Triggers the given function if the layout is invalid, cleaning it in the - /// process. - pub fn revalidate_layout(&mut self, f: impl FnOnce()) { - if self.is_layout_invalid { - self.is_layout_invalid = false; + /// Publish the given `Message` for an application to process it. + pub fn publish(&mut self, message: Message) { + self.messages.push(message); + } - f() + /// Requests a new frame to be drawn at the given [`Instant`]. + pub fn request_redraw(&mut self, at: Instant) { + match self.redraw_requested_at { + None => { + self.redraw_requested_at = Some(at); + } + Some(current) if at < current => { + self.redraw_requested_at = Some(at); + } + _ => {} } } + /// Returns the requested [`Instant`] a redraw should happen, if any. + pub fn redraw_requested_at(&self) -> Option { + self.redraw_requested_at + } + /// Returns whether the current layout is invalid or not. pub fn is_layout_invalid(&self) -> bool { self.is_layout_invalid } - /// Publish the given `Message` for an application to process it. - pub fn publish(&mut self, message: Message) { - self.messages.push(message); - } - /// Invalidates the current application layout. /// /// The shell will relayout the application widgets. @@ -48,6 +60,22 @@ impl<'a, Message> Shell<'a, Message> { self.is_layout_invalid = true; } + /// Triggers the given function if the layout is invalid, cleaning it in the + /// process. + pub fn revalidate_layout(&mut self, f: impl FnOnce()) { + if self.is_layout_invalid { + self.is_layout_invalid = false; + + f() + } + } + + /// Returns whether the widgets of the current application have been + /// invalidated. + pub fn are_widgets_invalid(&self) -> bool { + self.are_widgets_invalid + } + /// Invalidates the current application widgets. /// /// The shell will rebuild and relayout the widget tree. @@ -62,16 +90,14 @@ impl<'a, Message> Shell<'a, Message> { pub fn merge(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) { self.messages.extend(other.messages.drain(..).map(f)); + if let Some(at) = other.redraw_requested_at { + self.request_redraw(at); + } + self.is_layout_invalid = self.is_layout_invalid || other.is_layout_invalid; self.are_widgets_invalid = self.are_widgets_invalid || other.are_widgets_invalid; } - - /// Returns whether the widgets of the current application have been - /// invalidated. - pub fn are_widgets_invalid(&self) -> bool { - self.are_widgets_invalid - } } diff --git a/native/src/subscription.rs b/native/src/subscription.rs index c60b12819a..980a8116fe 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -1,5 +1,6 @@ //! Listen to external events in your application. use crate::event::{self, Event}; +use crate::window; use crate::Hasher; use iced_futures::futures::{self, Future, Stream}; @@ -33,7 +34,7 @@ pub type Tracker = pub use iced_futures::subscription::Recipe; -/// Returns a [`Subscription`] to all the runtime events. +/// 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. @@ -65,7 +66,10 @@ where use futures::stream::StreamExt; events.filter_map(move |(event, status)| { - future::ready(f(event, status)) + future::ready(match event { + Event::Window(window::Event::RedrawRequested(_)) => None, + _ => f(event, status), + }) }) }, }) diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 2b43829d91..49a6b00e74 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -7,6 +7,8 @@ use crate::renderer; use crate::widget; use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; +use std::time::Instant; + /// A set of interactive graphical elements with a specific [`Layout`]. /// /// It can be updated and drawn. @@ -188,7 +190,9 @@ where ) -> (State, Vec) { use std::mem::ManuallyDrop; - let mut state = State::Updated; + let mut outdated = false; + let mut redraw_requested_at = None; + let mut manual_overlay = ManuallyDrop::new(self.root.as_widget_mut().overlay( &mut self.state, @@ -217,6 +221,16 @@ where event_statuses.push(event_status); + match (redraw_requested_at, shell.redraw_requested_at()) { + (None, Some(at)) => { + redraw_requested_at = Some(at); + } + (Some(current), Some(new)) if new < current => { + redraw_requested_at = Some(new); + } + _ => {} + } + if shell.is_layout_invalid() { let _ = ManuallyDrop::into_inner(manual_overlay); @@ -244,7 +258,7 @@ where } if shell.are_widgets_invalid() { - state = State::Outdated; + outdated = true; } } @@ -289,6 +303,16 @@ where self.overlay = None; } + match (redraw_requested_at, shell.redraw_requested_at()) { + (None, Some(at)) => { + redraw_requested_at = Some(at); + } + (Some(current), Some(new)) if new < current => { + redraw_requested_at = Some(new); + } + _ => {} + } + shell.revalidate_layout(|| { self.base = renderer.layout( &self.root, @@ -299,14 +323,23 @@ where }); if shell.are_widgets_invalid() { - state = State::Outdated; + outdated = true; } event_status.merge(overlay_status) }) .collect(); - (state, event_statuses) + ( + if outdated { + State::Outdated + } else { + State::Updated { + redraw_requested_at, + } + }, + event_statuses, + ) } /// Draws the [`UserInterface`] with the provided [`Renderer`]. @@ -559,5 +592,8 @@ pub enum State { /// The [`UserInterface`] is up-to-date and can be reused without /// rebuilding. - Updated, + Updated { + /// The [`Instant`] when a redraw should be performed. + redraw_requested_at: Option, + }, } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 8b4514e3c7..9d5dd62091 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -22,11 +22,14 @@ use crate::touch; use crate::widget; use crate::widget::operation::{self, Operation}; use crate::widget::tree::{self, Tree}; +use crate::window; use crate::{ Clipboard, Color, Command, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, Widget, }; +use std::time::{Duration, Instant}; + pub use iced_style::text_input::{Appearance, StyleSheet}; /// A field that can be filled with text. @@ -425,7 +428,16 @@ where let state = state(); let is_clicked = layout.bounds().contains(cursor_position); - state.is_focused = is_clicked; + state.is_focused = if is_clicked { + let now = Instant::now(); + + Some(Focus { + at: now, + last_draw: now, + }) + } else { + None + }; if is_clicked { let text_layout = layout.children().next().unwrap(); @@ -541,26 +553,30 @@ where Event::Keyboard(keyboard::Event::CharacterReceived(c)) => { let state = state(); - if state.is_focused - && state.is_pasting.is_none() - && !state.keyboard_modifiers.command() - && !c.is_control() - { - let mut editor = Editor::new(value, &mut state.cursor); + if let Some(focus) = &mut state.is_focused { + if state.is_pasting.is_none() + && !state.keyboard_modifiers.command() + && !c.is_control() + { + let mut editor = Editor::new(value, &mut state.cursor); - editor.insert(c); + editor.insert(c); - let message = (on_change)(editor.contents()); - shell.publish(message); + let message = (on_change)(editor.contents()); + shell.publish(message); - return event::Status::Captured; + focus.at = Instant::now(); + + return event::Status::Captured; + } } } Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => { let state = state(); - if state.is_focused { + if let Some(focus) = &mut state.is_focused { let modifiers = state.keyboard_modifiers; + focus.at = Instant::now(); match key_code { keyboard::KeyCode::Enter @@ -721,7 +737,7 @@ where state.cursor.select_all(value); } keyboard::KeyCode::Escape => { - state.is_focused = false; + state.is_focused = None; state.is_dragging = false; state.is_pasting = None; @@ -742,7 +758,7 @@ where Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. }) => { let state = state(); - if state.is_focused { + if state.is_focused.is_some() { match key_code { keyboard::KeyCode::V => { state.is_pasting = None; @@ -765,6 +781,21 @@ where state.keyboard_modifiers = modifiers; } + Event::Window(window::Event::RedrawRequested(now)) => { + let state = state(); + + if let Some(focus) = &mut state.is_focused { + focus.last_draw = now; + + let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS + - (now - focus.at).as_millis() + % CURSOR_BLINK_INTERVAL_MILLIS; + + shell.request_redraw( + now + Duration::from_millis(millis_until_redraw as u64), + ); + } + } _ => {} } @@ -820,7 +851,7 @@ pub fn draw( let text = value.to_string(); let size = size.unwrap_or_else(|| renderer.default_size()); - let (cursor, offset) = if state.is_focused() { + let (cursor, offset) = if let Some(focus) = &state.is_focused { match state.cursor.state(value) { cursor::State::Index(position) => { let (text_value_width, offset) = @@ -833,7 +864,13 @@ pub fn draw( font.clone(), ); - ( + let is_cursor_visible = ((focus.last_draw - focus.at) + .as_millis() + / CURSOR_BLINK_INTERVAL_MILLIS) + % 2 + == 0; + + let cursor = if is_cursor_visible { Some(( renderer::Quad { bounds: Rectangle { @@ -847,9 +884,12 @@ pub fn draw( border_color: Color::TRANSPARENT, }, theme.value_color(style), - )), - offset, - ) + )) + } else { + None + }; + + (cursor, offset) } cursor::State::Selection { start, end } => { let left = start.min(end); @@ -958,7 +998,7 @@ pub fn mouse_interaction( /// The state of a [`TextInput`]. #[derive(Debug, Default, Clone)] pub struct State { - is_focused: bool, + is_focused: Option, is_dragging: bool, is_pasting: Option, last_click: Option, @@ -967,6 +1007,12 @@ pub struct State { // TODO: Add stateful horizontal scrolling offset } +#[derive(Debug, Clone, Copy)] +struct Focus { + at: Instant, + last_draw: Instant, +} + impl State { /// Creates a new [`State`], representing an unfocused [`TextInput`]. pub fn new() -> Self { @@ -976,7 +1022,7 @@ impl State { /// Creates a new [`State`], representing a focused [`TextInput`]. pub fn focused() -> Self { Self { - is_focused: true, + is_focused: None, is_dragging: false, is_pasting: None, last_click: None, @@ -987,7 +1033,7 @@ impl State { /// Returns whether the [`TextInput`] is currently focused or not. pub fn is_focused(&self) -> bool { - self.is_focused + self.is_focused.is_some() } /// Returns the [`Cursor`] of the [`TextInput`]. @@ -997,13 +1043,19 @@ impl State { /// Focuses the [`TextInput`]. pub fn focus(&mut self) { - self.is_focused = true; + let now = Instant::now(); + + self.is_focused = Some(Focus { + at: now, + last_draw: now, + }); + self.move_cursor_to_end(); } /// Unfocuses the [`TextInput`]. pub fn unfocus(&mut self) { - self.is_focused = false; + self.is_focused = None; } /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text. @@ -1156,3 +1208,5 @@ where ) .map(text::Hit::cursor) } + +const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; diff --git a/native/src/window/event.rs b/native/src/window/event.rs index 86321ac029..16684222ab 100644 --- a/native/src/window/event.rs +++ b/native/src/window/event.rs @@ -1,4 +1,5 @@ use std::path::PathBuf; +use std::time::Instant; /// A window-related event. #[derive(PartialEq, Eq, Clone, Debug)] @@ -19,6 +20,11 @@ pub enum Event { height: u32, }, + /// A window redraw was requested. + /// + /// The [`Instant`] contains the current time. + RedrawRequested(Instant), + /// The user has requested for the window to close. /// /// Usually, you will want to terminate the execution whenever this event diff --git a/src/application.rs b/src/application.rs index f2b7c95574..6d68779be6 100644 --- a/src/application.rs +++ b/src/application.rs @@ -180,13 +180,6 @@ pub trait Application: Sized { 1.0 } - /// Returns whether the [`Application`] should be terminated. - /// - /// By default, it returns `false`. - fn should_exit(&self) -> bool { - false - } - /// Runs the [`Application`]. /// /// On native platforms, this method will take control of the current thread diff --git a/src/sandbox.rs b/src/sandbox.rs index 47bad8310c..40c699d9a3 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -140,13 +140,6 @@ pub trait Sandbox { 1.0 } - /// Returns whether the [`Sandbox`] should be terminated. - /// - /// By default, it returns `false`. - fn should_exit(&self) -> bool { - false - } - /// Runs the [`Sandbox`]. /// /// On native platforms, this method will take control of the current thread @@ -203,8 +196,4 @@ where fn scale_factor(&self) -> f64 { T::scale_factor(self) } - - fn should_exit(&self) -> bool { - T::should_exit(self) - } } diff --git a/winit/src/application.rs b/winit/src/application.rs index 74c738153a..0f5309d27f 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -11,7 +11,7 @@ use crate::mouse; use crate::renderer; use crate::widget::operation; use crate::{ - Command, Debug, Error, Executor, Proxy, Runtime, Settings, Size, + Command, Debug, Error, Event, Executor, Proxy, Runtime, Settings, Size, Subscription, }; @@ -25,6 +25,7 @@ use iced_native::user_interface::{self, UserInterface}; pub use iced_native::application::{Appearance, StyleSheet}; use std::mem::ManuallyDrop; +use std::time::Instant; #[cfg(feature = "trace")] pub use profiler::Profiler; @@ -186,7 +187,8 @@ where let (compositor, renderer) = C::new(compositor_settings, Some(&window))?; - let (mut sender, receiver) = mpsc::unbounded(); + let (mut event_sender, event_receiver) = mpsc::unbounded(); + let (control_sender, mut control_receiver) = mpsc::unbounded(); let mut instance = Box::pin({ let run_instance = run_instance::( @@ -196,7 +198,8 @@ where runtime, proxy, debug, - receiver, + event_receiver, + control_sender, init_command, window, settings.exit_on_close_request, @@ -234,13 +237,19 @@ where }; if let Some(event) = event { - sender.start_send(event).expect("Send event"); + event_sender.start_send(event).expect("Send event"); let poll = instance.as_mut().poll(&mut context); - *control_flow = match poll { - task::Poll::Pending => ControlFlow::Wait, - task::Poll::Ready(_) => ControlFlow::Exit, + match poll { + task::Poll::Pending => { + if let Ok(Some(flow)) = control_receiver.try_next() { + *control_flow = flow; + } + } + task::Poll::Ready(_) => { + *control_flow = ControlFlow::Exit; + } }; } }) @@ -253,7 +262,10 @@ async fn run_instance( mut runtime: Runtime, A::Message>, mut proxy: winit::event_loop::EventLoopProxy, mut debug: Debug, - mut receiver: mpsc::UnboundedReceiver>, + mut event_receiver: mpsc::UnboundedReceiver< + winit::event::Event<'_, A::Message>, + >, + mut control_sender: mpsc::UnboundedSender, init_command: Command, window: winit::window::Window, exit_on_close_request: bool, @@ -265,6 +277,7 @@ async fn run_instance( { use iced_futures::futures::stream::StreamExt; use winit::event; + use winit::event_loop::ControlFlow; let mut clipboard = Clipboard::connect(&window); let mut cache = user_interface::Cache::default(); @@ -309,13 +322,21 @@ async fn run_instance( let mut mouse_interaction = mouse::Interaction::default(); let mut events = Vec::new(); let mut messages = Vec::new(); + let mut redraw_pending = false; debug.startup_finished(); - while let Some(event) = receiver.next().await { + while let Some(event) = event_receiver.next().await { match event { + event::Event::NewEvents(start_cause) => { + redraw_pending = matches!( + start_cause, + event::StartCause::Init + | event::StartCause::ResumeTimeReached { .. } + ); + } event::Event::MainEventsCleared => { - if events.is_empty() && messages.is_empty() { + if !redraw_pending && events.is_empty() && messages.is_empty() { continue; } @@ -338,7 +359,7 @@ async fn run_instance( if !messages.is_empty() || matches!( interface_state, - user_interface::State::Outdated, + user_interface::State::Outdated ) { let mut cache = @@ -376,6 +397,24 @@ async fn run_instance( } } + // TODO: Avoid redrawing all the time by forcing widgets to + // request redraws on state changes + // + // Then, we can use the `interface_state` here to decide whether + // if a redraw is needed right away, or simply wait until a + // specific time. + let redraw_event = Event::Window( + crate::window::Event::RedrawRequested(Instant::now()), + ); + + let (interface_state, _) = user_interface.update( + &[redraw_event.clone()], + state.cursor_position(), + &mut renderer, + &mut clipboard, + &mut messages, + ); + debug.draw_started(); let new_mouse_interaction = user_interface.draw( &mut renderer, @@ -396,6 +435,17 @@ async fn run_instance( } window.request_redraw(); + runtime + .broadcast((redraw_event, crate::event::Status::Ignored)); + + let _ = control_sender.start_send(match interface_state { + user_interface::State::Updated { + redraw_requested_at: Some(at), + } => ControlFlow::WaitUntil(at), + _ => ControlFlow::Wait, + }); + + redraw_pending = false; } event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), From a980024bbf7fec595efd2daa56b0572574ba2ce7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Jan 2023 03:11:08 +0100 Subject: [PATCH 02/11] Implement widget redraw support in `iced_glutin` --- glutin/src/application.rs | 72 +++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 1464bb2d78..a3cef829c5 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -12,10 +12,11 @@ use iced_winit::futures; use iced_winit::futures::channel::mpsc; use iced_winit::renderer; use iced_winit::user_interface; -use iced_winit::{Clipboard, Command, Debug, Proxy, Settings}; +use iced_winit::{Clipboard, Command, Debug, Event, Proxy, Settings}; use glutin::window::Window; use std::mem::ManuallyDrop; +use std::time::Instant; #[cfg(feature = "tracing")] use tracing::{info_span, instrument::Instrument}; @@ -131,7 +132,8 @@ where })? }; - let (mut sender, receiver) = mpsc::unbounded(); + let (mut event_sender, event_receiver) = mpsc::unbounded(); + let (control_sender, mut control_receiver) = mpsc::unbounded(); let mut instance = Box::pin({ let run_instance = run_instance::( @@ -141,7 +143,8 @@ where runtime, proxy, debug, - receiver, + event_receiver, + control_sender, context, init_command, settings.exit_on_close_request, @@ -179,14 +182,20 @@ where }; if let Some(event) = event { - sender.start_send(event).expect("Send event"); + event_sender.start_send(event).expect("Send event"); let poll = instance.as_mut().poll(&mut context); - *control_flow = match poll { - task::Poll::Pending => ControlFlow::Wait, - task::Poll::Ready(_) => ControlFlow::Exit, - }; + match poll { + task::Poll::Pending => { + if let Ok(Some(flow)) = control_receiver.try_next() { + *control_flow = flow; + } + } + task::Poll::Ready(_) => { + *control_flow = ControlFlow::Exit; + } + } } }); @@ -200,7 +209,10 @@ async fn run_instance( mut runtime: Runtime, A::Message>, mut proxy: glutin::event_loop::EventLoopProxy, mut debug: Debug, - mut receiver: mpsc::UnboundedReceiver>, + mut event_receiver: mpsc::UnboundedReceiver< + glutin::event::Event<'_, A::Message>, + >, + mut control_sender: mpsc::UnboundedSender, mut context: glutin::ContextWrapper, init_command: Command, exit_on_close_request: bool, @@ -211,6 +223,7 @@ async fn run_instance( ::Theme: StyleSheet, { use glutin::event; + use glutin::event_loop::ControlFlow; use iced_winit::futures::stream::StreamExt; let mut clipboard = Clipboard::connect(context.window()); @@ -247,13 +260,21 @@ async fn run_instance( let mut mouse_interaction = mouse::Interaction::default(); let mut events = Vec::new(); let mut messages = Vec::new(); + let mut redraw_pending = false; debug.startup_finished(); - while let Some(event) = receiver.next().await { + while let Some(event) = event_receiver.next().await { match event { + event::Event::NewEvents(start_cause) => { + redraw_pending = matches!( + start_cause, + event::StartCause::Init + | event::StartCause::ResumeTimeReached { .. } + ); + } event::Event::MainEventsCleared => { - if events.is_empty() && messages.is_empty() { + if !redraw_pending && events.is_empty() && messages.is_empty() { continue; } @@ -315,6 +336,24 @@ async fn run_instance( } } + // TODO: Avoid redrawing all the time by forcing widgets to + // request redraws on state changes + // + // Then, we can use the `interface_state` here to decide whether + // if a redraw is needed right away, or simply wait until a + // specific time. + let redraw_event = Event::Window( + crate::window::Event::RedrawRequested(Instant::now()), + ); + + let (interface_state, _) = user_interface.update( + &[redraw_event.clone()], + state.cursor_position(), + &mut renderer, + &mut clipboard, + &mut messages, + ); + debug.draw_started(); let new_mouse_interaction = user_interface.draw( &mut renderer, @@ -335,6 +374,17 @@ async fn run_instance( } context.window().request_redraw(); + runtime + .broadcast((redraw_event, crate::event::Status::Ignored)); + + let _ = control_sender.start_send(match interface_state { + user_interface::State::Updated { + redraw_requested_at: Some(at), + } => ControlFlow::WaitUntil(at), + _ => ControlFlow::Wait, + }); + + redraw_pending = false; } event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), From 178bd2d83c3336f80b9bb54c78a71711c2e0cdcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Jan 2023 03:21:15 +0100 Subject: [PATCH 03/11] Avoid reblinking cursor when clicking a focused `TextInput` --- native/src/widget/text_input.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 9d5dd62091..4a7cc1e782 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -429,11 +429,10 @@ where let is_clicked = layout.bounds().contains(cursor_position); state.is_focused = if is_clicked { - let now = Instant::now(); + state.is_focused.or_else(|| { + let now = Instant::now(); - Some(Focus { - at: now, - last_draw: now, + Some(Focus { at: now, now }) }) } else { None @@ -785,7 +784,7 @@ where let state = state(); if let Some(focus) = &mut state.is_focused { - focus.last_draw = now; + focus.now = now; let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS - (now - focus.at).as_millis() @@ -864,8 +863,7 @@ pub fn draw( font.clone(), ); - let is_cursor_visible = ((focus.last_draw - focus.at) - .as_millis() + let is_cursor_visible = ((focus.now - focus.at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS) % 2 == 0; @@ -1010,7 +1008,7 @@ pub struct State { #[derive(Debug, Clone, Copy)] struct Focus { at: Instant, - last_draw: Instant, + now: Instant, } impl State { @@ -1045,10 +1043,7 @@ impl State { pub fn focus(&mut self) { let now = Instant::now(); - self.is_focused = Some(Focus { - at: now, - last_draw: now, - }); + self.is_focused = Some(Focus { at: now, now: now }); self.move_cursor_to_end(); } From c649ec8cf7066eb190193ff499c0ecccbca76796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Jan 2023 03:22:34 +0100 Subject: [PATCH 04/11] Use short-hand field notation in `TextInput` --- native/src/widget/text_input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 4a7cc1e782..db8f25edd6 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -1043,7 +1043,7 @@ impl State { pub fn focus(&mut self) { let now = Instant::now(); - self.is_focused = Some(Focus { at: now, now: now }); + self.is_focused = Some(Focus { at: now, now }); self.move_cursor_to_end(); } From 0b86c4a299d384cafca31206eac8c94f1123518d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Jan 2023 04:35:41 +0100 Subject: [PATCH 05/11] Implement `window::frames` subscription ... and use it in the `solar_system` example :tada: --- examples/solar_system/src/main.rs | 3 +-- native/src/subscription.rs | 27 ++++++++++++++++++++++++++- native/src/window.rs | 15 +++++++++++++++ winit/src/window.rs | 2 +- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 9e303576f9..9a4ee7549a 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -9,7 +9,6 @@ use iced::application; use iced::executor; use iced::theme::{self, Theme}; -use iced::time; use iced::widget::canvas; use iced::widget::canvas::gradient::{self, Gradient}; use iced::widget::canvas::stroke::{self, Stroke}; @@ -90,7 +89,7 @@ impl Application for SolarSystem { } fn subscription(&self) -> Subscription { - time::every(time::Duration::from_millis(10)).map(Message::Tick) + window::frames().map(Message::Tick) } } diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 980a8116fe..4c0d80a7d9 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -59,8 +59,11 @@ pub fn events_with( where Message: 'static + MaybeSend, { + #[derive(Hash)] + struct EventsWith; + Subscription::from_recipe(Runner { - id: f, + id: (EventsWith, f), spawn: move |events| { use futures::future; use futures::stream::StreamExt; @@ -75,6 +78,28 @@ where }) } +pub(crate) 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 create and asynchronously run the /// given [`Stream`]. /// diff --git a/native/src/window.rs b/native/src/window.rs index 1b97e6557a..4bccc471bf 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -8,3 +8,18 @@ pub use action::Action; pub use event::Event; pub use mode::Mode; pub use user_attention::UserAttention; + +use crate::subscription::{self, Subscription}; + +use std::time::Instant; + +/// Subscribes to the frames of the window of the running application. +/// +/// The resulting [`Subscription`] will produce items at a rate equal to the +/// framerate of the monitor of said window. +pub fn frames() -> Subscription { + subscription::raw_events(|event, _status| match event { + crate::Event::Window(Event::RedrawRequested(at)) => Some(at), + _ => None, + }) +} diff --git a/winit/src/window.rs b/winit/src/window.rs index 89db32628c..0b9e4c46b8 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -2,7 +2,7 @@ use crate::command::{self, Command}; use iced_native::window; -pub use window::{Event, Mode, UserAttention}; +pub use window::{frames, Event, Mode, UserAttention}; /// Closes the current window and exits the application. pub fn close() -> Command { From 502c9bfbf6eb6193adf6c88abdc4cef90816a04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Jan 2023 04:54:34 +0100 Subject: [PATCH 06/11] Rename `Focus::at` to `Focus::updated_at` in `text_input` --- native/src/widget/text_input.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index db8f25edd6..f88022faf5 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -432,7 +432,10 @@ where state.is_focused.or_else(|| { let now = Instant::now(); - Some(Focus { at: now, now }) + Some(Focus { + updated_at: now, + now, + }) }) } else { None @@ -564,7 +567,7 @@ where let message = (on_change)(editor.contents()); shell.publish(message); - focus.at = Instant::now(); + focus.updated_at = Instant::now(); return event::Status::Captured; } @@ -575,7 +578,7 @@ where if let Some(focus) = &mut state.is_focused { let modifiers = state.keyboard_modifiers; - focus.at = Instant::now(); + focus.updated_at = Instant::now(); match key_code { keyboard::KeyCode::Enter @@ -787,7 +790,7 @@ where focus.now = now; let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS - - (now - focus.at).as_millis() + - (now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS; shell.request_redraw( @@ -863,7 +866,8 @@ pub fn draw( font.clone(), ); - let is_cursor_visible = ((focus.now - focus.at).as_millis() + let is_cursor_visible = ((focus.now - focus.updated_at) + .as_millis() / CURSOR_BLINK_INTERVAL_MILLIS) % 2 == 0; @@ -1007,7 +1011,7 @@ pub struct State { #[derive(Debug, Clone, Copy)] struct Focus { - at: Instant, + updated_at: Instant, now: Instant, } @@ -1043,7 +1047,10 @@ impl State { pub fn focus(&mut self) { let now = Instant::now(); - self.is_focused = Some(Focus { at: now, now }); + self.is_focused = Some(Focus { + updated_at: now, + now, + }); self.move_cursor_to_end(); } From e2ddef74387bcd81859b56e47316c47d7b739a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Jan 2023 05:18:25 +0100 Subject: [PATCH 07/11] Replace `Option` with `RedrawRequest` enum --- glutin/src/application.rs | 12 +++++++-- native/src/shell.rs | 22 ++++++++--------- native/src/user_interface.rs | 23 ++++++++--------- native/src/widget/text_input.rs | 4 +-- native/src/window.rs | 2 ++ native/src/window/redraw_request.rs | 38 +++++++++++++++++++++++++++++ winit/src/application.rs | 12 +++++++-- winit/src/window.rs | 2 +- 8 files changed, 84 insertions(+), 31 deletions(-) create mode 100644 native/src/window/redraw_request.rs diff --git a/glutin/src/application.rs b/glutin/src/application.rs index a3cef829c5..3bb9e61ab4 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -270,6 +270,7 @@ async fn run_instance( redraw_pending = matches!( start_cause, event::StartCause::Init + | event::StartCause::Poll | event::StartCause::ResumeTimeReached { .. } ); } @@ -379,8 +380,15 @@ async fn run_instance( let _ = control_sender.start_send(match interface_state { user_interface::State::Updated { - redraw_requested_at: Some(at), - } => ControlFlow::WaitUntil(at), + redraw_request: Some(redraw_request), + } => match redraw_request { + crate::window::RedrawRequest::NextFrame => { + ControlFlow::Poll + } + crate::window::RedrawRequest::At(at) => { + ControlFlow::WaitUntil(at) + } + }, _ => ControlFlow::Wait, }); diff --git a/native/src/shell.rs b/native/src/shell.rs index 81d2a0e6ac..f1ddb48e61 100644 --- a/native/src/shell.rs +++ b/native/src/shell.rs @@ -1,4 +1,4 @@ -use std::time::Instant; +use crate::window; /// A connection to the state of a shell. /// @@ -9,7 +9,7 @@ use std::time::Instant; #[derive(Debug)] pub struct Shell<'a, Message> { messages: &'a mut Vec, - redraw_requested_at: Option, + redraw_request: Option, is_layout_invalid: bool, are_widgets_invalid: bool, } @@ -19,7 +19,7 @@ impl<'a, Message> Shell<'a, Message> { pub fn new(messages: &'a mut Vec) -> Self { Self { messages, - redraw_requested_at: None, + redraw_request: None, is_layout_invalid: false, are_widgets_invalid: false, } @@ -31,21 +31,21 @@ impl<'a, Message> Shell<'a, Message> { } /// Requests a new frame to be drawn at the given [`Instant`]. - pub fn request_redraw(&mut self, at: Instant) { - match self.redraw_requested_at { + pub fn request_redraw(&mut self, request: window::RedrawRequest) { + match self.redraw_request { None => { - self.redraw_requested_at = Some(at); + self.redraw_request = Some(request); } - Some(current) if at < current => { - self.redraw_requested_at = Some(at); + Some(current) if request < current => { + self.redraw_request = Some(request); } _ => {} } } /// Returns the requested [`Instant`] a redraw should happen, if any. - pub fn redraw_requested_at(&self) -> Option { - self.redraw_requested_at + pub fn redraw_request(&self) -> Option { + self.redraw_request } /// Returns whether the current layout is invalid or not. @@ -90,7 +90,7 @@ impl<'a, Message> Shell<'a, Message> { pub fn merge(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) { self.messages.extend(other.messages.drain(..).map(f)); - if let Some(at) = other.redraw_requested_at { + if let Some(at) = other.redraw_request { self.request_redraw(at); } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 49a6b00e74..025f28a186 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -5,10 +5,9 @@ use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; +use crate::window; use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; -use std::time::Instant; - /// A set of interactive graphical elements with a specific [`Layout`]. /// /// It can be updated and drawn. @@ -191,7 +190,7 @@ where use std::mem::ManuallyDrop; let mut outdated = false; - let mut redraw_requested_at = None; + let mut redraw_request = None; let mut manual_overlay = ManuallyDrop::new(self.root.as_widget_mut().overlay( @@ -221,12 +220,12 @@ where event_statuses.push(event_status); - match (redraw_requested_at, shell.redraw_requested_at()) { + match (redraw_request, shell.redraw_request()) { (None, Some(at)) => { - redraw_requested_at = Some(at); + redraw_request = Some(at); } (Some(current), Some(new)) if new < current => { - redraw_requested_at = Some(new); + redraw_request = Some(new); } _ => {} } @@ -303,12 +302,12 @@ where self.overlay = None; } - match (redraw_requested_at, shell.redraw_requested_at()) { + match (redraw_request, shell.redraw_request()) { (None, Some(at)) => { - redraw_requested_at = Some(at); + redraw_request = Some(at); } (Some(current), Some(new)) if new < current => { - redraw_requested_at = Some(new); + redraw_request = Some(new); } _ => {} } @@ -334,9 +333,7 @@ where if outdated { State::Outdated } else { - State::Updated { - redraw_requested_at, - } + State::Updated { redraw_request } }, event_statuses, ) @@ -594,6 +591,6 @@ pub enum State { /// rebuilding. Updated { /// The [`Instant`] when a redraw should be performed. - redraw_requested_at: Option, + redraw_request: Option, }, } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index f88022faf5..ae28906945 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -793,9 +793,9 @@ where - (now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS; - shell.request_redraw( + shell.request_redraw(window::RedrawRequest::At( now + Duration::from_millis(millis_until_redraw as u64), - ); + )); } } _ => {} diff --git a/native/src/window.rs b/native/src/window.rs index 4bccc471bf..6ebe15b130 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -2,11 +2,13 @@ mod action; mod event; mod mode; +mod redraw_request; mod user_attention; pub use action::Action; pub use event::Event; pub use mode::Mode; +pub use redraw_request::RedrawRequest; pub use user_attention::UserAttention; use crate::subscription::{self, Subscription}; diff --git a/native/src/window/redraw_request.rs b/native/src/window/redraw_request.rs new file mode 100644 index 0000000000..1377823ae2 --- /dev/null +++ b/native/src/window/redraw_request.rs @@ -0,0 +1,38 @@ +use std::time::Instant; + +/// A request to redraw a window. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum RedrawRequest { + /// Redraw the next frame. + NextFrame, + + /// Redraw at the given time. + At(Instant), +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::{Duration, Instant}; + + #[test] + fn ordering() { + let now = Instant::now(); + let later = now + Duration::from_millis(10); + + assert_eq!(RedrawRequest::NextFrame, RedrawRequest::NextFrame); + assert_eq!(RedrawRequest::At(now), RedrawRequest::At(now)); + + assert!(RedrawRequest::NextFrame < RedrawRequest::At(now)); + assert!(RedrawRequest::At(now) > RedrawRequest::NextFrame); + assert!(RedrawRequest::At(now) < RedrawRequest::At(later)); + assert!(RedrawRequest::At(later) > RedrawRequest::At(now)); + + assert!(RedrawRequest::NextFrame <= RedrawRequest::NextFrame); + assert!(RedrawRequest::NextFrame <= RedrawRequest::At(now)); + assert!(RedrawRequest::At(now) >= RedrawRequest::NextFrame); + assert!(RedrawRequest::At(now) <= RedrawRequest::At(now)); + assert!(RedrawRequest::At(now) <= RedrawRequest::At(later)); + assert!(RedrawRequest::At(later) >= RedrawRequest::At(now)); + } +} diff --git a/winit/src/application.rs b/winit/src/application.rs index 0f5309d27f..b6485cb72a 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -332,6 +332,7 @@ async fn run_instance( redraw_pending = matches!( start_cause, event::StartCause::Init + | event::StartCause::Poll | event::StartCause::ResumeTimeReached { .. } ); } @@ -440,8 +441,15 @@ async fn run_instance( let _ = control_sender.start_send(match interface_state { user_interface::State::Updated { - redraw_requested_at: Some(at), - } => ControlFlow::WaitUntil(at), + redraw_request: Some(redraw_request), + } => match redraw_request { + crate::window::RedrawRequest::NextFrame => { + ControlFlow::Poll + } + crate::window::RedrawRequest::At(at) => { + ControlFlow::WaitUntil(at) + } + }, _ => ControlFlow::Wait, }); diff --git a/winit/src/window.rs b/winit/src/window.rs index 0b9e4c46b8..2306bdf151 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -2,7 +2,7 @@ use crate::command::{self, Command}; use iced_native::window; -pub use window::{frames, Event, Mode, UserAttention}; +pub use window::{frames, Event, Mode, RedrawRequest, UserAttention}; /// Closes the current window and exits the application. pub fn close() -> Command { From fc54d6ba31246157422d092914ba7c1e483129c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Jan 2023 05:26:39 +0100 Subject: [PATCH 08/11] Use `instant` to fix Wasm target --- glutin/Cargo.toml | 7 ++++--- glutin/src/application.rs | 2 +- native/Cargo.toml | 1 + native/src/widget/text_input.rs | 2 +- native/src/window.rs | 2 +- native/src/window/event.rs | 2 +- native/src/window/redraw_request.rs | 2 +- winit/Cargo.toml | 1 + winit/src/application.rs | 2 +- 9 files changed, 12 insertions(+), 9 deletions(-) diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 304170cd09..a458ee9e51 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -15,8 +15,9 @@ trace = ["iced_winit/trace"] debug = ["iced_winit/debug"] system = ["iced_winit/system"] -[dependencies.log] -version = "0.4" +[dependencies] +log = "0.4" +instant = "0.1" [dependencies.glutin] version = "0.29" @@ -39,4 +40,4 @@ features = ["opengl"] [dependencies.tracing] version = "0.1.6" -optional = true \ No newline at end of file +optional = true diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 3bb9e61ab4..a2096ee41a 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -15,8 +15,8 @@ use iced_winit::user_interface; use iced_winit::{Clipboard, Command, Debug, Event, Proxy, Settings}; use glutin::window::Window; +use instant::Instant; use std::mem::ManuallyDrop; -use std::time::Instant; #[cfg(feature = "tracing")] use tracing::{info_span, instrument::Instrument}; diff --git a/native/Cargo.toml b/native/Cargo.toml index bbf9295122..040ea454d8 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -14,6 +14,7 @@ debug = [] twox-hash = { version = "1.5", default-features = false } unicode-segmentation = "1.6" num-traits = "0.2" +instant = "0.1" [dependencies.iced_core] version = "0.6" diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index ae28906945..e5b7d93a0c 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -28,7 +28,7 @@ use crate::{ Rectangle, Shell, Size, Vector, Widget, }; -use std::time::{Duration, Instant}; +use instant::{Duration, Instant}; pub use iced_style::text_input::{Appearance, StyleSheet}; diff --git a/native/src/window.rs b/native/src/window.rs index 6ebe15b130..94201059dc 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -13,7 +13,7 @@ pub use user_attention::UserAttention; use crate::subscription::{self, Subscription}; -use std::time::Instant; +use instant::Instant; /// Subscribes to the frames of the window of the running application. /// diff --git a/native/src/window/event.rs b/native/src/window/event.rs index 16684222ab..64dd17d7c7 100644 --- a/native/src/window/event.rs +++ b/native/src/window/event.rs @@ -1,5 +1,5 @@ +use instant::Instant; use std::path::PathBuf; -use std::time::Instant; /// A window-related event. #[derive(PartialEq, Eq, Clone, Debug)] diff --git a/native/src/window/redraw_request.rs b/native/src/window/redraw_request.rs index 1377823ae2..be5bd75750 100644 --- a/native/src/window/redraw_request.rs +++ b/native/src/window/redraw_request.rs @@ -1,4 +1,4 @@ -use std::time::Instant; +use instant::Instant; /// A request to redraw a window. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 94aaa2cad3..872b30c8cc 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -21,6 +21,7 @@ application = [] window_clipboard = "0.2" log = "0.4" thiserror = "1.0" +instant = "0.1" [dependencies.winit] version = "0.27" diff --git a/winit/src/application.rs b/winit/src/application.rs index b6485cb72a..4d7382ccad 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -24,8 +24,8 @@ use iced_native::user_interface::{self, UserInterface}; pub use iced_native::application::{Appearance, StyleSheet}; +use instant::Instant; use std::mem::ManuallyDrop; -use std::time::Instant; #[cfg(feature = "trace")] pub use profiler::Profiler; From c6d0046102bb6951bf0f1f6102f748199c5889e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 12 Jan 2023 06:24:44 +0100 Subject: [PATCH 09/11] Use `instant` instead of `wasm-timer` in `iced_core` --- core/Cargo.toml | 2 +- core/src/time.rs | 6 +++++- glutin/Cargo.toml | 1 - glutin/src/application.rs | 2 +- native/Cargo.toml | 1 - native/src/widget/text_input.rs | 3 +-- native/src/window.rs | 3 +-- native/src/window/event.rs | 3 ++- native/src/window/redraw_request.rs | 2 +- winit/Cargo.toml | 1 - winit/src/application.rs | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index c401f30a38..7be4b132f6 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -15,4 +15,4 @@ version = "0.6" optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-timer = { version = "0.2" } +instant = "0.1" diff --git a/core/src/time.rs b/core/src/time.rs index f496d1a4ba..9355ae6d84 100644 --- a/core/src/time.rs +++ b/core/src/time.rs @@ -1,9 +1,13 @@ //! Keep track of time, both in native and web platforms! #[cfg(target_arch = "wasm32")] -pub use wasm_timer::Instant; +pub use instant::Instant; + +#[cfg(target_arch = "wasm32")] +pub use instant::Duration; #[cfg(not(target_arch = "wasm32"))] pub use std::time::Instant; +#[cfg(not(target_arch = "wasm32"))] pub use std::time::Duration; diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index a458ee9e51..709b118b75 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -17,7 +17,6 @@ system = ["iced_winit/system"] [dependencies] log = "0.4" -instant = "0.1" [dependencies.glutin] version = "0.29" diff --git a/glutin/src/application.rs b/glutin/src/application.rs index a2096ee41a..da0af5c04e 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -11,11 +11,11 @@ use iced_winit::conversion; use iced_winit::futures; use iced_winit::futures::channel::mpsc; use iced_winit::renderer; +use iced_winit::time::Instant; use iced_winit::user_interface; use iced_winit::{Clipboard, Command, Debug, Event, Proxy, Settings}; use glutin::window::Window; -use instant::Instant; use std::mem::ManuallyDrop; #[cfg(feature = "tracing")] diff --git a/native/Cargo.toml b/native/Cargo.toml index 040ea454d8..bbf9295122 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -14,7 +14,6 @@ debug = [] twox-hash = { version = "1.5", default-features = false } unicode-segmentation = "1.6" num-traits = "0.2" -instant = "0.1" [dependencies.iced_core] version = "0.6" diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index e5b7d93a0c..8755b85d81 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -18,6 +18,7 @@ use crate::layout; use crate::mouse::{self, click}; use crate::renderer; use crate::text::{self, Text}; +use crate::time::{Duration, Instant}; use crate::touch; use crate::widget; use crate::widget::operation::{self, Operation}; @@ -28,8 +29,6 @@ use crate::{ Rectangle, Shell, Size, Vector, Widget, }; -use instant::{Duration, Instant}; - pub use iced_style::text_input::{Appearance, StyleSheet}; /// A field that can be filled with text. diff --git a/native/src/window.rs b/native/src/window.rs index 94201059dc..bd92730db1 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -12,8 +12,7 @@ pub use redraw_request::RedrawRequest; pub use user_attention::UserAttention; use crate::subscription::{self, Subscription}; - -use instant::Instant; +use crate::time::Instant; /// Subscribes to the frames of the window of the running application. /// diff --git a/native/src/window/event.rs b/native/src/window/event.rs index 64dd17d7c7..e2fb5e665b 100644 --- a/native/src/window/event.rs +++ b/native/src/window/event.rs @@ -1,4 +1,5 @@ -use instant::Instant; +use crate::time::Instant; + use std::path::PathBuf; /// A window-related event. diff --git a/native/src/window/redraw_request.rs b/native/src/window/redraw_request.rs index be5bd75750..3b4f0fd35e 100644 --- a/native/src/window/redraw_request.rs +++ b/native/src/window/redraw_request.rs @@ -1,4 +1,4 @@ -use instant::Instant; +use crate::time::Instant; /// A request to redraw a window. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 872b30c8cc..94aaa2cad3 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -21,7 +21,6 @@ application = [] window_clipboard = "0.2" log = "0.4" thiserror = "1.0" -instant = "0.1" [dependencies.winit] version = "0.27" diff --git a/winit/src/application.rs b/winit/src/application.rs index 4d7382ccad..8817939102 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -20,11 +20,11 @@ use iced_futures::futures::channel::mpsc; use iced_graphics::compositor; use iced_graphics::window; use iced_native::program::Program; +use iced_native::time::Instant; use iced_native::user_interface::{self, UserInterface}; pub use iced_native::application::{Appearance, StyleSheet}; -use instant::Instant; use std::mem::ManuallyDrop; #[cfg(feature = "trace")] From b9c8c7b08d2778b6717c2df0731605aea35dc0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 13 Jan 2023 18:17:15 +0100 Subject: [PATCH 10/11] Clarify documentation of `window::frames` --- native/src/window.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/native/src/window.rs b/native/src/window.rs index bd92730db1..a5cdc8ceda 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -17,7 +17,11 @@ use crate::time::Instant; /// Subscribes to the frames of the window of the running application. /// /// The resulting [`Subscription`] will produce items at a rate equal to the -/// framerate of the monitor of said window. +/// refresh rate of the window. Note that this rate may be variable, as it is +/// normally managed by the graphics driver and/or the OS. +/// +/// 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 { crate::Event::Window(Event::RedrawRequested(at)) => Some(at), From 507820a8438cec25074f92b72e118e0931fa7f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 13 Jan 2023 18:19:05 +0100 Subject: [PATCH 11/11] Fix grammar of `TODO` comment in `application` modules --- glutin/src/application.rs | 5 ++--- winit/src/application.rs | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index da0af5c04e..b7bf21c372 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -340,9 +340,8 @@ async fn run_instance( // TODO: Avoid redrawing all the time by forcing widgets to // request redraws on state changes // - // Then, we can use the `interface_state` here to decide whether - // if a redraw is needed right away, or simply wait until a - // specific time. + // Then, we can use the `interface_state` here to decide if a redraw + // is needed right away, or simply wait until a specific time. let redraw_event = Event::Window( crate::window::Event::RedrawRequested(Instant::now()), ); diff --git a/winit/src/application.rs b/winit/src/application.rs index 8817939102..77ca4b31e9 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -401,9 +401,8 @@ async fn run_instance( // TODO: Avoid redrawing all the time by forcing widgets to // request redraws on state changes // - // Then, we can use the `interface_state` here to decide whether - // if a redraw is needed right away, or simply wait until a - // specific time. + // Then, we can use the `interface_state` here to decide if a redraw + // is needed right away, or simply wait until a specific time. let redraw_event = Event::Window( crate::window::Event::RedrawRequested(Instant::now()), );