diff options
Diffstat (limited to 'native/src/widget')
-rw-r--r-- | native/src/widget/text_input.rs | 103 |
1 files changed, 79 insertions, 24 deletions
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 8b4514e3..8755b85d 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -18,10 +18,12 @@ 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}; use crate::widget::tree::{self, Tree}; +use crate::window; use crate::{ Clipboard, Color, Command, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, Widget, @@ -425,7 +427,18 @@ where let state = state(); let is_clicked = layout.bounds().contains(cursor_position); - state.is_focused = is_clicked; + state.is_focused = if is_clicked { + state.is_focused.or_else(|| { + let now = Instant::now(); + + Some(Focus { + updated_at: now, + now, + }) + }) + } else { + None + }; if is_clicked { let text_layout = layout.children().next().unwrap(); @@ -541,26 +554,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.updated_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.updated_at = Instant::now(); match key_code { keyboard::KeyCode::Enter @@ -721,7 +738,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 +759,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 +782,21 @@ where state.keyboard_modifiers = modifiers; } + Event::Window(window::Event::RedrawRequested(now)) => { + let state = state(); + + if let Some(focus) = &mut state.is_focused { + focus.now = now; + + let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS + - (now - focus.updated_at).as_millis() + % CURSOR_BLINK_INTERVAL_MILLIS; + + shell.request_redraw(window::RedrawRequest::At( + now + Duration::from_millis(millis_until_redraw as u64), + )); + } + } _ => {} } @@ -820,7 +852,7 @@ pub fn draw<Renderer>( 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 +865,13 @@ pub fn draw<Renderer>( font.clone(), ); - ( + let is_cursor_visible = ((focus.now - focus.updated_at) + .as_millis() + / CURSOR_BLINK_INTERVAL_MILLIS) + % 2 + == 0; + + let cursor = if is_cursor_visible { Some(( renderer::Quad { bounds: Rectangle { @@ -847,9 +885,12 @@ pub fn draw<Renderer>( 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 +999,7 @@ pub fn mouse_interaction( /// The state of a [`TextInput`]. #[derive(Debug, Default, Clone)] pub struct State { - is_focused: bool, + is_focused: Option<Focus>, is_dragging: bool, is_pasting: Option<Value>, last_click: Option<mouse::Click>, @@ -967,6 +1008,12 @@ pub struct State { // TODO: Add stateful horizontal scrolling offset } +#[derive(Debug, Clone, Copy)] +struct Focus { + updated_at: Instant, + now: Instant, +} + impl State { /// Creates a new [`State`], representing an unfocused [`TextInput`]. pub fn new() -> Self { @@ -976,7 +1023,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 +1034,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 +1044,19 @@ impl State { /// Focuses the [`TextInput`]. pub fn focus(&mut self) { - self.is_focused = true; + let now = Instant::now(); + + self.is_focused = Some(Focus { + updated_at: now, + 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 +1209,5 @@ where ) .map(text::Hit::cursor) } + +const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500; |