diff options
Diffstat (limited to 'widget/src/text_input.rs')
-rw-r--r-- | widget/src/text_input.rs | 251 |
1 files changed, 170 insertions, 81 deletions
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ff413779..51e61cc0 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -42,7 +42,6 @@ use editor::Editor; use crate::core::alignment; use crate::core::clipboard::{self, Clipboard}; -use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout; @@ -57,8 +56,8 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - Background, Border, Color, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Theme, Vector, Widget, + Background, Border, Color, Element, Event, Layout, Length, Padding, Pixels, + Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::runtime::task::{self, Task}; use crate::runtime::Action; @@ -120,6 +119,7 @@ pub struct TextInput< on_submit: Option<Message>, icon: Option<Icon<Renderer::Font>>, class: Theme::Class<'a>, + last_status: Option<Status>, } /// The default [`Padding`] of a [`TextInput`]. @@ -150,6 +150,7 @@ where on_submit: None, icon: None, class: Theme::default(), + last_status: None, } } @@ -400,7 +401,7 @@ where renderer: &mut Renderer, theme: &Theme, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, value: Option<&Value>, viewport: &Rectangle, ) { @@ -416,19 +417,8 @@ where let mut children_layout = layout.children(); let text_bounds = children_layout.next().unwrap().bounds(); - let is_mouse_over = cursor.is_over(bounds); - - let status = if is_disabled { - Status::Disabled - } else if state.is_focused() { - Status::Focused - } else if is_mouse_over { - Status::Hovered - } else { - Status::Active - }; - - let style = theme.style(&self.class, status); + let style = theme + .style(&self.class, self.last_status.unwrap_or(Status::Disabled)); renderer.fill_quad( renderer::Quad { @@ -637,7 +627,7 @@ where operation.text_input(state, self.id.as_ref().map(|id| &id.0)); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -647,7 +637,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let update_cache = |state, value| { replace_paragraph( renderer, @@ -660,22 +650,21 @@ where ); }; - match event { + match &event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { let state = state::<Renderer>(tree); + let cursor_before = state.cursor; let click_position = cursor.position_over(layout.bounds()); state.is_focused = if click_position.is_some() { - state.is_focused.or_else(|| { - let now = Instant::now(); - - Some(Focus { - updated_at: now, - now, - is_window_focused: true, - }) + let now = Instant::now(); + + Some(Focus { + updated_at: now, + now, + is_window_focused: true, }) } else { None @@ -760,7 +749,11 @@ where state.last_click = Some(click); - return event::Status::Captured; + if cursor_before != state.cursor { + shell.request_redraw(); + } + + shell.capture_event(); } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) @@ -801,11 +794,21 @@ where ) .unwrap_or(0); + let selection_before = state.cursor.selection(&value); + state .cursor .select_range(state.cursor.start(&value), position); - return event::Status::Captured; + if let Some(focus) = &mut state.is_focused { + focus.updated_at = Instant::now(); + } + + if selection_before != state.cursor.selection(&value) { + shell.request_redraw(); + } + + shell.capture_event(); } } Event::Keyboard(keyboard::Event::KeyPressed { @@ -815,7 +818,6 @@ where if let Some(focus) = &mut state.is_focused { let modifiers = state.keyboard_modifiers; - focus.updated_at = Instant::now(); match key.as_ref() { keyboard::Key::Character("c") @@ -831,14 +833,15 @@ where ); } - return event::Status::Captured; + shell.capture_event(); + return; } keyboard::Key::Character("x") if state.keyboard_modifiers.command() && !self.is_secure => { let Some(on_input) = &self.on_input else { - return event::Status::Ignored; + return; }; if let Some((start, end)) = @@ -856,17 +859,18 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + shell.capture_event(); + focus.updated_at = Instant::now(); update_cache(state, &self.value); - - return event::Status::Captured; + return; } keyboard::Key::Character("v") if state.keyboard_modifiers.command() && !state.keyboard_modifiers.alt() => { let Some(on_input) = &self.on_input else { - return event::Status::Ignored; + return; }; let content = match state.is_pasting.take() { @@ -885,7 +889,6 @@ where let mut editor = Editor::new(&mut self.value, &mut state.cursor); - editor.paste(content.clone()); let message = if let Some(paste) = &self.on_paste { @@ -894,26 +897,35 @@ where (on_input)(editor.contents()) }; shell.publish(message); + shell.capture_event(); state.is_pasting = Some(content); - + focus.updated_at = Instant::now(); update_cache(state, &self.value); - - return event::Status::Captured; + return; } keyboard::Key::Character("a") if state.keyboard_modifiers.command() => { + let cursor_before = state.cursor; + state.cursor.select_all(&self.value); - return event::Status::Captured; + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); + return; } _ => {} } if let Some(text) = text { let Some(on_input) = &self.on_input else { - return event::Status::Ignored; + return; }; state.is_pasting = None; @@ -928,12 +940,11 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + shell.capture_event(); focus.updated_at = Instant::now(); - update_cache(state, &self.value); - - return event::Status::Captured; + return; } } @@ -941,11 +952,12 @@ where keyboard::Key::Named(key::Named::Enter) => { if let Some(on_submit) = self.on_submit.clone() { shell.publish(on_submit); + shell.capture_event(); } } keyboard::Key::Named(key::Named::Backspace) => { let Some(on_input) = &self.on_input else { - return event::Status::Ignored; + return; }; if modifiers.jump() @@ -968,12 +980,14 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + shell.capture_event(); + focus.updated_at = Instant::now(); update_cache(state, &self.value); } keyboard::Key::Named(key::Named::Delete) => { let Some(on_input) = &self.on_input else { - return event::Status::Ignored; + return; }; if modifiers.jump() @@ -999,10 +1013,14 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + shell.capture_event(); + focus.updated_at = Instant::now(); update_cache(state, &self.value); } keyboard::Key::Named(key::Named::Home) => { + let cursor_before = state.cursor; + if modifiers.shift() { state.cursor.select_range( state.cursor.start(&self.value), @@ -1011,8 +1029,18 @@ where } else { state.cursor.move_to(0); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); } keyboard::Key::Named(key::Named::End) => { + let cursor_before = state.cursor; + if modifiers.shift() { state.cursor.select_range( state.cursor.start(&self.value), @@ -1021,10 +1049,20 @@ where } else { state.cursor.move_to(self.value.len()); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); } keyboard::Key::Named(key::Named::ArrowLeft) if modifiers.macos_command() => { + let cursor_before = state.cursor; + if modifiers.shift() { state.cursor.select_range( state.cursor.start(&self.value), @@ -1033,10 +1071,20 @@ where } else { state.cursor.move_to(0); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); } keyboard::Key::Named(key::Named::ArrowRight) if modifiers.macos_command() => { + let cursor_before = state.cursor; + if modifiers.shift() { state.cursor.select_range( state.cursor.start(&self.value), @@ -1045,8 +1093,18 @@ where } else { state.cursor.move_to(self.value.len()); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); } keyboard::Key::Named(key::Named::ArrowLeft) => { + let cursor_before = state.cursor; + if modifiers.jump() && !self.is_secure { if modifiers.shift() { state @@ -1062,8 +1120,18 @@ where } else { state.cursor.move_left(&self.value); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); } keyboard::Key::Named(key::Named::ArrowRight) => { + let cursor_before = state.cursor; + if modifiers.jump() && !self.is_secure { if modifiers.shift() { state @@ -1079,6 +1147,14 @@ where } else { state.cursor.move_right(&self.value); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw(); + } + + shell.capture_event(); } keyboard::Key::Named(key::Named::Escape) => { state.is_focused = None; @@ -1087,39 +1163,22 @@ where state.keyboard_modifiers = keyboard::Modifiers::default(); - } - keyboard::Key::Named( - key::Named::Tab - | key::Named::ArrowUp - | key::Named::ArrowDown, - ) => { - return event::Status::Ignored; + + shell.capture_event(); } _ => {} } - - return event::Status::Captured; } } Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => { let state = state::<Renderer>(tree); if state.is_focused.is_some() { - match key.as_ref() { - keyboard::Key::Character("v") => { - state.is_pasting = None; - } - keyboard::Key::Named( - key::Named::Tab - | key::Named::ArrowUp - | key::Named::ArrowDown, - ) => { - return event::Status::Ignored; - } - _ => {} - } + if let keyboard::Key::Character("v") = key.as_ref() { + state.is_pasting = None; - return event::Status::Captured; + shell.capture_event(); + } } state.is_pasting = None; @@ -1127,7 +1186,7 @@ where Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { let state = state::<Renderer>(tree); - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; } Event::Window(window::Event::Unfocused) => { let state = state::<Renderer>(tree); @@ -1143,32 +1202,59 @@ where focus.is_window_focused = true; focus.updated_at = Instant::now(); - shell.request_redraw(window::RedrawRequest::NextFrame); + shell.request_redraw(); } } Event::Window(window::Event::RedrawRequested(now)) => { let state = state::<Renderer>(tree); if let Some(focus) = &mut state.is_focused { - if focus.is_window_focused { - focus.now = now; + if focus.is_window_focused + && matches!( + state.cursor.state(&self.value), + cursor::State::Index(_) + ) + { + focus.now = *now; let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS - - (now - focus.updated_at).as_millis() + - (*now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS; - shell.request_redraw(window::RedrawRequest::At( - now + Duration::from_millis( + shell.request_redraw_at( + *now + Duration::from_millis( millis_until_redraw as u64, ), - )); + ); } } } _ => {} } - event::Status::Ignored + let state = state::<Renderer>(tree); + let is_disabled = self.on_input.is_none(); + + let status = if is_disabled { + Status::Disabled + } else if state.is_focused() { + Status::Focused { + is_hovered: cursor.is_over(layout.bounds()), + } + } else if cursor.is_over(layout.bounds()) { + Status::Hovered + } else { + Status::Active + }; + + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + self.last_status = Some(status); + } else if self + .last_status + .is_some_and(|last_status| status != last_status) + { + shell.request_redraw(); + } } fn draw( @@ -1535,7 +1621,10 @@ pub enum Status { /// The [`TextInput`] is being hovered. Hovered, /// The [`TextInput`] is focused. - Focused, + Focused { + /// Whether the [`TextInput`] is hovered, while focused. + is_hovered: bool, + }, /// The [`TextInput`] cannot be interacted with. Disabled, } @@ -1612,7 +1701,7 @@ pub fn default(theme: &Theme, status: Status) -> Style { }, ..active }, - Status::Focused => Style { + Status::Focused { .. } => Style { border: Border { color: palette.primary.strong.color, ..active.border |