From 7354f68b3ca345767de3f09dccddf168493977bf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 12 Jan 2023 02:59:08 +0100 Subject: Draft `Shell:request_redraw` API ... and implement `TextInput` cursor blink :tada: --- 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 +++ 6 files changed, 177 insertions(+), 51 deletions(-) (limited to 'native') diff --git a/native/src/renderer.rs b/native/src/renderer.rs index 5e776be6..d5329acd 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 b96d23e5..81d2a0e6 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 c60b1281..980a8116 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 2b43829d..49a6b00e 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 8b4514e3..9d5dd620 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 86321ac0..16684222 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 -- cgit From 178bd2d83c3336f80b9bb54c78a71711c2e0cdcd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 12 Jan 2023 03:21:15 +0100 Subject: Avoid reblinking cursor when clicking a focused `TextInput` --- native/src/widget/text_input.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) (limited to 'native') diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 9d5dd620..4a7cc1e7 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(); } -- cgit From c649ec8cf7066eb190193ff499c0ecccbca76796 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 12 Jan 2023 03:22:34 +0100 Subject: Use short-hand field notation in `TextInput` --- native/src/widget/text_input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 4a7cc1e7..db8f25ed 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(); } -- cgit From 0b86c4a299d384cafca31206eac8c94f1123518d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 12 Jan 2023 04:35:41 +0100 Subject: Implement `window::frames` subscription ... and use it in the `solar_system` example :tada: --- native/src/subscription.rs | 27 ++++++++++++++++++++++++++- native/src/window.rs | 15 +++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 980a8116..4c0d80a7 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 1b97e655..4bccc471 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, + }) +} -- cgit From 502c9bfbf6eb6193adf6c88abdc4cef90816a04b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 12 Jan 2023 04:54:34 +0100 Subject: Rename `Focus::at` to `Focus::updated_at` in `text_input` --- native/src/widget/text_input.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'native') diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index db8f25ed..f88022fa 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(); } -- cgit From e2ddef74387bcd81859b56e47316c47d7b739a01 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 12 Jan 2023 05:18:25 +0100 Subject: Replace `Option` with `RedrawRequest` enum --- 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 +++++++++++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 native/src/window/redraw_request.rs (limited to 'native') diff --git a/native/src/shell.rs b/native/src/shell.rs index 81d2a0e6..f1ddb48e 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 49a6b00e..025f28a1 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 f88022fa..ae289069 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 4bccc471..6ebe15b1 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 00000000..1377823a --- /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)); + } +} -- cgit From fc54d6ba31246157422d092914ba7c1e483129c4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 12 Jan 2023 05:26:39 +0100 Subject: Use `instant` to fix Wasm target --- 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 +- 5 files changed, 5 insertions(+), 4 deletions(-) (limited to 'native') diff --git a/native/Cargo.toml b/native/Cargo.toml index bbf92951..040ea454 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 ae289069..e5b7d93a 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 6ebe15b1..94201059 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 16684222..64dd17d7 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 1377823a..be5bd757 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)] -- cgit From c6d0046102bb6951bf0f1f6102f748199c5889e2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 12 Jan 2023 06:24:44 +0100 Subject: Use `instant` instead of `wasm-timer` in `iced_core` --- 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 +- 5 files changed, 5 insertions(+), 7 deletions(-) (limited to 'native') diff --git a/native/Cargo.toml b/native/Cargo.toml index 040ea454..bbf92951 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 e5b7d93a..8755b85d 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 94201059..bd92730d 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 64dd17d7..e2fb5e66 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 be5bd757..3b4f0fd3 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)] -- cgit From b9c8c7b08d2778b6717c2df0731605aea35dc0a2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 13 Jan 2023 18:17:15 +0100 Subject: Clarify documentation of `window::frames` --- native/src/window.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'native') diff --git a/native/src/window.rs b/native/src/window.rs index bd92730d..a5cdc8ce 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), -- cgit