diff options
-rw-r--r-- | widget/src/text_input.rs | 223 | ||||
-rw-r--r-- | widget/src/text_input/cursor.rs | 4 | ||||
-rw-r--r-- | winit/src/program.rs | 48 | ||||
-rw-r--r-- | winit/src/program/window_manager.rs | 16 |
4 files changed, 202 insertions, 89 deletions
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ff413779..c18009a2 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -120,6 +120,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 +151,7 @@ where on_submit: None, icon: None, class: Theme::default(), + last_status: None, } } @@ -400,7 +402,7 @@ where renderer: &mut Renderer, theme: &Theme, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, value: Option<&Value>, viewport: &Rectangle, ) { @@ -416,19 +418,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 { @@ -660,22 +651,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,6 +750,10 @@ where state.last_click = Some(click); + if cursor_before != state.cursor { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + return event::Status::Captured; } } @@ -801,10 +795,20 @@ where ) .unwrap_or(0); + let selection_before = state.cursor.selection(&value); + state .cursor .select_range(state.cursor.start(&value), position); + if let Some(focus) = &mut state.is_focused { + focus.updated_at = Instant::now(); + } + + if selection_before != state.cursor.selection(&value) { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + return event::Status::Captured; } } @@ -815,7 +819,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") @@ -857,6 +860,7 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + focus.updated_at = Instant::now(); update_cache(state, &self.value); return event::Status::Captured; @@ -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 { @@ -896,7 +899,7 @@ where shell.publish(message); state.is_pasting = Some(content); - + focus.updated_at = Instant::now(); update_cache(state, &self.value); return event::Status::Captured; @@ -904,8 +907,18 @@ where keyboard::Key::Character("a") if state.keyboard_modifiers.command() => { + let cursor_before = state.cursor; + state.cursor.select_all(&self.value); + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + return event::Status::Captured; } _ => {} @@ -930,7 +943,6 @@ where shell.publish(message); focus.updated_at = Instant::now(); - update_cache(state, &self.value); return event::Status::Captured; @@ -941,6 +953,8 @@ where keyboard::Key::Named(key::Named::Enter) => { if let Some(on_submit) = self.on_submit.clone() { shell.publish(on_submit); + + return event::Status::Captured; } } keyboard::Key::Named(key::Named::Backspace) => { @@ -969,7 +983,10 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + focus.updated_at = Instant::now(); update_cache(state, &self.value); + + return event::Status::Captured; } keyboard::Key::Named(key::Named::Delete) => { let Some(on_input) = &self.on_input else { @@ -1000,9 +1017,14 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + focus.updated_at = Instant::now(); update_cache(state, &self.value); + + return event::Status::Captured; } 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 +1033,20 @@ where } else { state.cursor.move_to(0); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + + return event::Status::Captured; } 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 +1055,22 @@ where } else { state.cursor.move_to(self.value.len()); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + + return event::Status::Captured; } 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 +1079,22 @@ where } else { state.cursor.move_to(0); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + + return event::Status::Captured; } 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 +1103,20 @@ where } else { state.cursor.move_to(self.value.len()); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + + return event::Status::Captured; } keyboard::Key::Named(key::Named::ArrowLeft) => { + let cursor_before = state.cursor; + if modifiers.jump() && !self.is_secure { if modifiers.shift() { state @@ -1062,8 +1132,20 @@ where } else { state.cursor.move_left(&self.value); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + + return event::Status::Captured; } keyboard::Key::Named(key::Named::ArrowRight) => { + let cursor_before = state.cursor; + if modifiers.jump() && !self.is_secure { if modifiers.shift() { state @@ -1079,6 +1161,16 @@ where } else { state.cursor.move_right(&self.value); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + + return event::Status::Captured; } keyboard::Key::Named(key::Named::Escape) => { state.is_focused = None; @@ -1087,39 +1179,22 @@ where state.keyboard_modifiers = keyboard::Modifiers::default(); - } - keyboard::Key::Named( - key::Named::Tab - | key::Named::ArrowUp - | key::Named::ArrowDown, - ) => { - return event::Status::Ignored; + + return event::Status::Captured; } _ => {} } - - 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; + return event::Status::Captured; + } } state.is_pasting = None; @@ -1127,7 +1202,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); @@ -1150,15 +1225,20 @@ where 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( + *now + Duration::from_millis( millis_until_redraw as u64, ), )); @@ -1168,6 +1248,32 @@ where _ => {} } + 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 { + match self.last_status { + Some(last_status) if status != last_status => { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + _ => {} + } + } + event::Status::Ignored } @@ -1535,7 +1641,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 +1721,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), diff --git a/winit/src/program.rs b/winit/src/program.rs index 579038af..a6729fa0 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -691,7 +691,6 @@ async fn run_instance<P, C>( let mut ui_caches = FxHashMap::default(); let mut user_interfaces = ManuallyDrop::new(FxHashMap::default()); let mut clipboard = Clipboard::unconnected(); - let mut redraw_queue = Vec::new(); debug.startup_finished(); @@ -769,17 +768,12 @@ async fn run_instance<P, C>( ) => { let now = Instant::now(); - while let Some((target, id)) = - redraw_queue.last().copied() - { - if target > now { - break; - } - - let _ = redraw_queue.pop(); - - if let Some(window) = window_manager.get_mut(id) { - window.raw.request_redraw(); + for (_id, window) in window_manager.iter_mut() { + if let Some(redraw_at) = window.redraw_at { + if redraw_at <= now { + window.raw.request_redraw(); + window.redraw_at = None; + } } } } @@ -878,7 +872,7 @@ async fn run_instance<P, C>( window.raw.request_redraw(); } window::RedrawRequest::At(at) => { - redraw_queue.push((at, id)); + window.redraw_at = Some(at); } } } @@ -1039,7 +1033,10 @@ async fn run_instance<P, C>( } } event::Event::AboutToWait => { - if events.is_empty() && messages.is_empty() { + if events.is_empty() + && messages.is_empty() + && window_manager.is_idle() + { continue; } @@ -1085,7 +1082,7 @@ async fn run_instance<P, C>( window.raw.request_redraw(); } window::RedrawRequest::At(at) => { - redraw_queue.push((at, id)); + window.redraw_at = Some(at); } }, user_interface::State::Outdated => { @@ -1160,24 +1157,15 @@ async fn run_instance<P, C>( } } - if !redraw_queue.is_empty() { - // The queue should be fairly short, so we can - // simply sort all of the time. - redraw_queue.sort_by( - |(target_a, _), (target_b, _)| { - target_a.cmp(target_b).reverse() - }, - ); - - let (target, _id) = redraw_queue - .last() - .copied() - .expect("Redraw queue is not empty"); - + if let Some(redraw_at) = window_manager.redraw_at() { let _ = control_sender.start_send(Control::ChangeFlow( - ControlFlow::WaitUntil(target), + ControlFlow::WaitUntil(redraw_at), )); + } else { + let _ = control_sender.start_send( + Control::ChangeFlow(ControlFlow::Wait), + ); } } _ => {} diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index 3d22e155..7c00a84b 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -1,4 +1,5 @@ use crate::core::mouse; +use crate::core::time::Instant; use crate::core::window::Id; use crate::core::{Point, Size}; use crate::graphics::Compositor; @@ -62,6 +63,7 @@ where surface, renderer, mouse_interaction: mouse::Interaction::None, + redraw_at: None, }, ); @@ -74,6 +76,19 @@ where self.entries.is_empty() } + pub fn is_idle(&self) -> bool { + self.entries + .values() + .any(|window| window.redraw_at.is_some()) + } + + pub fn redraw_at(&self) -> Option<Instant> { + self.entries + .values() + .filter_map(|window| window.redraw_at) + .min() + } + pub fn first(&self) -> Option<&Window<P, C>> { self.entries.first_key_value().map(|(_id, window)| window) } @@ -138,6 +153,7 @@ where pub mouse_interaction: mouse::Interaction, pub surface: C::Surface, pub renderer: P::Renderer, + pub redraw_at: Option<Instant>, } impl<P, C> Window<P, C> |