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/widget/text_input.rs | 102 ++++++++++++++++++++++++++++++---------- 1 file changed, 78 insertions(+), 24 deletions(-) (limited to 'native/src/widget') 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; -- 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/src/widget') 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/src/widget') 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 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/src/widget') 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/widget/text_input.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'native/src/widget') 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), - ); + )); } } _ => {} -- 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/src/widget/text_input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native/src/widget') 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}; -- 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/src/widget/text_input.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'native/src/widget') 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. -- cgit