diff options
Diffstat (limited to '')
-rw-r--r-- | widget/src/text/rich.rs | 20 | ||||
-rw-r--r-- | widget/src/text_editor.rs | 332 | ||||
-rw-r--r-- | widget/src/text_input.rs | 251 | ||||
-rw-r--r-- | widget/src/text_input/cursor.rs | 4 |
4 files changed, 362 insertions, 245 deletions
diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index 3d241375..7ef2707b 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -1,5 +1,4 @@ use crate::core::alignment; -use crate::core::event; use crate::core::layout; use crate::core::mouse; use crate::core::renderer; @@ -355,7 +354,7 @@ where ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -365,7 +364,7 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Link>, _viewport: &Rectangle, - ) -> event::Status { + ) { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if let Some(position) = cursor.position_in(layout.bounds()) { @@ -374,9 +373,16 @@ where .downcast_mut::<State<Link, Renderer::Paragraph>>(); if let Some(span) = state.paragraph.hit_span(position) { - state.span_pressed = Some(span); - - return event::Status::Captured; + if self + .spans + .as_ref() + .as_ref() + .get(span) + .is_some_and(|span| span.link.is_some()) + { + state.span_pressed = Some(span); + shell.capture_event(); + } } } } @@ -409,8 +415,6 @@ where } _ => {} } - - event::Status::Ignored } fn mouse_interaction( diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index a298252a..b6e291e4 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -33,7 +33,6 @@ //! ``` 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::{self, Layout}; @@ -47,7 +46,7 @@ use crate::core::widget::operation; use crate::core::widget::{self, Widget}; use crate::core::window; use crate::core::{ - Background, Border, Color, Element, Length, Padding, Pixels, Point, + Background, Border, Color, Element, Event, Length, Padding, Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector, }; @@ -120,6 +119,7 @@ pub struct TextEditor< &Highlighter::Highlight, &Theme, ) -> highlighter::Format<Renderer::Font>, + last_status: Option<Status>, } impl<'a, Message, Theme, Renderer> @@ -147,6 +147,7 @@ where highlighter_format: |_highlight, _theme| { highlighter::Format::default() }, + last_status: None, } } } @@ -270,6 +271,7 @@ where on_edit: self.on_edit, highlighter_settings: settings, highlighter_format: to_format, + last_status: self.last_status, } } @@ -596,7 +598,7 @@ where } } - fn on_event( + fn update( &mut self, tree: &mut widget::Tree, event: Event, @@ -606,12 +608,16 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let Some(on_edit) = self.on_edit.as_ref() else { - return event::Status::Ignored; + return; }; let state = tree.state.downcast_mut::<State<Highlighter>>(); + let is_redraw = matches!( + event, + Event::Window(window::Event::RedrawRequested(_now)), + ); match event { Event::Window(window::Event::Unfocused) => { @@ -624,7 +630,7 @@ 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)) => { @@ -637,168 +643,193 @@ where - (now - focus.updated_at).as_millis() % Focus::CURSOR_BLINK_INTERVAL_MILLIS; - shell.request_redraw(window::RedrawRequest::At( + shell.request_redraw_at( now + Duration::from_millis( millis_until_redraw as u64, ), - )); + ); } } } _ => {} } - let Some(update) = Update::from_event( + if let Some(update) = Update::from_event( event, state, layout.bounds(), self.padding, cursor, self.key_binding.as_deref(), - ) else { - return event::Status::Ignored; - }; - - match update { - Update::Click(click) => { - let action = match click.kind() { - mouse::click::Kind::Single => { - Action::Click(click.position()) - } - mouse::click::Kind::Double => Action::SelectWord, - mouse::click::Kind::Triple => Action::SelectLine, - }; - - state.focus = Some(Focus::now()); - state.last_click = Some(click); - state.drag_click = Some(click.kind()); + ) { + match update { + Update::Click(click) => { + let action = match click.kind() { + mouse::click::Kind::Single => { + Action::Click(click.position()) + } + mouse::click::Kind::Double => Action::SelectWord, + mouse::click::Kind::Triple => Action::SelectLine, + }; - shell.publish(on_edit(action)); - } - Update::Drag(position) => { - shell.publish(on_edit(Action::Drag(position))); - } - Update::Release => { - state.drag_click = None; - } - Update::Scroll(lines) => { - let bounds = self.content.0.borrow().editor.bounds(); + state.focus = Some(Focus::now()); + state.last_click = Some(click); + state.drag_click = Some(click.kind()); - if bounds.height >= i32::MAX as f32 { - return event::Status::Ignored; + shell.publish(on_edit(action)); + shell.capture_event(); + } + Update::Drag(position) => { + shell.publish(on_edit(Action::Drag(position))); } + Update::Release => { + state.drag_click = None; + } + Update::Scroll(lines) => { + let bounds = self.content.0.borrow().editor.bounds(); - let lines = lines + state.partial_scroll; - state.partial_scroll = lines.fract(); + if bounds.height >= i32::MAX as f32 { + return; + } - shell.publish(on_edit(Action::Scroll { - lines: lines as i32, - })); - } - Update::Binding(binding) => { - fn apply_binding< - H: text::Highlighter, - R: text::Renderer, - Message, - >( - binding: Binding<Message>, - content: &Content<R>, - state: &mut State<H>, - on_edit: &dyn Fn(Action) -> Message, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) { - let mut publish = |action| shell.publish(on_edit(action)); - - match binding { - Binding::Unfocus => { - state.focus = None; - state.drag_click = None; - } - Binding::Copy => { - if let Some(selection) = content.selection() { - clipboard.write( - clipboard::Kind::Standard, - selection, - ); - } - } - Binding::Cut => { - if let Some(selection) = content.selection() { - clipboard.write( - clipboard::Kind::Standard, - selection, - ); + let lines = lines + state.partial_scroll; + state.partial_scroll = lines.fract(); + shell.publish(on_edit(Action::Scroll { + lines: lines as i32, + })); + shell.capture_event(); + } + Update::Binding(binding) => { + fn apply_binding< + H: text::Highlighter, + R: text::Renderer, + Message, + >( + binding: Binding<Message>, + content: &Content<R>, + state: &mut State<H>, + on_edit: &dyn Fn(Action) -> Message, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) { + let mut publish = + |action| shell.publish(on_edit(action)); + + match binding { + Binding::Unfocus => { + state.focus = None; + state.drag_click = None; + } + Binding::Copy => { + if let Some(selection) = content.selection() { + clipboard.write( + clipboard::Kind::Standard, + selection, + ); + } + } + Binding::Cut => { + if let Some(selection) = content.selection() { + clipboard.write( + clipboard::Kind::Standard, + selection, + ); + + publish(Action::Edit(Edit::Delete)); + } + } + Binding::Paste => { + if let Some(contents) = + clipboard.read(clipboard::Kind::Standard) + { + publish(Action::Edit(Edit::Paste( + Arc::new(contents), + ))); + } + } + Binding::Move(motion) => { + publish(Action::Move(motion)); + } + Binding::Select(motion) => { + publish(Action::Select(motion)); + } + Binding::SelectWord => { + publish(Action::SelectWord); + } + Binding::SelectLine => { + publish(Action::SelectLine); + } + Binding::SelectAll => { + publish(Action::SelectAll); + } + Binding::Insert(c) => { + publish(Action::Edit(Edit::Insert(c))); + } + Binding::Enter => { + publish(Action::Edit(Edit::Enter)); + } + Binding::Backspace => { + publish(Action::Edit(Edit::Backspace)); + } + Binding::Delete => { publish(Action::Edit(Edit::Delete)); } - } - Binding::Paste => { - if let Some(contents) = - clipboard.read(clipboard::Kind::Standard) - { - publish(Action::Edit(Edit::Paste(Arc::new( - contents, - )))); + Binding::Sequence(sequence) => { + for binding in sequence { + apply_binding( + binding, content, state, on_edit, + clipboard, shell, + ); + } } - } - Binding::Move(motion) => { - publish(Action::Move(motion)); - } - Binding::Select(motion) => { - publish(Action::Select(motion)); - } - Binding::SelectWord => { - publish(Action::SelectWord); - } - Binding::SelectLine => { - publish(Action::SelectLine); - } - Binding::SelectAll => { - publish(Action::SelectAll); - } - Binding::Insert(c) => { - publish(Action::Edit(Edit::Insert(c))); - } - Binding::Enter => { - publish(Action::Edit(Edit::Enter)); - } - Binding::Backspace => { - publish(Action::Edit(Edit::Backspace)); - } - Binding::Delete => { - publish(Action::Edit(Edit::Delete)); - } - Binding::Sequence(sequence) => { - for binding in sequence { - apply_binding( - binding, content, state, on_edit, - clipboard, shell, - ); + Binding::Custom(message) => { + shell.publish(message); } } - Binding::Custom(message) => { - shell.publish(message); - } } - } - apply_binding( - binding, - self.content, - state, - on_edit, - clipboard, - shell, - ); + apply_binding( + binding, + self.content, + state, + on_edit, + clipboard, + shell, + ); + + if let Some(focus) = &mut state.focus { + focus.updated_at = Instant::now(); + } - if let Some(focus) = &mut state.focus { - focus.updated_at = Instant::now(); + shell.capture_event(); } } } - event::Status::Captured + let status = { + let is_disabled = self.on_edit.is_none(); + let is_hovered = cursor.is_over(layout.bounds()); + + if is_disabled { + Status::Disabled + } else if state.focus.is_some() { + Status::Focused { is_hovered } + } else if is_hovered { + Status::Hovered + } else { + Status::Active + } + }; + + if is_redraw { + self.last_status = Some(status); + } else if self + .last_status + .is_some_and(|last_status| status != last_status) + { + shell.request_redraw(); + } } fn draw( @@ -808,7 +839,7 @@ where theme: &Theme, _defaults: &renderer::Style, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, _viewport: &Rectangle, ) { let bounds = layout.bounds(); @@ -824,20 +855,8 @@ where |highlight| (self.highlighter_format)(highlight, theme), ); - let is_disabled = self.on_edit.is_none(); - let is_mouse_over = cursor.is_over(bounds); - - let status = if is_disabled { - Status::Disabled - } else if state.focus.is_some() { - 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::Active)); renderer.fill_quad( renderer::Quad { @@ -1036,7 +1055,7 @@ impl<Message> Binding<Message> { status, } = event; - if status != Status::Focused { + if !matches!(status, Status::Focused { .. }) { return None; } @@ -1176,7 +1195,9 @@ impl<Message> Update<Message> { .. }) => { let status = if state.focus.is_some() { - Status::Focused + Status::Focused { + is_hovered: cursor.is_over(bounds), + } } else { Status::Active }; @@ -1222,7 +1243,10 @@ pub enum Status { /// The [`TextEditor`] is being hovered. Hovered, /// The [`TextEditor`] is focused. - Focused, + Focused { + /// Whether the [`TextEditor`] is hovered, while focused. + is_hovered: bool, + }, /// The [`TextEditor`] cannot be interacted with. Disabled, } @@ -1297,7 +1321,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 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 diff --git a/widget/src/text_input/cursor.rs b/widget/src/text_input/cursor.rs index f682b17d..a326fc8f 100644 --- a/widget/src/text_input/cursor.rs +++ b/widget/src/text_input/cursor.rs @@ -2,13 +2,13 @@ use crate::text_input::Value; /// The cursor of a text input. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Cursor { state: State, } /// The state of a [`Cursor`]. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum State { /// Cursor without a selection Index(usize), |