From 7db5256b720c3ecbe7c1cce7a1b47fd03151e03a Mon Sep 17 00:00:00 2001 From: KENZ Date: Fri, 10 Jan 2025 07:12:31 +0900 Subject: [PATCH 01/20] Draft `input_method` support --- core/src/event.rs | 4 + core/src/input_method.rs | 24 +++++ core/src/lib.rs | 2 + core/src/shell.rs | 25 +++++- runtime/src/user_interface.rs | 14 ++- widget/src/combo_box.rs | 6 +- widget/src/lazy/component.rs | 2 + widget/src/scrollable.rs | 19 +++- widget/src/text_editor.rs | 69 ++++++++++++++- widget/src/text_input.rs | 95 +++++++++++++++++++- winit/src/conversion.rs | 11 +++ winit/src/program.rs | 162 ++++++++++++++++++++++++++++++---- winit/src/program/state.rs | 14 ++- 13 files changed, 420 insertions(+), 27 deletions(-) create mode 100644 core/src/input_method.rs diff --git a/core/src/event.rs b/core/src/event.rs index b6cf321ec3..323c5ffd61 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -1,4 +1,5 @@ //! Handle events of a user interface. +use crate::input_method; use crate::keyboard; use crate::mouse; use crate::touch; @@ -23,6 +24,9 @@ pub enum Event { /// A touch event Touch(touch::Event), + + /// A input method event + InputMethod(input_method::Event), } /// The status of an [`Event`] after being processed. diff --git a/core/src/input_method.rs b/core/src/input_method.rs new file mode 100644 index 0000000000..ceda39c389 --- /dev/null +++ b/core/src/input_method.rs @@ -0,0 +1,24 @@ +//! Listen to input method events. + +/// A input method event. +/// +/// _**Note:** This type is largely incomplete! If you need to track +/// additional events, feel free to [open an issue] and share your use case!_ +/// +/// [open an issue]: https://github.com/iced-rs/iced/issues +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Event { + // These events correspond to underlying winit ime events. + // https://docs.rs/winit/latest/winit/event/enum.Ime.html + /// the IME was enabled. + Enabled, + + /// new composing text should be set at the cursor position. + Preedit(String, Option<(usize, usize)>), + + /// text should be inserted into the editor widget. + Commit(String), + + /// the IME was disabled. + Disabled, +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 16b3aa0f27..c7c3804455 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -17,6 +17,7 @@ pub mod event; pub mod font; pub mod gradient; pub mod image; +pub mod input_method; pub mod keyboard; pub mod layout; pub mod mouse; @@ -72,6 +73,7 @@ pub use renderer::Renderer; pub use rotation::Rotation; pub use settings::Settings; pub use shadow::Shadow; +pub use shell::CaretInfo; pub use shell::Shell; pub use size::Size; pub use svg::Svg; diff --git a/core/src/shell.rs b/core/src/shell.rs index 12ebbaa89a..d2c1b9ecf7 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -1,6 +1,15 @@ -use crate::event; use crate::time::Instant; use crate::window; +use crate::{event, Point}; + +/// TODO +#[derive(Clone, Copy, Debug)] +pub struct CaretInfo { + /// TODO + pub position: Point, + /// TODO + pub input_method_allowed: bool, +} /// A connection to the state of a shell. /// @@ -15,6 +24,7 @@ pub struct Shell<'a, Message> { redraw_request: Option, is_layout_invalid: bool, are_widgets_invalid: bool, + caret_info: Option, } impl<'a, Message> Shell<'a, Message> { @@ -26,6 +36,7 @@ impl<'a, Message> Shell<'a, Message> { redraw_request: None, is_layout_invalid: false, are_widgets_invalid: false, + caret_info: None, } } @@ -80,6 +91,16 @@ impl<'a, Message> Shell<'a, Message> { self.redraw_request } + /// TODO + pub fn update_caret_info(&mut self, caret_info: Option) { + self.caret_info = caret_info.or(self.caret_info); + } + + /// TODO + pub fn caret_info(&self) -> Option { + self.caret_info + } + /// Returns whether the current layout is invalid or not. pub fn is_layout_invalid(&self) -> bool { self.is_layout_invalid @@ -130,6 +151,8 @@ impl<'a, Message> Shell<'a, Message> { ); } + self.update_caret_info(other.caret_info()); + self.is_layout_invalid = self.is_layout_invalid || other.is_layout_invalid; diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index b2826f7114..6d3360d0c1 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -5,7 +5,9 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget; use crate::core::window; -use crate::core::{Clipboard, Element, Layout, Rectangle, Shell, Size, Vector}; +use crate::core::{ + CaretInfo, Clipboard, Element, Layout, Rectangle, Shell, Size, Vector, +}; use crate::overlay; /// A set of interactive graphical elements with a specific [`Layout`]. @@ -187,6 +189,7 @@ where let mut outdated = false; let mut redraw_request = None; + let mut caret_info = None; let mut manual_overlay = ManuallyDrop::new( self.root @@ -230,6 +233,7 @@ where } _ => {} } + caret_info = caret_info.or(shell.caret_info()); if shell.is_layout_invalid() { let _ = ManuallyDrop::into_inner(manual_overlay); @@ -332,6 +336,7 @@ where } _ => {} } + caret_info = caret_info.or(shell.caret_info()); shell.revalidate_layout(|| { self.base = self.root.as_widget().layout( @@ -355,7 +360,10 @@ where if outdated { State::Outdated } else { - State::Updated { redraw_request } + State::Updated { + redraw_request, + caret_info, + } }, event_statuses, ) @@ -646,5 +654,7 @@ pub enum State { Updated { /// The [`window::RedrawRequest`] when a redraw should be performed. redraw_request: Option, + /// TODO + caret_info: Option, }, } diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 500d2bec56..d7c7c92244 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -564,6 +564,7 @@ where } } } + shell.update_caret_info(local_shell.caret_info()); // Then finally react to them here for message in local_messages { @@ -742,6 +743,8 @@ where published_message_to_shell = true; // Unfocus the input + let mut local_messages = Vec::new(); + let mut local_shell = Shell::new(&mut local_messages); self.text_input.update( &mut tree.children[0], Event::Mouse(mouse::Event::ButtonPressed( @@ -751,9 +754,10 @@ where mouse::Cursor::Unavailable, renderer, clipboard, - &mut Shell::new(&mut vec![]), + &mut local_shell, viewport, ); + shell.update_caret_info(local_shell.caret_info()); } }); diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index 15b8b62ed3..b9fbde58d3 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -355,6 +355,7 @@ where } } } + shell.update_caret_info(local_shell.caret_info()); if !local_messages.is_empty() { let mut heads = self.state.take().unwrap().into_heads(); @@ -640,6 +641,7 @@ where } } } + shell.update_caret_info(local_shell.caret_info()); if !local_messages.is_empty() { let mut inner = diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 312aee2968..7df7a0e5c0 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -33,8 +33,9 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - self, Background, Clipboard, Color, Element, Event, Layout, Length, - Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, + self, Background, CaretInfo, Clipboard, Color, Element, Event, Layout, + Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, + Widget, }; use crate::runtime::task::{self, Task}; use crate::runtime::Action; @@ -729,6 +730,7 @@ where let translation = state.translation(self.direction, bounds, content_bounds); + let children_may_have_caret = shell.caret_info().is_none(); self.content.as_widget_mut().update( &mut tree.children[0], event.clone(), @@ -743,6 +745,19 @@ where ..bounds }, ); + + if children_may_have_caret { + if let Some(caret_info) = shell.caret_info() { + shell.update_caret_info(Some(CaretInfo { + position: Point::new( + caret_info.position.x - translation.x, + caret_info.position.y - translation.y, + ), + input_method_allowed: caret_info + .input_method_allowed, + })); + } + } }; if matches!( diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index f1ec589b04..2931e7f616 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -33,6 +33,7 @@ //! ``` use crate::core::alignment; use crate::core::clipboard::{self, Clipboard}; +use crate::core::input_method; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout::{self, Layout}; @@ -46,8 +47,8 @@ use crate::core::widget::operation; use crate::core::widget::{self, Widget}; use crate::core::window; use crate::core::{ - Background, Border, Color, Element, Event, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, SmolStr, Theme, Vector, + Background, Border, CaretInfo, Color, Element, Event, Length, Padding, + Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector, }; use std::borrow::Cow; @@ -322,6 +323,46 @@ where self.class = class.into(); self } + + fn caret_rect( + &self, + tree: &widget::Tree, + renderer: &Renderer, + layout: Layout<'_>, + ) -> Option { + let bounds = layout.bounds(); + + let internal = self.content.0.borrow_mut(); + let state = tree.state.downcast_ref::>(); + + let text_bounds = bounds.shrink(self.padding); + let translation = text_bounds.position() - Point::ORIGIN; + + if let Some(_) = state.focus.as_ref() { + let position = match internal.editor.cursor() { + Cursor::Caret(position) => position, + Cursor::Selection(ranges) => ranges + .first() + .cloned() + .unwrap_or(Rectangle::default()) + .position(), + }; + Some(Rectangle::new( + position + translation, + Size::new( + 1.0, + self.line_height + .to_absolute( + self.text_size + .unwrap_or_else(|| renderer.default_size()), + ) + .into(), + ), + )) + } else { + None + } + } } /// The content of a [`TextEditor`]. @@ -605,7 +646,7 @@ where event: Event, layout: Layout<'_>, cursor: mouse::Cursor, - _renderer: &Renderer, + renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, @@ -701,6 +742,11 @@ where })); shell.capture_event(); } + Update::Commit(text) => { + shell.publish(on_edit(Action::Edit(Edit::Paste( + Arc::new(text), + )))); + } Update::Binding(binding) => { fn apply_binding< H: text::Highlighter, @@ -825,6 +871,19 @@ where } }; + shell.update_caret_info(if state.is_focused() { + let rect = self + .caret_rect(tree, renderer, layout) + .unwrap_or(Rectangle::default()); + let bottom_left = Point::new(rect.x, rect.y + rect.height); + Some(CaretInfo { + position: bottom_left, + input_method_allowed: true, + }) + } else { + None + }); + if is_redraw { self.last_status = Some(status); } else if self @@ -1129,6 +1188,7 @@ enum Update { Drag(Point), Release, Scroll(f32), + Commit(String), Binding(Binding), } @@ -1191,6 +1251,9 @@ impl Update { } _ => None, }, + Event::InputMethod(input_method::Event::Commit(text)) => { + Some(Update::Commit(text)) + } Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 57ebe46a12..f2756b5be1 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -36,6 +36,7 @@ mod value; pub mod cursor; pub use cursor::Cursor; +use iced_runtime::core::input_method; pub use value::Value; use editor::Editor; @@ -56,8 +57,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, Event, Layout, Length, Padding, Pixels, - Point, Rectangle, Shell, Size, Theme, Vector, Widget, + Background, Border, CaretInfo, Color, Element, Event, Layout, Length, + Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::runtime::task::{self, Task}; use crate::runtime::Action; @@ -391,6 +392,58 @@ where } } + fn caret_rect( + &self, + tree: &Tree, + layout: Layout<'_>, + value: Option<&Value>, + ) -> Option { + let state = tree.state.downcast_ref::>(); + let value = value.unwrap_or(&self.value); + + let secure_value = self.is_secure.then(|| value.secure()); + let value = secure_value.as_ref().unwrap_or(value); + + let mut children_layout = layout.children(); + let text_bounds = children_layout.next().unwrap().bounds(); + + if let Some(_) = state + .is_focused + .as_ref() + .filter(|focus| focus.is_window_focused) + { + let caret_index = match state.cursor.state(value) { + cursor::State::Index(position) => position, + cursor::State::Selection { start, end } => { + let left = start.min(end); + left + } + }; + let text = state.value.raw(); + let (caret_x, offset) = measure_cursor_and_scroll_offset( + text, + text_bounds, + caret_index, + ); + + let alignment_offset = alignment_offset( + text_bounds.width, + text.min_width(), + self.alignment, + ); + + let x = (text_bounds.x + caret_x).floor(); + Some(Rectangle { + x: (alignment_offset - offset) + x, + y: text_bounds.y, + width: 1.0, + height: text_bounds.height, + }) + } else { + None + } + } + /// Draws the [`TextInput`] with the given [`Renderer`], overriding its /// [`Value`] if provided. /// @@ -1197,6 +1250,31 @@ where state.keyboard_modifiers = *modifiers; } + Event::InputMethod(input_method::Event::Commit(string)) => { + let state = state::(tree); + + if let Some(focus) = &mut state.is_focused { + let Some(on_input) = &self.on_input else { + return; + }; + + state.is_pasting = None; + + let mut editor = + Editor::new(&mut self.value, &mut state.cursor); + + editor.paste(Value::new(&string)); + + let message = (on_input)(editor.contents()); + shell.publish(message); + + focus.updated_at = Instant::now(); + + update_cache(state, &self.value); + + shell.capture_event(); + } + } Event::Window(window::Event::Unfocused) => { let state = state::(tree); @@ -1256,6 +1334,19 @@ where Status::Active }; + shell.update_caret_info(if state.is_focused() { + let rect = self + .caret_rect(tree, layout, Some(&self.value)) + .unwrap_or(Rectangle::with_size(Size::::default())); + let bottom_left = Point::new(rect.x, rect.y + rect.height); + Some(CaretInfo { + position: bottom_left, + input_method_allowed: true, + }) + } else { + None + }); + if let Event::Window(window::Event::RedrawRequested(_now)) = event { self.last_status = Some(status); } else if self diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 462be65b03..a289f0609f 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -2,6 +2,7 @@ //! //! [`winit`]: https://github.com/rust-windowing/winit //! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.13/runtime +use crate::core::input_method; use crate::core::keyboard; use crate::core::mouse; use crate::core::touch; @@ -283,6 +284,16 @@ pub fn window_event( self::modifiers(new_modifiers.state()), ))) } + WindowEvent::Ime(ime) => { + use winit::event::Ime; + println!("ime event: {:?}", ime); + Some(Event::InputMethod(match ime { + Ime::Enabled => input_method::Event::Enabled, + Ime::Preedit(s, size) => input_method::Event::Preedit(s, size), + Ime::Commit(s) => input_method::Event::Commit(s), + Ime::Disabled => input_method::Event::Disabled, + })) + } WindowEvent::Focused(focused) => Some(Event::Window(if focused { window::Event::Focused } else { diff --git a/winit/src/program.rs b/winit/src/program.rs index d84362121b..688d6731b6 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -3,6 +3,8 @@ mod state; mod window_manager; pub use state::State; +use winit::dpi::LogicalPosition; +use winit::dpi::LogicalSize; use crate::conversion; use crate::core; @@ -579,6 +581,8 @@ async fn run_instance( let mut clipboard = Clipboard::unconnected(); let mut compositor_receiver: Option> = None; + let mut preedit = Preedit::

::new(); + debug.startup_finished(); loop { @@ -873,17 +877,27 @@ async fn run_instance( }); if let user_interface::State::Updated { - redraw_request: Some(redraw_request), + redraw_request, + caret_info, } = ui_state { match redraw_request { - window::RedrawRequest::NextFrame => { + Some(window::RedrawRequest::NextFrame) => { window.raw.request_redraw(); window.redraw_at = None; } - window::RedrawRequest::At(at) => { + Some(window::RedrawRequest::At(at)) => { window.redraw_at = Some(at); } + None => {} + } + + if let Some(caret_info) = caret_info { + update_input_method( + window, + &mut preedit, + &caret_info, + ); } } @@ -1032,24 +1046,37 @@ async fn run_instance( window.raw.request_redraw(); match ui_state { - #[cfg(not( - feature = "unconditional-rendering" - ))] user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - window.raw.request_redraw(); - window.redraw_at = None; + redraw_request: _redraw_request, + caret_info, + } => { + #[cfg(not( + feature = "unconditional-rendering" + ))] + match _redraw_request { + Some( + window::RedrawRequest::NextFrame, + ) => { + window.raw.request_redraw(); + window.redraw_at = None; + } + Some(window::RedrawRequest::At(at)) => { + window.redraw_at = Some(at); + } + None => {} } - window::RedrawRequest::At(at) => { - window.redraw_at = Some(at); + + if let Some(caret_info) = caret_info { + update_input_method( + window, + &mut preedit, + &caret_info, + ); } - }, + } user_interface::State::Outdated => { uis_stale = true; } - user_interface::State::Updated { .. } => {} } for (event, status) in window_events @@ -1138,6 +1165,111 @@ async fn run_instance( let _ = ManuallyDrop::into_inner(user_interfaces); } +fn update_input_method( + window: &mut crate::program::window_manager::Window, + preedit: &mut Preedit

, + caret_info: &crate::core::CaretInfo, +) where + P: Program, + C: Compositor + 'static, +{ + window.raw.set_ime_allowed(caret_info.input_method_allowed); + window.raw.set_ime_cursor_area( + LogicalPosition::new(caret_info.position.x, caret_info.position.y), + LogicalSize::new(10, 10), + ); + + let text = window.state.preedit(); + if !text.is_empty() { + preedit.update(text.as_str(), &window.renderer); + preedit.fill( + &mut window.renderer, + window.state.text_color(), + window.state.background_color(), + caret_info.position, + ); + } +} + +struct Preedit { + content: Option<::Paragraph>, +} + +impl Preedit

{ + fn new() -> Self { + Self { content: None } + } + + fn update(&mut self, text: &str, renderer: &P::Renderer) { + use core::text::Paragraph as _; + use core::text::Renderer as _; + + self.content = Some( + ::Paragraph::with_text( + core::Text::<&str, ::Font> { + content: text, + bounds: Size::INFINITY, + size: renderer.default_size(), + line_height: core::text::LineHeight::default(), + font: renderer.default_font(), + horizontal_alignment: core::alignment::Horizontal::Left, + vertical_alignment: core::alignment::Vertical::Top, //Bottom, + shaping: core::text::Shaping::Advanced, + wrapping: core::text::Wrapping::None, + }, + ), + ); + } + + fn fill( + &self, + renderer: &mut P::Renderer, + fore_color: core::Color, + bg_color: core::Color, + caret_position: Point, + ) { + use core::text::Paragraph as _; + use core::text::Renderer as _; + use core::Renderer as _; + + let Some(ref content) = self.content else { + return; + }; + if content.min_width() < 1.0 { + return; + } + + let top_left = Point::new( + caret_position.x, + caret_position.y - content.min_height(), + ); + let bounds = core::Rectangle::new(top_left, content.min_bounds()); + renderer.with_layer(bounds, |renderer| { + renderer.fill_quad( + core::renderer::Quad { + bounds, + ..Default::default() + }, + core::Background::Color(bg_color), + ); + + let underline = 2.; + renderer.fill_quad( + core::renderer::Quad { + bounds: bounds.shrink(core::Padding { + top: bounds.height - underline, + ..Default::default() + }), + ..Default::default() + }, + core::Background::Color(fore_color), + ); + + renderer.fill_paragraph(content, top_left, fore_color, bounds); + }); + } +} + /// Builds a window's [`UserInterface`] for the [`Program`]. fn build_user_interface<'a, P: Program>( program: &'a P, diff --git a/winit/src/program/state.rs b/winit/src/program/state.rs index e883d04acd..2b5710ac9d 100644 --- a/winit/src/program/state.rs +++ b/winit/src/program/state.rs @@ -4,7 +4,7 @@ use crate::core::{Color, Size}; use crate::graphics::Viewport; use crate::program::Program; -use winit::event::{Touch, WindowEvent}; +use winit::event::{Ime, Touch, WindowEvent}; use winit::window::Window; use std::fmt::{Debug, Formatter}; @@ -22,6 +22,7 @@ where modifiers: winit::keyboard::ModifiersState, theme: P::Theme, style: theme::Style, + preedit: String, } impl Debug for State

@@ -73,6 +74,7 @@ where modifiers: winit::keyboard::ModifiersState::default(), theme, style, + preedit: String::default(), } } @@ -136,6 +138,11 @@ where self.style.text_color } + /// TODO + pub fn preedit(&self) -> String { + self.preedit.clone() + } + /// Processes the provided window event and updates the [`State`] accordingly. pub fn update( &mut self, @@ -179,6 +186,11 @@ where WindowEvent::ModifiersChanged(new_modifiers) => { self.modifiers = new_modifiers.state(); } + WindowEvent::Ime(ime) => { + if let Ime::Preedit(text, _) = ime { + self.preedit = text.clone(); + } + } #[cfg(feature = "debug")] WindowEvent::KeyboardInput { event: From 0c6d4eb23f07e0ab424dc22dd198924b8540192a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 2 Feb 2025 17:50:12 +0100 Subject: [PATCH 02/20] Run `cargo fmt` and fix lints --- widget/src/text_editor.rs | 9 +++++---- widget/src/text_input.rs | 25 ++++++++++--------------- winit/src/program/state.rs | 6 ++---- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 2931e7f616..529c8b909a 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -338,7 +338,7 @@ where let text_bounds = bounds.shrink(self.padding); let translation = text_bounds.position() - Point::ORIGIN; - if let Some(_) = state.focus.as_ref() { + if state.focus.is_some() { let position = match internal.editor.cursor() { Cursor::Caret(position) => position, Cursor::Selection(ranges) => ranges @@ -872,10 +872,11 @@ where }; shell.update_caret_info(if state.is_focused() { - let rect = self - .caret_rect(tree, renderer, layout) - .unwrap_or(Rectangle::default()); + let rect = + self.caret_rect(tree, renderer, layout).unwrap_or_default(); + let bottom_left = Point::new(rect.x, rect.y + rect.height); + Some(CaretInfo { position: bottom_left, input_method_allowed: true, diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index f2756b5be1..ba5d184305 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -407,18 +407,15 @@ where let mut children_layout = layout.children(); let text_bounds = children_layout.next().unwrap().bounds(); - if let Some(_) = state + if state .is_focused - .as_ref() - .filter(|focus| focus.is_window_focused) + .is_some_and(|focus| focus.is_window_focused) { let caret_index = match state.cursor.state(value) { cursor::State::Index(position) => position, - cursor::State::Selection { start, end } => { - let left = start.min(end); - left - } + cursor::State::Selection { start, end } => start.min(end), }; + let text = state.value.raw(); let (caret_x, offset) = measure_cursor_and_scroll_offset( text, @@ -433,6 +430,7 @@ where ); let x = (text_bounds.x + caret_x).floor(); + Some(Rectangle { x: (alignment_offset - offset) + x, y: text_bounds.y, @@ -1250,7 +1248,7 @@ where state.keyboard_modifiers = *modifiers; } - Event::InputMethod(input_method::Event::Commit(string)) => { + Event::InputMethod(input_method::Event::Commit(text)) => { let state = state::(tree); if let Some(focus) = &mut state.is_focused { @@ -1258,21 +1256,18 @@ where return; }; - state.is_pasting = None; - let mut editor = Editor::new(&mut self.value, &mut state.cursor); + editor.paste(Value::new(text)); - editor.paste(Value::new(&string)); + focus.updated_at = Instant::now(); + state.is_pasting = None; let message = (on_input)(editor.contents()); shell.publish(message); - - focus.updated_at = Instant::now(); + shell.capture_event(); update_cache(state, &self.value); - - shell.capture_event(); } } Event::Window(window::Event::Unfocused) => { diff --git a/winit/src/program/state.rs b/winit/src/program/state.rs index 2b5710ac9d..6361d1bab6 100644 --- a/winit/src/program/state.rs +++ b/winit/src/program/state.rs @@ -186,10 +186,8 @@ where WindowEvent::ModifiersChanged(new_modifiers) => { self.modifiers = new_modifiers.state(); } - WindowEvent::Ime(ime) => { - if let Ime::Preedit(text, _) = ime { - self.preedit = text.clone(); - } + WindowEvent::Ime(Ime::Preedit(text, _)) => { + self.preedit = text.clone(); } #[cfg(feature = "debug")] WindowEvent::KeyboardInput { From d5ee9c27955e6dfeb645e2641f3d24b006685484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 2 Feb 2025 17:55:16 +0100 Subject: [PATCH 03/20] Copy `winit` docs for `input_method::Event` --- core/src/input_method.rs | 76 +++++++++++++++++++++++++++++++++------- winit/src/conversion.rs | 5 ++- 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/core/src/input_method.rs b/core/src/input_method.rs index ceda39c389..d282d4048c 100644 --- a/core/src/input_method.rs +++ b/core/src/input_method.rs @@ -1,24 +1,76 @@ //! Listen to input method events. +use std::ops::Range; -/// A input method event. +/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events. /// -/// _**Note:** This type is largely incomplete! If you need to track -/// additional events, feel free to [open an issue] and share your use case!_ +/// This is also called a "composition event". /// -/// [open an issue]: https://github.com/iced-rs/iced/issues -#[derive(Debug, Clone, PartialEq, Eq)] +/// Most keypresses using a latin-like keyboard layout simply generate a +/// [`WindowEvent::KeyboardInput`]. However, one couldn't possibly have a key for every single +/// unicode character that the user might want to type +/// - so the solution operating systems employ is to allow the user to type these using _a sequence +/// of keypresses_ instead. +/// +/// A prominent example of this is accents - many keyboard layouts allow you to first click the +/// "accent key", and then the character you want to apply the accent to. In this case, some +/// platforms will generate the following event sequence: +/// +/// ```ignore +/// // Press "`" key +/// Ime::Preedit("`", Some((0, 0))) +/// // Press "E" key +/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit. +/// Ime::Commit("é") +/// ``` +/// +/// Additionally, certain input devices are configured to display a candidate box that allow the +/// user to select the desired character interactively. (To properly position this box, you must use +/// [`Window::set_ime_cursor_area`].) +/// +/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the +/// following event sequence could be obtained: +/// +/// ```ignore +/// // Press "A" key +/// Ime::Preedit("a", Some((1, 1))) +/// // Press "B" key +/// Ime::Preedit("a b", Some((3, 3))) +/// // Press left arrow key +/// Ime::Preedit("a b", Some((1, 1))) +/// // Press space key +/// Ime::Preedit("啊b", Some((3, 3))) +/// // Press space key +/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit. +/// Ime::Commit("啊不") +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Event { - // These events correspond to underlying winit ime events. - // https://docs.rs/winit/latest/winit/event/enum.Ime.html - /// the IME was enabled. + /// Notifies when the IME was enabled. + /// + /// After getting this event you could receive [`Preedit`][Self::Preedit] and + /// [`Commit`][Self::Commit] events. You should also start performing IME related requests + /// like [`Window::set_ime_cursor_area`]. Enabled, - /// new composing text should be set at the cursor position. - Preedit(String, Option<(usize, usize)>), + /// Notifies when a new composing text should be set at the cursor position. + /// + /// The value represents a pair of the preedit string and the cursor begin position and end + /// position. When it's `None`, the cursor should be hidden. When `String` is an empty string + /// this indicates that preedit was cleared. + /// + /// The cursor range is byte-wise indexed. + Preedit(String, Option>), - /// text should be inserted into the editor widget. + /// Notifies when text should be inserted into the editor widget. + /// + /// Right before this event winit will send empty [`Self::Preedit`] event. Commit(String), - /// the IME was disabled. + /// Notifies when the IME was disabled. + /// + /// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or + /// [`Commit`][Self::Commit] events until the next [`Enabled`][Self::Enabled] event. You should + /// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear + /// pending preedit text. Disabled, } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index a289f0609f..c7f9aaaf0c 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -289,7 +289,10 @@ pub fn window_event( println!("ime event: {:?}", ime); Some(Event::InputMethod(match ime { Ime::Enabled => input_method::Event::Enabled, - Ime::Preedit(s, size) => input_method::Event::Preedit(s, size), + Ime::Preedit(s, size) => input_method::Event::Preedit( + s, + size.map(|(start, end)| (start..end)), + ), Ime::Commit(s) => input_method::Event::Commit(s), Ime::Disabled => input_method::Event::Disabled, })) From ae10adda74320e8098bfeb401f12a278e1e7b3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 2 Feb 2025 20:45:29 +0100 Subject: [PATCH 04/20] Refactor and simplify `input_method` API --- core/src/input_method.rs | 129 ++++++++++++++++++++--- core/src/lib.rs | 2 +- core/src/shell.rs | 78 ++++++-------- core/src/text/paragraph.rs | 6 ++ core/src/window/redraw_request.rs | 12 +++ runtime/src/user_interface.rs | 43 +++----- widget/src/action.rs | 14 +-- widget/src/canvas.rs | 15 +-- widget/src/combo_box.rs | 16 +-- widget/src/lazy/component.rs | 29 +----- widget/src/pane_grid.rs | 2 +- widget/src/scrollable.rs | 18 +--- widget/src/shader.rs | 14 +-- widget/src/text_editor.rs | 133 ++++++++++++++---------- widget/src/text_input.rs | 148 ++++++++++++++------------ winit/src/conversion.rs | 36 ++++--- winit/src/program.rs | 156 ++-------------------------- winit/src/program/state.rs | 12 +-- winit/src/program/window_manager.rs | 145 +++++++++++++++++++++++++- 19 files changed, 538 insertions(+), 470 deletions(-) diff --git a/core/src/input_method.rs b/core/src/input_method.rs index d282d4048c..c293582dac 100644 --- a/core/src/input_method.rs +++ b/core/src/input_method.rs @@ -1,17 +1,112 @@ //! Listen to input method events. +use crate::Point; + use std::ops::Range; +/// The input method strategy of a widget. +#[derive(Debug, Clone, PartialEq)] +pub enum InputMethod { + /// No input method is allowed. + Disabled, + /// Input methods are allowed, but not open yet. + Allowed, + /// Input method is open. + Open { + /// The position at which the input method dialog should be placed. + position: Point, + /// The [`Purpose`] of the input method. + purpose: Purpose, + /// The preedit to overlay on top of the input method dialog, if needed. + /// + /// Ideally, your widget will show pre-edits on-the-spot; but, since that can + /// be tricky, you can instead provide the current pre-edit here and the + /// runtime will display it as an overlay (i.e. "Over-the-spot IME"). + preedit: Option, + }, +} + +/// The purpose of an [`InputMethod`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum Purpose { + /// No special hints for the IME (default). + #[default] + Normal, + /// The IME is used for secure input (e.g. passwords). + Secure, + /// The IME is used to input into a terminal. + /// + /// For example, that could alter OSK on Wayland to show extra buttons. + Terminal, +} + +impl InputMethod { + /// Merges two [`InputMethod`] strategies, prioritizing the second one when both ready: + /// ``` + /// # use iced_core::input_method::{InputMethod, Purpose}; + /// # use iced_core::Point; + /// + /// let open = InputMethod::Open { + /// position: Point::ORIGIN, + /// purpose: Purpose::Normal, + /// preedit: None, + /// }; + /// + /// let open_2 = InputMethod::Open { + /// position: Point::ORIGIN, + /// purpose: Purpose::Secure, + /// preedit: None, + /// }; + /// + /// let mut ime = InputMethod::Disabled; + /// + /// ime.merge(&InputMethod::::Allowed); + /// assert_eq!(ime, InputMethod::Allowed); + /// + /// ime.merge(&InputMethod::::Disabled); + /// assert_eq!(ime, InputMethod::Allowed); + /// + /// ime.merge(&open); + /// assert_eq!(ime, open); + /// + /// ime.merge(&open_2); + /// assert_eq!(ime, open_2); + /// ``` + pub fn merge>(&mut self, other: &InputMethod) { + match other { + InputMethod::Disabled => {} + InputMethod::Open { + position, + purpose, + preedit, + } => { + *self = Self::Open { + position: *position, + purpose: *purpose, + preedit: preedit + .as_ref() + .map(AsRef::as_ref) + .map(str::to_owned), + }; + } + InputMethod::Allowed if matches!(self, Self::Disabled) => { + *self = Self::Allowed; + } + InputMethod::Allowed => {} + } + } +} + /// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events. /// /// This is also called a "composition event". /// /// Most keypresses using a latin-like keyboard layout simply generate a -/// [`WindowEvent::KeyboardInput`]. However, one couldn't possibly have a key for every single -/// unicode character that the user might want to type -/// - so the solution operating systems employ is to allow the user to type these using _a sequence -/// of keypresses_ instead. +/// [`keyboard::Event::KeyPressed`](crate::keyboard::Event::KeyPressed). +/// However, one couldn't possibly have a key for every single +/// unicode character that the user might want to type. The solution operating systems employ is +/// to allow the user to type these using _a sequence of keypresses_ instead. /// -/// A prominent example of this is accents - many keyboard layouts allow you to first click the +/// A prominent example of this is accents—many keyboard layouts allow you to first click the /// "accent key", and then the character you want to apply the accent to. In this case, some /// platforms will generate the following event sequence: /// @@ -19,13 +114,13 @@ use std::ops::Range; /// // Press "`" key /// Ime::Preedit("`", Some((0, 0))) /// // Press "E" key -/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit. +/// Ime::Preedit("", None) // Synthetic event generated to clear preedit. /// Ime::Commit("é") /// ``` /// /// Additionally, certain input devices are configured to display a candidate box that allow the /// user to select the desired character interactively. (To properly position this box, you must use -/// [`Window::set_ime_cursor_area`].) +/// [`Shell::request_input_method`](crate::Shell::request_input_method).) /// /// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the /// following event sequence could be obtained: @@ -40,17 +135,19 @@ use std::ops::Range; /// // Press space key /// Ime::Preedit("啊b", Some((3, 3))) /// // Press space key -/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit. +/// Ime::Preedit("", None) // Synthetic event generated to clear preedit. /// Ime::Commit("啊不") /// ``` #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Event { - /// Notifies when the IME was enabled. + /// Notifies when the IME was opened. /// /// After getting this event you could receive [`Preedit`][Self::Preedit] and /// [`Commit`][Self::Commit] events. You should also start performing IME related requests - /// like [`Window::set_ime_cursor_area`]. - Enabled, + /// like [`Shell::request_input_method`]. + /// + /// [`Shell::request_input_method`]: crate::Shell::request_input_method + Opened, /// Notifies when a new composing text should be set at the cursor position. /// @@ -63,14 +160,16 @@ pub enum Event { /// Notifies when text should be inserted into the editor widget. /// - /// Right before this event winit will send empty [`Self::Preedit`] event. + /// Right before this event, an empty [`Self::Preedit`] event will be issued. Commit(String), /// Notifies when the IME was disabled. /// /// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or - /// [`Commit`][Self::Commit] events until the next [`Enabled`][Self::Enabled] event. You should - /// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear + /// [`Commit`][Self::Commit] events until the next [`Opened`][Self::Opened] event. You should + /// also stop issuing IME related requests like [`Shell::request_input_method`] and clear /// pending preedit text. - Disabled, + /// + /// [`Shell::request_input_method`]: crate::Shell::request_input_method + Closed, } diff --git a/core/src/lib.rs b/core/src/lib.rs index c7c3804455..c31a8da749 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -62,6 +62,7 @@ pub use event::Event; pub use font::Font; pub use gradient::Gradient; pub use image::Image; +pub use input_method::InputMethod; pub use layout::Layout; pub use length::Length; pub use overlay::Overlay; @@ -73,7 +74,6 @@ pub use renderer::Renderer; pub use rotation::Rotation; pub use settings::Settings; pub use shadow::Shadow; -pub use shell::CaretInfo; pub use shell::Shell; pub use size::Size; pub use svg::Svg; diff --git a/core/src/shell.rs b/core/src/shell.rs index d2c1b9ecf7..e87d169621 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -1,15 +1,6 @@ -use crate::time::Instant; +use crate::event; use crate::window; -use crate::{event, Point}; - -/// TODO -#[derive(Clone, Copy, Debug)] -pub struct CaretInfo { - /// TODO - pub position: Point, - /// TODO - pub input_method_allowed: bool, -} +use crate::InputMethod; /// A connection to the state of a shell. /// @@ -21,10 +12,10 @@ pub struct CaretInfo { pub struct Shell<'a, Message> { messages: &'a mut Vec, event_status: event::Status, - redraw_request: Option, + redraw_request: window::RedrawRequest, + input_method: InputMethod, is_layout_invalid: bool, are_widgets_invalid: bool, - caret_info: Option, } impl<'a, Message> Shell<'a, Message> { @@ -33,10 +24,10 @@ impl<'a, Message> Shell<'a, Message> { Self { messages, event_status: event::Status::Ignored, - redraw_request: None, + redraw_request: window::RedrawRequest::Wait, is_layout_invalid: false, are_widgets_invalid: false, - caret_info: None, + input_method: InputMethod::Disabled, } } @@ -70,35 +61,38 @@ impl<'a, Message> Shell<'a, Message> { /// Requests a new frame to be drawn as soon as possible. pub fn request_redraw(&mut self) { - self.redraw_request = Some(window::RedrawRequest::NextFrame); - } - - /// Requests a new frame to be drawn at the given [`Instant`]. - pub fn request_redraw_at(&mut self, at: Instant) { - match self.redraw_request { - None => { - self.redraw_request = Some(window::RedrawRequest::At(at)); - } - Some(window::RedrawRequest::At(current)) if at < current => { - self.redraw_request = Some(window::RedrawRequest::At(at)); - } - _ => {} - } + self.redraw_request = window::RedrawRequest::NextFrame; + } + + /// Requests a new frame to be drawn at the given [`window::RedrawRequest`]. + pub fn request_redraw_at( + &mut self, + redraw_request: impl Into, + ) { + self.redraw_request = self.redraw_request.min(redraw_request.into()); } /// Returns the request a redraw should happen, if any. - pub fn redraw_request(&self) -> Option { + pub fn redraw_request(&self) -> window::RedrawRequest { self.redraw_request } - /// TODO - pub fn update_caret_info(&mut self, caret_info: Option) { - self.caret_info = caret_info.or(self.caret_info); + /// Requests the current [`InputMethod`] strategy. + pub fn request_input_method>( + &mut self, + ime: &InputMethod, + ) { + self.input_method.merge(ime); } - /// TODO - pub fn caret_info(&self) -> Option { - self.caret_info + /// Returns the current [`InputMethod`] strategy. + pub fn input_method(&self) -> &InputMethod { + &self.input_method + } + + /// Returns the current [`InputMethod`] strategy. + pub fn input_method_mut(&mut self) -> &mut InputMethod { + &mut self.input_method } /// Returns whether the current layout is invalid or not. @@ -143,22 +137,14 @@ impl<'a, Message> Shell<'a, Message> { pub fn merge(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) { self.messages.extend(other.messages.drain(..).map(f)); - if let Some(new) = other.redraw_request { - self.redraw_request = Some( - self.redraw_request - .map(|current| if current < new { current } else { new }) - .unwrap_or(new), - ); - } - - self.update_caret_info(other.caret_info()); - self.is_layout_invalid = self.is_layout_invalid || other.is_layout_invalid; self.are_widgets_invalid = self.are_widgets_invalid || other.are_widgets_invalid; + self.redraw_request = self.redraw_request.min(other.redraw_request); self.event_status = self.event_status.merge(other.event_status); + self.input_method.merge(&other.input_method); } } diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs index 924276c35c..700c2c7595 100644 --- a/core/src/text/paragraph.rs +++ b/core/src/text/paragraph.rs @@ -129,6 +129,12 @@ impl Plain

{ self.raw.min_width() } + /// Returns the minimum height that can fit the contents of the + /// [`Paragraph`]. + pub fn min_height(&self) -> f32 { + self.raw.min_height() + } + /// Returns the cached [`Paragraph`]. pub fn raw(&self) -> &P { &self.raw diff --git a/core/src/window/redraw_request.rs b/core/src/window/redraw_request.rs index b0c000d6e9..0ae4facec6 100644 --- a/core/src/window/redraw_request.rs +++ b/core/src/window/redraw_request.rs @@ -8,6 +8,15 @@ pub enum RedrawRequest { /// Redraw at the given time. At(Instant), + + /// No redraw is needed. + Wait, +} + +impl From for RedrawRequest { + fn from(time: Instant) -> Self { + Self::At(time) + } } #[cfg(test)] @@ -34,5 +43,8 @@ mod tests { assert!(RedrawRequest::At(now) <= RedrawRequest::At(now)); assert!(RedrawRequest::At(now) <= RedrawRequest::At(later)); assert!(RedrawRequest::At(later) >= RedrawRequest::At(now)); + + assert!(RedrawRequest::Wait > RedrawRequest::NextFrame); + assert!(RedrawRequest::Wait > RedrawRequest::At(later)); } } diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 6d3360d0c1..59e497c547 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -6,7 +6,7 @@ use crate::core::renderer; use crate::core::widget; use crate::core::window; use crate::core::{ - CaretInfo, Clipboard, Element, Layout, Rectangle, Shell, Size, Vector, + Clipboard, Element, InputMethod, Layout, Rectangle, Shell, Size, Vector, }; use crate::overlay; @@ -188,8 +188,8 @@ where use std::mem::ManuallyDrop; let mut outdated = false; - let mut redraw_request = None; - let mut caret_info = None; + let mut redraw_request = window::RedrawRequest::Wait; + let mut input_method = InputMethod::Disabled; let mut manual_overlay = ManuallyDrop::new( self.root @@ -223,17 +223,8 @@ where ); event_statuses.push(shell.event_status()); - - match (redraw_request, shell.redraw_request()) { - (None, Some(at)) => { - redraw_request = Some(at); - } - (Some(current), Some(new)) if new < current => { - redraw_request = Some(new); - } - _ => {} - } - caret_info = caret_info.or(shell.caret_info()); + redraw_request = redraw_request.min(shell.redraw_request()); + input_method.merge(shell.input_method()); if shell.is_layout_invalid() { let _ = ManuallyDrop::into_inner(manual_overlay); @@ -327,16 +318,8 @@ where self.overlay = None; } - match (redraw_request, shell.redraw_request()) { - (None, Some(at)) => { - redraw_request = Some(at); - } - (Some(current), Some(new)) if new < current => { - redraw_request = Some(new); - } - _ => {} - } - caret_info = caret_info.or(shell.caret_info()); + redraw_request = redraw_request.min(shell.redraw_request()); + input_method.merge(shell.input_method()); shell.revalidate_layout(|| { self.base = self.root.as_widget().layout( @@ -362,7 +345,7 @@ where } else { State::Updated { redraw_request, - caret_info, + input_method, } }, event_statuses, @@ -644,7 +627,7 @@ impl Default for Cache { } /// The resulting state after updating a [`UserInterface`]. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub enum State { /// The [`UserInterface`] is outdated and needs to be rebuilt. Outdated, @@ -652,9 +635,9 @@ pub enum State { /// The [`UserInterface`] is up-to-date and can be reused without /// rebuilding. Updated { - /// The [`window::RedrawRequest`] when a redraw should be performed. - redraw_request: Option, - /// TODO - caret_info: Option, + /// The [`window::RedrawRequest`] describing when a redraw should be performed. + redraw_request: window::RedrawRequest, + /// The current [`InputMethod`] strategy of the user interface. + input_method: InputMethod, }, } diff --git a/widget/src/action.rs b/widget/src/action.rs index 1dd3a787a5..cc31e76a6e 100644 --- a/widget/src/action.rs +++ b/widget/src/action.rs @@ -6,7 +6,7 @@ use crate::core::window; #[derive(Debug, Clone)] pub struct Action { message_to_publish: Option, - redraw_request: Option, + redraw_request: window::RedrawRequest, event_status: event::Status, } @@ -14,7 +14,7 @@ impl Action { fn new() -> Self { Self { message_to_publish: None, - redraw_request: None, + redraw_request: window::RedrawRequest::Wait, event_status: event::Status::Ignored, } } @@ -46,7 +46,7 @@ impl Action { /// soon as possible; without publishing any `Message`. pub fn request_redraw() -> Self { Self { - redraw_request: Some(window::RedrawRequest::NextFrame), + redraw_request: window::RedrawRequest::NextFrame, ..Self::new() } } @@ -58,7 +58,7 @@ impl Action { /// blinking caret on a text input. pub fn request_redraw_at(at: Instant) -> Self { Self { - redraw_request: Some(window::RedrawRequest::At(at)), + redraw_request: window::RedrawRequest::At(at), ..Self::new() } } @@ -75,11 +75,7 @@ impl Action { /// widget implementations. pub fn into_inner( self, - ) -> ( - Option, - Option, - event::Status, - ) { + ) -> (Option, window::RedrawRequest, event::Status) { ( self.message_to_publish, self.redraw_request, diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 23cc3f2b36..d10771f005 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -238,27 +238,18 @@ where { let (message, redraw_request, event_status) = action.into_inner(); + shell.request_redraw_at(redraw_request); + if let Some(message) = message { shell.publish(message); } - if let Some(redraw_request) = redraw_request { - match redraw_request { - window::RedrawRequest::NextFrame => { - shell.request_redraw(); - } - window::RedrawRequest::At(at) => { - shell.request_redraw_at(at); - } - } - } - if event_status == event::Status::Captured { shell.capture_event(); } } - if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { + if shell.redraw_request() != window::RedrawRequest::NextFrame { let mouse_interaction = self .mouse_interaction(tree, layout, cursor, viewport, renderer); diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index d7c7c92244..057931551b 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -63,7 +63,6 @@ use crate::core::renderer; use crate::core::text; use crate::core::time::Instant; use crate::core::widget::{self, Widget}; -use crate::core::window; use crate::core::{ Clipboard, Element, Event, Length, Padding, Rectangle, Shell, Size, Theme, Vector, @@ -554,17 +553,8 @@ where shell.capture_event(); } - if let Some(redraw_request) = local_shell.redraw_request() { - match redraw_request { - window::RedrawRequest::NextFrame => { - shell.request_redraw(); - } - window::RedrawRequest::At(at) => { - shell.request_redraw_at(at); - } - } - } - shell.update_caret_info(local_shell.caret_info()); + shell.request_redraw_at(local_shell.redraw_request()); + shell.request_input_method(local_shell.input_method()); // Then finally react to them here for message in local_messages { @@ -757,7 +747,7 @@ where &mut local_shell, viewport, ); - shell.update_caret_info(local_shell.caret_info()); + shell.request_input_method(local_shell.input_method()); } }); diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index b9fbde58d3..c93b7c4294 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -6,7 +6,6 @@ use crate::core::overlay; use crate::core::renderer; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; -use crate::core::window; use crate::core::{ self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, Widget, @@ -344,18 +343,8 @@ where } local_shell.revalidate_layout(|| shell.invalidate_layout()); - - if let Some(redraw_request) = local_shell.redraw_request() { - match redraw_request { - window::RedrawRequest::NextFrame => { - shell.request_redraw(); - } - window::RedrawRequest::At(at) => { - shell.request_redraw_at(at); - } - } - } - shell.update_caret_info(local_shell.caret_info()); + shell.request_redraw_at(local_shell.redraw_request()); + shell.request_input_method(local_shell.input_method()); if !local_messages.is_empty() { let mut heads = self.state.take().unwrap().into_heads(); @@ -630,18 +619,8 @@ where } local_shell.revalidate_layout(|| shell.invalidate_layout()); - - if let Some(redraw_request) = local_shell.redraw_request() { - match redraw_request { - window::RedrawRequest::NextFrame => { - shell.request_redraw(); - } - window::RedrawRequest::At(at) => { - shell.request_redraw_at(at); - } - } - } - shell.update_caret_info(local_shell.caret_info()); + shell.request_redraw_at(local_shell.redraw_request()); + shell.request_input_method(local_shell.input_method()); if !local_messages.is_empty() { let mut inner = diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 5c3b343c80..e972b983ad 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -687,7 +687,7 @@ where _ => {} } - if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { + if shell.redraw_request() != window::RedrawRequest::NextFrame { let interaction = self .grid_interaction(action, layout, cursor) .or_else(|| { diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 7df7a0e5c0..0a93584e8d 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -33,7 +33,7 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - self, Background, CaretInfo, Clipboard, Color, Element, Event, Layout, + self, Background, Clipboard, Color, Element, Event, InputMethod, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; @@ -730,7 +730,6 @@ where let translation = state.translation(self.direction, bounds, content_bounds); - let children_may_have_caret = shell.caret_info().is_none(); self.content.as_widget_mut().update( &mut tree.children[0], event.clone(), @@ -746,17 +745,10 @@ where }, ); - if children_may_have_caret { - if let Some(caret_info) = shell.caret_info() { - shell.update_caret_info(Some(CaretInfo { - position: Point::new( - caret_info.position.x - translation.x, - caret_info.position.y - translation.y, - ), - input_method_allowed: caret_info - .input_method_allowed, - })); - } + if let InputMethod::Open { position, .. } = + shell.input_method_mut() + { + *position = *position + translation; } }; diff --git a/widget/src/shader.rs b/widget/src/shader.rs index 8ec57482c7..48c9632198 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -9,7 +9,6 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Widget}; -use crate::core::window; use crate::core::{Clipboard, Element, Event, Length, Rectangle, Shell, Size}; use crate::renderer::wgpu::primitive; @@ -105,21 +104,12 @@ where { let (message, redraw_request, event_status) = action.into_inner(); + shell.request_redraw_at(redraw_request); + if let Some(message) = message { shell.publish(message); } - if let Some(redraw_request) = redraw_request { - match redraw_request { - window::RedrawRequest::NextFrame => { - shell.request_redraw(); - } - window::RedrawRequest::At(at) => { - shell.request_redraw_at(at); - } - } - } - if event_status == event::Status::Captured { shell.capture_event(); } diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 529c8b909a..4f985f28b1 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -47,7 +47,7 @@ use crate::core::widget::operation; use crate::core::widget::{self, Widget}; use crate::core::window; use crate::core::{ - Background, Border, CaretInfo, Color, Element, Event, Length, Padding, + Background, Border, Color, Element, Event, InputMethod, Length, Padding, Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector, }; @@ -324,43 +324,49 @@ where self } - fn caret_rect( + fn input_method<'b>( &self, - tree: &widget::Tree, + state: &'b State, renderer: &Renderer, layout: Layout<'_>, - ) -> Option { - let bounds = layout.bounds(); + ) -> InputMethod<&'b str> { + let Some(Focus { + is_window_focused: true, + is_ime_open, + .. + }) = &state.focus + else { + return InputMethod::Disabled; + }; + + let Some(preedit) = &is_ime_open else { + return InputMethod::Allowed; + }; + let bounds = layout.bounds(); let internal = self.content.0.borrow_mut(); - let state = tree.state.downcast_ref::>(); let text_bounds = bounds.shrink(self.padding); let translation = text_bounds.position() - Point::ORIGIN; - if state.focus.is_some() { - let position = match internal.editor.cursor() { - Cursor::Caret(position) => position, - Cursor::Selection(ranges) => ranges - .first() - .cloned() - .unwrap_or(Rectangle::default()) - .position(), - }; - Some(Rectangle::new( - position + translation, - Size::new( - 1.0, - self.line_height - .to_absolute( - self.text_size - .unwrap_or_else(|| renderer.default_size()), - ) - .into(), - ), - )) - } else { - None + let cursor = match internal.editor.cursor() { + Cursor::Caret(position) => position, + Cursor::Selection(ranges) => { + ranges.first().cloned().unwrap_or_default().position() + } + }; + + let line_height = self.line_height.to_absolute( + self.text_size.unwrap_or_else(|| renderer.default_size()), + ); + + let position = + cursor + translation + Vector::new(0.0, f32::from(line_height)); + + InputMethod::Open { + position, + purpose: input_method::Purpose::Normal, + preedit: Some(preedit), } } } @@ -499,11 +505,12 @@ pub struct State { highlighter_format_address: usize, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] struct Focus { updated_at: Instant, now: Instant, is_window_focused: bool, + is_ime_open: Option, } impl Focus { @@ -516,6 +523,7 @@ impl Focus { updated_at: now, now, is_window_focused: true, + is_ime_open: None, } } @@ -742,11 +750,23 @@ where })); shell.capture_event(); } - Update::Commit(text) => { - shell.publish(on_edit(Action::Edit(Edit::Paste( - Arc::new(text), - )))); - } + Update::InputMethod(update) => match update { + Ime::Toggle(is_open) => { + if let Some(focus) = &mut state.focus { + focus.is_ime_open = is_open.then(String::new); + } + } + Ime::Preedit(text) => { + if let Some(focus) = &mut state.focus { + focus.is_ime_open = Some(text); + } + } + Ime::Commit(text) => { + shell.publish(on_edit(Action::Edit(Edit::Paste( + Arc::new(text), + )))); + } + }, Update::Binding(binding) => { fn apply_binding< H: text::Highlighter, @@ -871,22 +891,12 @@ where } }; - shell.update_caret_info(if state.is_focused() { - let rect = - self.caret_rect(tree, renderer, layout).unwrap_or_default(); - - let bottom_left = Point::new(rect.x, rect.y + rect.height); - - Some(CaretInfo { - position: bottom_left, - input_method_allowed: true, - }) - } else { - None - }); - if is_redraw { self.last_status = Some(status); + + shell.request_input_method( + &self.input_method(state, renderer, layout), + ); } else if self .last_status .is_some_and(|last_status| status != last_status) @@ -1189,10 +1199,16 @@ enum Update { Drag(Point), Release, Scroll(f32), - Commit(String), + InputMethod(Ime), Binding(Binding), } +enum Ime { + Toggle(bool), + Preedit(String), + Commit(String), +} + impl Update { fn from_event( event: Event, @@ -1252,9 +1268,20 @@ impl Update { } _ => None, }, - Event::InputMethod(input_method::Event::Commit(text)) => { - Some(Update::Commit(text)) - } + Event::InputMethod(event) => match event { + input_method::Event::Opened | input_method::Event::Closed => { + Some(Update::InputMethod(Ime::Toggle(matches!( + event, + input_method::Event::Opened + )))) + } + input_method::Event::Preedit(content, _range) => { + Some(Update::InputMethod(Ime::Preedit(content))) + } + input_method::Event::Commit(content) => { + Some(Update::InputMethod(Ime::Commit(content))) + } + }, Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ba5d184305..d0e93927a4 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -57,7 +57,7 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - Background, Border, CaretInfo, Color, Element, Event, Layout, Length, + Background, Border, Color, Element, Event, InputMethod, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::runtime::task::{self, Task}; @@ -392,14 +392,24 @@ where } } - fn caret_rect( + fn input_method<'b>( &self, - tree: &Tree, + state: &'b State, layout: Layout<'_>, - value: Option<&Value>, - ) -> Option { - let state = tree.state.downcast_ref::>(); - let value = value.unwrap_or(&self.value); + value: &Value, + ) -> InputMethod<&'b str> { + let Some(Focus { + is_window_focused: true, + is_ime_open, + .. + }) = &state.is_focused + else { + return InputMethod::Disabled; + }; + + let Some(preedit) = is_ime_open else { + return InputMethod::Allowed; + }; let secure_value = self.is_secure.then(|| value.secure()); let value = secure_value.as_ref().unwrap_or(value); @@ -407,38 +417,32 @@ where let mut children_layout = layout.children(); let text_bounds = children_layout.next().unwrap().bounds(); - if state - .is_focused - .is_some_and(|focus| focus.is_window_focused) - { - let caret_index = match state.cursor.state(value) { - cursor::State::Index(position) => position, - cursor::State::Selection { start, end } => start.min(end), - }; + let caret_index = match state.cursor.state(value) { + cursor::State::Index(position) => position, + cursor::State::Selection { start, end } => start.min(end), + }; - let text = state.value.raw(); - let (caret_x, offset) = measure_cursor_and_scroll_offset( - text, - text_bounds, - caret_index, - ); + let text = state.value.raw(); + let (cursor_x, scroll_offset) = + measure_cursor_and_scroll_offset(text, text_bounds, caret_index); - let alignment_offset = alignment_offset( - text_bounds.width, - text.min_width(), - self.alignment, - ); + let alignment_offset = alignment_offset( + text_bounds.width, + text.min_width(), + self.alignment, + ); - let x = (text_bounds.x + caret_x).floor(); + let x = (text_bounds.x + cursor_x).floor() - scroll_offset + + alignment_offset; - Some(Rectangle { - x: (alignment_offset - offset) + x, - y: text_bounds.y, - width: 1.0, - height: text_bounds.height, - }) - } else { - None + InputMethod::Open { + position: Point::new(x, text_bounds.y), + purpose: if self.is_secure { + input_method::Purpose::Secure + } else { + input_method::Purpose::Normal + }, + preedit: Some(preedit), } } @@ -725,6 +729,7 @@ where updated_at: now, now, is_window_focused: true, + is_ime_open: None, }) } else { None @@ -1248,28 +1253,46 @@ where state.keyboard_modifiers = *modifiers; } - Event::InputMethod(input_method::Event::Commit(text)) => { - let state = state::(tree); + Event::InputMethod(event) => match event { + input_method::Event::Opened | input_method::Event::Closed => { + let state = state::(tree); - if let Some(focus) = &mut state.is_focused { - let Some(on_input) = &self.on_input else { - return; - }; + if let Some(focus) = &mut state.is_focused { + focus.is_ime_open = + matches!(event, input_method::Event::Opened) + .then(String::new); + } + } + input_method::Event::Preedit(content, _range) => { + let state = state::(tree); - let mut editor = - Editor::new(&mut self.value, &mut state.cursor); - editor.paste(Value::new(text)); + if let Some(focus) = &mut state.is_focused { + focus.is_ime_open = Some(content.to_owned()); + } + } + input_method::Event::Commit(text) => { + let state = state::(tree); - focus.updated_at = Instant::now(); - state.is_pasting = None; + if let Some(focus) = &mut state.is_focused { + let Some(on_input) = &self.on_input else { + return; + }; - let message = (on_input)(editor.contents()); - shell.publish(message); - shell.capture_event(); + let mut editor = + Editor::new(&mut self.value, &mut state.cursor); + editor.paste(Value::new(text)); + + focus.updated_at = Instant::now(); + state.is_pasting = None; - update_cache(state, &self.value); + let message = (on_input)(editor.contents()); + shell.publish(message); + shell.capture_event(); + + update_cache(state, &self.value); + } } - } + }, Event::Window(window::Event::Unfocused) => { let state = state::(tree); @@ -1329,21 +1352,14 @@ where Status::Active }; - shell.update_caret_info(if state.is_focused() { - let rect = self - .caret_rect(tree, layout, Some(&self.value)) - .unwrap_or(Rectangle::with_size(Size::::default())); - let bottom_left = Point::new(rect.x, rect.y + rect.height); - Some(CaretInfo { - position: bottom_left, - input_method_allowed: true, - }) - } else { - None - }); - if let Event::Window(window::Event::RedrawRequested(_now)) = event { self.last_status = Some(status); + + shell.request_input_method(&self.input_method( + state, + layout, + &self.value, + )); } else if self .last_status .is_some_and(|last_status| status != last_status) @@ -1517,11 +1533,12 @@ fn state( tree.state.downcast_mut::>() } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] struct Focus { updated_at: Instant, now: Instant, is_window_focused: bool, + is_ime_open: Option, } impl State

{ @@ -1548,6 +1565,7 @@ impl State

{ updated_at: now, now, is_window_focused: true, + is_ime_open: None, }); self.move_cursor_to_end(); diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index c7f9aaaf0c..ab84afff27 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -141,6 +141,7 @@ pub fn window_event( scale_factor: f64, modifiers: winit::keyboard::ModifiersState, ) -> Option { + use winit::event::Ime; use winit::event::WindowEvent; match event { @@ -284,19 +285,15 @@ pub fn window_event( self::modifiers(new_modifiers.state()), ))) } - WindowEvent::Ime(ime) => { - use winit::event::Ime; - println!("ime event: {:?}", ime); - Some(Event::InputMethod(match ime { - Ime::Enabled => input_method::Event::Enabled, - Ime::Preedit(s, size) => input_method::Event::Preedit( - s, - size.map(|(start, end)| (start..end)), - ), - Ime::Commit(s) => input_method::Event::Commit(s), - Ime::Disabled => input_method::Event::Disabled, - })) - } + WindowEvent::Ime(event) => Some(Event::InputMethod(match event { + Ime::Enabled => input_method::Event::Opened, + Ime::Preedit(content, size) => input_method::Event::Preedit( + content, + size.map(|(start, end)| (start..end)), + ), + Ime::Commit(content) => input_method::Event::Commit(content), + Ime::Disabled => input_method::Event::Closed, + })), WindowEvent::Focused(focused) => Some(Event::Window(if focused { window::Event::Focused } else { @@ -1174,7 +1171,7 @@ pub fn resize_direction( } } -/// Converts some [`window::Icon`] into it's `winit` counterpart. +/// Converts some [`window::Icon`] into its `winit` counterpart. /// /// Returns `None` if there is an error during the conversion. pub fn icon(icon: window::Icon) -> Option { @@ -1183,6 +1180,17 @@ pub fn icon(icon: window::Icon) -> Option { winit::window::Icon::from_rgba(pixels, size.width, size.height).ok() } +/// Convertions some [`input_method::Purpose`] to its `winit` counterpart. +pub fn ime_purpose( + purpose: input_method::Purpose, +) -> winit::window::ImePurpose { + match purpose { + input_method::Purpose::Normal => winit::window::ImePurpose::Normal, + input_method::Purpose::Secure => winit::window::ImePurpose::Password, + input_method::Purpose::Terminal => winit::window::ImePurpose::Terminal, + } +} + // See: https://en.wikipedia.org/wiki/Private_Use_Areas fn is_private_use(c: char) -> bool { ('\u{E000}'..='\u{F8FF}').contains(&c) diff --git a/winit/src/program.rs b/winit/src/program.rs index 688d6731b6..302bc6c333 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -3,8 +3,6 @@ mod state; mod window_manager; pub use state::State; -use winit::dpi::LogicalPosition; -use winit::dpi::LogicalSize; use crate::conversion; use crate::core; @@ -581,8 +579,6 @@ async fn run_instance( let mut clipboard = Clipboard::unconnected(); let mut compositor_receiver: Option> = None; - let mut preedit = Preedit::

::new(); - debug.startup_finished(); loop { @@ -878,29 +874,15 @@ async fn run_instance( if let user_interface::State::Updated { redraw_request, - caret_info, + input_method, } = ui_state { - match redraw_request { - Some(window::RedrawRequest::NextFrame) => { - window.raw.request_redraw(); - window.redraw_at = None; - } - Some(window::RedrawRequest::At(at)) => { - window.redraw_at = Some(at); - } - None => {} - } - - if let Some(caret_info) = caret_info { - update_input_method( - window, - &mut preedit, - &caret_info, - ); - } + window.request_redraw(redraw_request); + window.request_input_method(input_method); } + window.draw_preedit(); + debug.render_started(); match compositor.present( &mut window.renderer, @@ -1048,31 +1030,14 @@ async fn run_instance( match ui_state { user_interface::State::Updated { redraw_request: _redraw_request, - caret_info, + input_method, } => { #[cfg(not( feature = "unconditional-rendering" ))] - match _redraw_request { - Some( - window::RedrawRequest::NextFrame, - ) => { - window.raw.request_redraw(); - window.redraw_at = None; - } - Some(window::RedrawRequest::At(at)) => { - window.redraw_at = Some(at); - } - None => {} - } + window.request_redraw(_redraw_request); - if let Some(caret_info) = caret_info { - update_input_method( - window, - &mut preedit, - &caret_info, - ); - } + window.request_input_method(input_method); } user_interface::State::Outdated => { uis_stale = true; @@ -1165,111 +1130,6 @@ async fn run_instance( let _ = ManuallyDrop::into_inner(user_interfaces); } -fn update_input_method( - window: &mut crate::program::window_manager::Window, - preedit: &mut Preedit

, - caret_info: &crate::core::CaretInfo, -) where - P: Program, - C: Compositor + 'static, -{ - window.raw.set_ime_allowed(caret_info.input_method_allowed); - window.raw.set_ime_cursor_area( - LogicalPosition::new(caret_info.position.x, caret_info.position.y), - LogicalSize::new(10, 10), - ); - - let text = window.state.preedit(); - if !text.is_empty() { - preedit.update(text.as_str(), &window.renderer); - preedit.fill( - &mut window.renderer, - window.state.text_color(), - window.state.background_color(), - caret_info.position, - ); - } -} - -struct Preedit { - content: Option<::Paragraph>, -} - -impl Preedit

{ - fn new() -> Self { - Self { content: None } - } - - fn update(&mut self, text: &str, renderer: &P::Renderer) { - use core::text::Paragraph as _; - use core::text::Renderer as _; - - self.content = Some( - ::Paragraph::with_text( - core::Text::<&str, ::Font> { - content: text, - bounds: Size::INFINITY, - size: renderer.default_size(), - line_height: core::text::LineHeight::default(), - font: renderer.default_font(), - horizontal_alignment: core::alignment::Horizontal::Left, - vertical_alignment: core::alignment::Vertical::Top, //Bottom, - shaping: core::text::Shaping::Advanced, - wrapping: core::text::Wrapping::None, - }, - ), - ); - } - - fn fill( - &self, - renderer: &mut P::Renderer, - fore_color: core::Color, - bg_color: core::Color, - caret_position: Point, - ) { - use core::text::Paragraph as _; - use core::text::Renderer as _; - use core::Renderer as _; - - let Some(ref content) = self.content else { - return; - }; - if content.min_width() < 1.0 { - return; - } - - let top_left = Point::new( - caret_position.x, - caret_position.y - content.min_height(), - ); - let bounds = core::Rectangle::new(top_left, content.min_bounds()); - renderer.with_layer(bounds, |renderer| { - renderer.fill_quad( - core::renderer::Quad { - bounds, - ..Default::default() - }, - core::Background::Color(bg_color), - ); - - let underline = 2.; - renderer.fill_quad( - core::renderer::Quad { - bounds: bounds.shrink(core::Padding { - top: bounds.height - underline, - ..Default::default() - }), - ..Default::default() - }, - core::Background::Color(fore_color), - ); - - renderer.fill_paragraph(content, top_left, fore_color, bounds); - }); - } -} - /// Builds a window's [`UserInterface`] for the [`Program`]. fn build_user_interface<'a, P: Program>( program: &'a P, diff --git a/winit/src/program/state.rs b/winit/src/program/state.rs index 6361d1bab6..e883d04acd 100644 --- a/winit/src/program/state.rs +++ b/winit/src/program/state.rs @@ -4,7 +4,7 @@ use crate::core::{Color, Size}; use crate::graphics::Viewport; use crate::program::Program; -use winit::event::{Ime, Touch, WindowEvent}; +use winit::event::{Touch, WindowEvent}; use winit::window::Window; use std::fmt::{Debug, Formatter}; @@ -22,7 +22,6 @@ where modifiers: winit::keyboard::ModifiersState, theme: P::Theme, style: theme::Style, - preedit: String, } impl Debug for State

@@ -74,7 +73,6 @@ where modifiers: winit::keyboard::ModifiersState::default(), theme, style, - preedit: String::default(), } } @@ -138,11 +136,6 @@ where self.style.text_color } - /// TODO - pub fn preedit(&self) -> String { - self.preedit.clone() - } - /// Processes the provided window event and updates the [`State`] accordingly. pub fn update( &mut self, @@ -186,9 +179,6 @@ where WindowEvent::ModifiersChanged(new_modifiers) => { self.modifiers = new_modifiers.state(); } - WindowEvent::Ime(Ime::Preedit(text, _)) => { - self.preedit = text.clone(); - } #[cfg(feature = "debug")] WindowEvent::KeyboardInput { event: diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index a3c991df0d..cd49a8b41a 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -1,13 +1,20 @@ +use crate::conversion; +use crate::core::alignment; use crate::core::mouse; +use crate::core::renderer; +use crate::core::text; use crate::core::theme; use crate::core::time::Instant; -use crate::core::window::Id; -use crate::core::{Point, Size}; +use crate::core::window::{Id, RedrawRequest}; +use crate::core::{ + Color, InputMethod, Padding, Point, Rectangle, Size, Text, Vector, +}; use crate::graphics::Compositor; use crate::program::{Program, State}; use std::collections::BTreeMap; use std::sync::Arc; +use winit::dpi::{LogicalPosition, LogicalSize}; use winit::monitor::MonitorHandle; #[allow(missing_debug_implementations)] @@ -65,6 +72,7 @@ where renderer, mouse_interaction: mouse::Interaction::None, redraw_at: None, + preedit: None, }, ); @@ -155,6 +163,7 @@ where pub surface: C::Surface, pub renderer: P::Renderer, pub redraw_at: Option, + preedit: Option>, } impl Window @@ -179,4 +188,136 @@ where Size::new(size.width, size.height) } + + pub fn request_redraw(&mut self, redraw_request: RedrawRequest) { + match redraw_request { + RedrawRequest::NextFrame => { + self.raw.request_redraw(); + self.redraw_at = None; + } + RedrawRequest::At(at) => { + self.redraw_at = Some(at); + } + RedrawRequest::Wait => {} + } + } + + pub fn request_input_method(&mut self, input_method: InputMethod) { + self.raw.set_ime_allowed(match input_method { + InputMethod::Disabled => false, + InputMethod::Allowed | InputMethod::Open { .. } => true, + }); + + if let InputMethod::Open { + position, + purpose, + preedit, + } = input_method + { + self.raw.set_ime_cursor_area( + LogicalPosition::new(position.x, position.y), + LogicalSize::new(10, 10), + ); + + self.raw.set_ime_purpose(conversion::ime_purpose(purpose)); + + if let Some(content) = preedit { + if let Some(preedit) = &mut self.preedit { + preedit.update(&content, &self.renderer); + } else { + let mut preedit = Preedit::new(); + preedit.update(&content, &self.renderer); + + self.preedit = Some(preedit); + } + } + } else { + self.preedit = None; + } + } + + pub fn draw_preedit(&mut self) { + if let Some(preedit) = &self.preedit { + preedit.draw( + &mut self.renderer, + self.state.text_color(), + self.state.background_color(), + ); + } + } +} + +struct Preedit +where + Renderer: text::Renderer, +{ + position: Point, + content: text::paragraph::Plain, +} + +impl Preedit +where + Renderer: text::Renderer, +{ + fn new() -> Self { + Self { + position: Point::ORIGIN, + content: text::paragraph::Plain::default(), + } + } + + fn update(&mut self, text: &str, renderer: &Renderer) { + self.content.update(Text { + content: text, + bounds: Size::INFINITY, + size: renderer.default_size(), + line_height: text::LineHeight::default(), + font: renderer.default_font(), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, //Bottom, + shaping: text::Shaping::Advanced, + wrapping: text::Wrapping::None, + }); + } + + fn draw(&self, renderer: &mut Renderer, color: Color, background: Color) { + if self.content.min_width() < 1.0 { + return; + } + + let top_left = + self.position - Vector::new(0.0, self.content.min_height()); + + let bounds = Rectangle::new(top_left, self.content.min_bounds()); + + renderer.with_layer(bounds, |renderer| { + renderer.fill_quad( + renderer::Quad { + bounds, + ..Default::default() + }, + background, + ); + + renderer.fill_paragraph( + self.content.raw(), + top_left, + color, + bounds, + ); + + const UNDERLINE: f32 = 2.0; + + renderer.fill_quad( + renderer::Quad { + bounds: bounds.shrink(Padding { + top: bounds.height - UNDERLINE, + ..Default::default() + }), + ..Default::default() + }, + color, + ); + }); + } } From db990b77e4aa8d001c774703301342c951d6caaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 2 Feb 2025 21:06:50 +0100 Subject: [PATCH 05/20] Add neutral `None` variant to `InputMethod` --- core/src/input_method.rs | 13 ++++++++++--- core/src/shell.rs | 2 +- runtime/src/user_interface.rs | 2 +- winit/src/program/window_manager.rs | 11 +++++++---- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/core/src/input_method.rs b/core/src/input_method.rs index c293582dac..b25f29aac5 100644 --- a/core/src/input_method.rs +++ b/core/src/input_method.rs @@ -6,6 +6,8 @@ use std::ops::Range; /// The input method strategy of a widget. #[derive(Debug, Clone, PartialEq)] pub enum InputMethod { + /// No input method strategy has been specified. + None, /// No input method is allowed. Disabled, /// Input methods are allowed, but not open yet. @@ -73,7 +75,7 @@ impl InputMethod { /// ``` pub fn merge>(&mut self, other: &InputMethod) { match other { - InputMethod::Disabled => {} + InputMethod::None => {} InputMethod::Open { position, purpose, @@ -88,10 +90,15 @@ impl InputMethod { .map(str::to_owned), }; } - InputMethod::Allowed if matches!(self, Self::Disabled) => { + InputMethod::Allowed + if matches!(self, Self::None | Self::Disabled) => + { *self = Self::Allowed; } - InputMethod::Allowed => {} + InputMethod::Disabled if matches!(self, Self::None) => { + *self = Self::Disabled; + } + _ => {} } } } diff --git a/core/src/shell.rs b/core/src/shell.rs index e87d169621..d01233c781 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -27,7 +27,7 @@ impl<'a, Message> Shell<'a, Message> { redraw_request: window::RedrawRequest::Wait, is_layout_invalid: false, are_widgets_invalid: false, - input_method: InputMethod::Disabled, + input_method: InputMethod::None, } } diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 59e497c547..4bb7bba777 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -189,7 +189,7 @@ where let mut outdated = false; let mut redraw_request = window::RedrawRequest::Wait; - let mut input_method = InputMethod::Disabled; + let mut input_method = InputMethod::None; let mut manual_overlay = ManuallyDrop::new( self.root diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index cd49a8b41a..32faa05981 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -203,10 +203,13 @@ where } pub fn request_input_method(&mut self, input_method: InputMethod) { - self.raw.set_ime_allowed(match input_method { - InputMethod::Disabled => false, - InputMethod::Allowed | InputMethod::Open { .. } => true, - }); + match input_method { + InputMethod::None => {} + InputMethod::Disabled => self.raw.set_ime_allowed(false), + InputMethod::Allowed | InputMethod::Open { .. } => { + self.raw.set_ime_allowed(true) + } + } if let InputMethod::Open { position, From d28af5739bfaafa141dc8071a0c910e8693f3b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 3 Feb 2025 00:51:57 +0100 Subject: [PATCH 06/20] Track pre-edits separately from focus in text inputs --- widget/src/text_editor.rs | 15 ++++++--------- widget/src/text_input.rs | 19 +++++++------------ winit/src/program/window_manager.rs | 6 ++++-- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 4f985f28b1..72e15c288d 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -332,14 +332,13 @@ where ) -> InputMethod<&'b str> { let Some(Focus { is_window_focused: true, - is_ime_open, .. }) = &state.focus else { return InputMethod::Disabled; }; - let Some(preedit) = &is_ime_open else { + let Some(preedit) = &state.preedit else { return InputMethod::Allowed; }; @@ -497,6 +496,7 @@ where #[derive(Debug)] pub struct State { focus: Option, + preedit: Option, last_click: Option, drag_click: Option, partial_scroll: f32, @@ -510,7 +510,6 @@ struct Focus { updated_at: Instant, now: Instant, is_window_focused: bool, - is_ime_open: Option, } impl Focus { @@ -523,7 +522,6 @@ impl Focus { updated_at: now, now, is_window_focused: true, - is_ime_open: None, } } @@ -573,6 +571,7 @@ where fn state(&self) -> widget::tree::State { widget::tree::State::new(State { focus: None, + preedit: None, last_click: None, drag_click: None, partial_scroll: 0.0, @@ -752,13 +751,11 @@ where } Update::InputMethod(update) => match update { Ime::Toggle(is_open) => { - if let Some(focus) = &mut state.focus { - focus.is_ime_open = is_open.then(String::new); - } + state.preedit = is_open.then(String::new); } Ime::Preedit(text) => { - if let Some(focus) = &mut state.focus { - focus.is_ime_open = Some(text); + if state.focus.is_some() { + state.preedit = Some(text); } } Ime::Commit(text) => { diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index d0e93927a4..58bbc0d611 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -400,14 +400,13 @@ where ) -> InputMethod<&'b str> { let Some(Focus { is_window_focused: true, - is_ime_open, .. }) = &state.is_focused else { return InputMethod::Disabled; }; - let Some(preedit) = is_ime_open else { + let Some(preedit) = &state.is_ime_open else { return InputMethod::Allowed; }; @@ -729,7 +728,6 @@ where updated_at: now, now, is_window_focused: true, - is_ime_open: None, }) } else { None @@ -1257,17 +1255,15 @@ where input_method::Event::Opened | input_method::Event::Closed => { let state = state::(tree); - if let Some(focus) = &mut state.is_focused { - focus.is_ime_open = - matches!(event, input_method::Event::Opened) - .then(String::new); - } + state.is_ime_open = + matches!(event, input_method::Event::Opened) + .then(String::new); } input_method::Event::Preedit(content, _range) => { let state = state::(tree); - if let Some(focus) = &mut state.is_focused { - focus.is_ime_open = Some(content.to_owned()); + if state.is_focused.is_some() { + state.is_ime_open = Some(content.to_owned()); } } input_method::Event::Commit(text) => { @@ -1519,6 +1515,7 @@ pub struct State { placeholder: paragraph::Plain

, icon: paragraph::Plain

, is_focused: Option, + is_ime_open: Option, is_dragging: bool, is_pasting: Option, last_click: Option, @@ -1538,7 +1535,6 @@ struct Focus { updated_at: Instant, now: Instant, is_window_focused: bool, - is_ime_open: Option, } impl State

{ @@ -1565,7 +1561,6 @@ impl State

{ updated_at: now, now, is_window_focused: true, - is_ime_open: None, }); self.move_cursor_to_end(); diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index 32faa05981..08d19d161f 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -205,9 +205,11 @@ where pub fn request_input_method(&mut self, input_method: InputMethod) { match input_method { InputMethod::None => {} - InputMethod::Disabled => self.raw.set_ime_allowed(false), + InputMethod::Disabled => { + self.raw.set_ime_allowed(false); + } InputMethod::Allowed | InputMethod::Open { .. } => { - self.raw.set_ime_allowed(true) + self.raw.set_ime_allowed(true); } } From 3a35fd6249eeb324379d3a14b020ccc48ec16fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 3 Feb 2025 01:30:41 +0100 Subject: [PATCH 07/20] Clamp pre-edit inside viewport bounds --- widget/src/text_input.rs | 5 ++-- winit/src/program/window_manager.rs | 46 ++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 58bbc0d611..a1a1d3b5cc 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -413,8 +413,7 @@ where let secure_value = self.is_secure.then(|| value.secure()); let value = secure_value.as_ref().unwrap_or(value); - let mut children_layout = layout.children(); - let text_bounds = children_layout.next().unwrap().bounds(); + let text_bounds = layout.children().next().unwrap().bounds(); let caret_index = match state.cursor.state(value) { cursor::State::Index(position) => position, @@ -435,7 +434,7 @@ where + alignment_offset; InputMethod::Open { - position: Point::new(x, text_bounds.y), + position: Point::new(x, text_bounds.y + text_bounds.height), purpose: if self.is_secure { input_method::Purpose::Secure } else { diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index 08d19d161f..35a8d7dc45 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -221,17 +221,19 @@ where { self.raw.set_ime_cursor_area( LogicalPosition::new(position.x, position.y), - LogicalSize::new(10, 10), + LogicalSize::new(10, 10), // TODO? ); self.raw.set_ime_purpose(conversion::ime_purpose(purpose)); if let Some(content) = preedit { - if let Some(preedit) = &mut self.preedit { - preedit.update(&content, &self.renderer); + if content.is_empty() { + self.preedit = None; + } else if let Some(preedit) = &mut self.preedit { + preedit.update(position, &content, &self.renderer); } else { let mut preedit = Preedit::new(); - preedit.update(&content, &self.renderer); + preedit.update(position, &content, &self.renderer); self.preedit = Some(preedit); } @@ -247,6 +249,10 @@ where &mut self.renderer, self.state.text_color(), self.state.background_color(), + &Rectangle::new( + Point::ORIGIN, + self.state.viewport().logical_size(), + ), ); } } @@ -271,7 +277,9 @@ where } } - fn update(&mut self, text: &str, renderer: &Renderer) { + fn update(&mut self, position: Point, text: &str, renderer: &Renderer) { + self.position = position; + self.content.update(Text { content: text, bounds: Size::INFINITY, @@ -279,21 +287,37 @@ where line_height: text::LineHeight::default(), font: renderer.default_font(), horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, //Bottom, + vertical_alignment: alignment::Vertical::Top, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::None, }); } - fn draw(&self, renderer: &mut Renderer, color: Color, background: Color) { + fn draw( + &self, + renderer: &mut Renderer, + color: Color, + background: Color, + viewport: &Rectangle, + ) { if self.content.min_width() < 1.0 { return; } - let top_left = - self.position - Vector::new(0.0, self.content.min_height()); + let mut bounds = Rectangle::new( + self.position - Vector::new(0.0, self.content.min_height()), + self.content.min_bounds(), + ); + + bounds.x = bounds + .x + .max(viewport.x) + .min(viewport.x + viewport.width - bounds.width); - let bounds = Rectangle::new(top_left, self.content.min_bounds()); + bounds.y = bounds + .y + .max(viewport.y) + .min(viewport.y + viewport.height - bounds.height); renderer.with_layer(bounds, |renderer| { renderer.fill_quad( @@ -306,7 +330,7 @@ where renderer.fill_paragraph( self.content.raw(), - top_left, + bounds.position(), color, bounds, ); From c83809adb907498ba2a573ec9fb50936601ac8fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 3 Feb 2025 02:33:40 +0100 Subject: [PATCH 08/20] Implement basic IME selection in `Preedit` overlay --- core/src/input_method.rs | 47 +++++++++-- core/src/text.rs | 17 ++++ widget/src/text_editor.rs | 27 +++++-- widget/src/text_input.rs | 13 +-- winit/src/program/window_manager.rs | 120 ++++++++++++++++++++++------ 5 files changed, 181 insertions(+), 43 deletions(-) diff --git a/core/src/input_method.rs b/core/src/input_method.rs index b25f29aac5..f10a1c3b31 100644 --- a/core/src/input_method.rs +++ b/core/src/input_method.rs @@ -23,10 +23,50 @@ pub enum InputMethod { /// Ideally, your widget will show pre-edits on-the-spot; but, since that can /// be tricky, you can instead provide the current pre-edit here and the /// runtime will display it as an overlay (i.e. "Over-the-spot IME"). - preedit: Option, + preedit: Option>, }, } +/// The pre-edit of an [`InputMethod`]. +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Preedit { + /// The current content. + pub content: T, + /// The selected range of the content. + pub selection: Option>, +} + +impl Preedit { + /// Creates a new empty [`Preedit`]. + pub fn new() -> Self + where + T: Default, + { + Self::default() + } + + /// Turns a [`Preedit`] into its owned version. + pub fn to_owned(&self) -> Preedit + where + T: AsRef, + { + Preedit { + content: self.content.as_ref().to_owned(), + selection: self.selection.clone(), + } + } +} + +impl Preedit { + /// Borrows the contents of a [`Preedit`]. + pub fn as_ref(&self) -> Preedit<&str> { + Preedit { + content: &self.content, + selection: self.selection.clone(), + } + } +} + /// The purpose of an [`InputMethod`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Purpose { @@ -84,10 +124,7 @@ impl InputMethod { *self = Self::Open { position: *position, purpose: *purpose, - preedit: preedit - .as_ref() - .map(AsRef::as_ref) - .map(str::to_owned), + preedit: preedit.as_ref().map(Preedit::to_owned), }; } InputMethod::Allowed diff --git a/core/src/text.rs b/core/src/text.rs index c144fd24b7..8dde9e21fa 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -270,6 +270,23 @@ pub struct Span<'a, Link = (), Font = crate::Font> { pub strikethrough: bool, } +impl Default for Span<'_, Link, Font> { + fn default() -> Self { + Self { + text: Cow::default(), + size: None, + line_height: None, + font: None, + color: None, + link: None, + highlight: None, + padding: Padding::default(), + underline: false, + strikethrough: false, + } + } +} + /// A text highlight. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Highlight { diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 72e15c288d..26d05ccdfc 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -55,6 +55,7 @@ use std::borrow::Cow; use std::cell::RefCell; use std::fmt; use std::ops::DerefMut; +use std::ops::Range; use std::sync::Arc; pub use text::editor::{Action, Edit, Line, LineEnding, Motion}; @@ -365,7 +366,7 @@ where InputMethod::Open { position, purpose: input_method::Purpose::Normal, - preedit: Some(preedit), + preedit: Some(preedit.as_ref()), } } } @@ -496,7 +497,7 @@ where #[derive(Debug)] pub struct State { focus: Option, - preedit: Option, + preedit: Option, last_click: Option, drag_click: Option, partial_scroll: f32, @@ -751,11 +752,15 @@ where } Update::InputMethod(update) => match update { Ime::Toggle(is_open) => { - state.preedit = is_open.then(String::new); + state.preedit = + is_open.then(input_method::Preedit::new); } - Ime::Preedit(text) => { + Ime::Preedit { content, selection } => { if state.focus.is_some() { - state.preedit = Some(text); + state.preedit = Some(input_method::Preedit { + content, + selection, + }); } } Ime::Commit(text) => { @@ -1202,7 +1207,10 @@ enum Update { enum Ime { Toggle(bool), - Preedit(String), + Preedit { + content: String, + selection: Option>, + }, Commit(String), } @@ -1272,8 +1280,11 @@ impl Update { input_method::Event::Opened )))) } - input_method::Event::Preedit(content, _range) => { - Some(Update::InputMethod(Ime::Preedit(content))) + input_method::Event::Preedit(content, selection) => { + Some(Update::InputMethod(Ime::Preedit { + content, + selection, + })) } input_method::Event::Commit(content) => { Some(Update::InputMethod(Ime::Commit(content))) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index a1a1d3b5cc..4c9e46c196 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -440,7 +440,7 @@ where } else { input_method::Purpose::Normal }, - preedit: Some(preedit), + preedit: Some(preedit.as_ref()), } } @@ -1256,13 +1256,16 @@ where state.is_ime_open = matches!(event, input_method::Event::Opened) - .then(String::new); + .then(input_method::Preedit::new); } - input_method::Event::Preedit(content, _range) => { + input_method::Event::Preedit(content, selection) => { let state = state::(tree); if state.is_focused.is_some() { - state.is_ime_open = Some(content.to_owned()); + state.is_ime_open = Some(input_method::Preedit { + content: content.to_owned(), + selection: selection.clone(), + }); } } input_method::Event::Commit(text) => { @@ -1514,7 +1517,7 @@ pub struct State { placeholder: paragraph::Plain

, icon: paragraph::Plain

, is_focused: Option, - is_ime_open: Option, + is_ime_open: Option, is_dragging: bool, is_pasting: Option, last_click: Option, diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index 35a8d7dc45..86cee973ed 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -1,5 +1,6 @@ use crate::conversion; use crate::core::alignment; +use crate::core::input_method; use crate::core::mouse; use crate::core::renderer; use crate::core::text; @@ -12,11 +13,13 @@ use crate::core::{ use crate::graphics::Compositor; use crate::program::{Program, State}; -use std::collections::BTreeMap; -use std::sync::Arc; use winit::dpi::{LogicalPosition, LogicalSize}; use winit::monitor::MonitorHandle; +use std::borrow::Cow; +use std::collections::BTreeMap; +use std::sync::Arc; + #[allow(missing_debug_implementations)] pub struct WindowManager where @@ -226,16 +229,26 @@ where self.raw.set_ime_purpose(conversion::ime_purpose(purpose)); - if let Some(content) = preedit { - if content.is_empty() { + if let Some(preedit) = preedit { + if preedit.content.is_empty() { self.preedit = None; - } else if let Some(preedit) = &mut self.preedit { - preedit.update(position, &content, &self.renderer); + } else if let Some(overlay) = &mut self.preedit { + overlay.update( + position, + &preedit, + self.state.background_color(), + &self.renderer, + ); } else { - let mut preedit = Preedit::new(); - preedit.update(position, &content, &self.renderer); - - self.preedit = Some(preedit); + let mut overlay = Preedit::new(); + overlay.update( + position, + &preedit, + self.state.background_color(), + &self.renderer, + ); + + self.preedit = Some(overlay); } } } else { @@ -263,7 +276,8 @@ where Renderer: text::Renderer, { position: Point, - content: text::paragraph::Plain, + content: Renderer::Paragraph, + spans: Vec>, } impl Preedit @@ -273,24 +287,67 @@ where fn new() -> Self { Self { position: Point::ORIGIN, - content: text::paragraph::Plain::default(), + spans: Vec::new(), + content: Renderer::Paragraph::default(), } } - fn update(&mut self, position: Point, text: &str, renderer: &Renderer) { + fn update( + &mut self, + position: Point, + preedit: &input_method::Preedit, + background: Color, + renderer: &Renderer, + ) { self.position = position; - self.content.update(Text { - content: text, - bounds: Size::INFINITY, - size: renderer.default_size(), - line_height: text::LineHeight::default(), - font: renderer.default_font(), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - shaping: text::Shaping::Advanced, - wrapping: text::Wrapping::None, - }); + let spans = match &preedit.selection { + Some(selection) => { + vec![ + text::Span { + text: Cow::Borrowed( + &preedit.content[..selection.start], + ), + ..text::Span::default() + }, + text::Span { + text: Cow::Borrowed( + if selection.start == selection.end { + "\u{200A}" + } else { + &preedit.content[selection.start..selection.end] + }, + ), + color: Some(background), + ..text::Span::default() + }, + text::Span { + text: Cow::Borrowed(&preedit.content[selection.end..]), + ..text::Span::default() + }, + ] + } + _ => vec![text::Span { + text: Cow::Borrowed(&preedit.content), + ..text::Span::default() + }], + }; + + if spans != self.spans.as_slice() { + use text::Paragraph as _; + + self.content = Renderer::Paragraph::with_spans(Text { + content: &spans, + bounds: Size::INFINITY, + size: renderer.default_size(), + line_height: text::LineHeight::default(), + font: renderer.default_font(), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + shaping: text::Shaping::Advanced, + wrapping: text::Wrapping::None, + }); + } } fn draw( @@ -300,6 +357,8 @@ where background: Color, viewport: &Rectangle, ) { + use text::Paragraph as _; + if self.content.min_width() < 1.0 { return; } @@ -329,7 +388,7 @@ where ); renderer.fill_paragraph( - self.content.raw(), + &self.content, bounds.position(), color, bounds, @@ -347,6 +406,17 @@ where }, color, ); + + for span_bounds in self.content.span_bounds(1) { + renderer.fill_quad( + renderer::Quad { + bounds: span_bounds + + (bounds.position() - Point::ORIGIN), + ..Default::default() + }, + color, + ); + } }); } } From c9abe25d3167bb12d935e3a095160a897dd98176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 3 Feb 2025 02:38:20 +0100 Subject: [PATCH 09/20] Use `text::Span::new` in `window_manager` --- core/src/text.rs | 44 ++++++++++++----------------- winit/src/program/window_manager.rs | 35 ++++++----------------- 2 files changed, 27 insertions(+), 52 deletions(-) diff --git a/core/src/text.rs b/core/src/text.rs index 8dde9e21fa..a7e1f28163 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -270,23 +270,6 @@ pub struct Span<'a, Link = (), Font = crate::Font> { pub strikethrough: bool, } -impl Default for Span<'_, Link, Font> { - fn default() -> Self { - Self { - text: Cow::default(), - size: None, - line_height: None, - font: None, - color: None, - link: None, - highlight: None, - padding: Padding::default(), - underline: false, - strikethrough: false, - } - } -} - /// A text highlight. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Highlight { @@ -301,15 +284,7 @@ impl<'a, Link, Font> Span<'a, Link, Font> { pub fn new(fragment: impl IntoFragment<'a>) -> Self { Self { text: fragment.into_fragment(), - size: None, - line_height: None, - font: None, - color: None, - highlight: None, - link: None, - padding: Padding::ZERO, - underline: false, - strikethrough: false, + ..Self::default() } } @@ -457,6 +432,23 @@ impl<'a, Link, Font> Span<'a, Link, Font> { } } +impl Default for Span<'_, Link, Font> { + fn default() -> Self { + Self { + text: Cow::default(), + size: None, + line_height: None, + font: None, + color: None, + link: None, + highlight: None, + padding: Padding::default(), + underline: false, + strikethrough: false, + } + } +} + impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> { fn from(value: &'a str) -> Self { Span::new(value) diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index 86cee973ed..ae214e7cfd 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -16,7 +16,6 @@ use crate::program::{Program, State}; use winit::dpi::{LogicalPosition, LogicalSize}; use winit::monitor::MonitorHandle; -use std::borrow::Cow; use std::collections::BTreeMap; use std::sync::Arc; @@ -304,33 +303,17 @@ where let spans = match &preedit.selection { Some(selection) => { vec![ - text::Span { - text: Cow::Borrowed( - &preedit.content[..selection.start], - ), - ..text::Span::default() - }, - text::Span { - text: Cow::Borrowed( - if selection.start == selection.end { - "\u{200A}" - } else { - &preedit.content[selection.start..selection.end] - }, - ), - color: Some(background), - ..text::Span::default() - }, - text::Span { - text: Cow::Borrowed(&preedit.content[selection.end..]), - ..text::Span::default() - }, + text::Span::new(&preedit.content[..selection.start]), + text::Span::new(if selection.start == selection.end { + "\u{200A}" + } else { + &preedit.content[selection.start..selection.end] + }) + .color(background), + text::Span::new(&preedit.content[selection.end..]), ] } - _ => vec![text::Span { - text: Cow::Borrowed(&preedit.content), - ..text::Span::default() - }], + _ => vec![text::Span::new(&preedit.content)], }; if spans != self.spans.as_slice() { From ba755c69d648cace61f23537266f2e556ee70c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 3 Feb 2025 03:34:41 +0100 Subject: [PATCH 10/20] Fulfill `InputMethod` requests only during `RedrawRequested` --- core/src/shell.rs | 3 +++ winit/src/program.rs | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/shell.rs b/core/src/shell.rs index d01233c781..a13492d5ad 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -78,6 +78,9 @@ impl<'a, Message> Shell<'a, Message> { } /// Requests the current [`InputMethod`] strategy. + /// + /// __Important__: This request will only be honored by the + /// [`Shell`] only during a [`window::Event::RedrawRequested`]. pub fn request_input_method>( &mut self, ime: &InputMethod, diff --git a/winit/src/program.rs b/winit/src/program.rs index 302bc6c333..7ead4c3b53 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -1025,19 +1025,19 @@ async fn run_instance( ); #[cfg(feature = "unconditional-rendering")] - window.raw.request_redraw(); + window.request_redraw( + window::RedrawRequest::NextFrame, + ); match ui_state { user_interface::State::Updated { redraw_request: _redraw_request, - input_method, + .. } => { #[cfg(not( feature = "unconditional-rendering" ))] window.request_redraw(_redraw_request); - - window.request_input_method(input_method); } user_interface::State::Outdated => { uis_stale = true; From 76c25d2fb2a6d83c6dbb71a92c184209c1d36acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 3 Feb 2025 03:36:18 +0100 Subject: [PATCH 11/20] Fix typo in `core::Event` documentation --- core/src/event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/event.rs b/core/src/event.rs index 323c5ffd61..7f0ab91438 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -25,7 +25,7 @@ pub enum Event { /// A touch event Touch(touch::Event), - /// A input method event + /// An input method event InputMethod(input_method::Event), } From 3029481b72a2ca975a9bb915e23c0461334ba54f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 3 Feb 2025 04:34:13 +0100 Subject: [PATCH 12/20] Ignore redraws of invisible content in `hover` widget --- core/src/shell.rs | 12 ++++++++++++ widget/src/helpers.rs | 14 +++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/core/src/shell.rs b/core/src/shell.rs index 12ebbaa89a..c2275f71c9 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -80,6 +80,18 @@ impl<'a, Message> Shell<'a, Message> { self.redraw_request } + /// Replaces the redraw request of the [`Shell`]; without conflict resolution. + /// + /// This is useful if you want to overwrite the redraw request to a previous value. + /// Since it's a fairly advanced use case and should rarely be used, it is a static + /// method. + pub fn replace_redraw_request( + shell: &mut Self, + redraw_request: Option, + ) { + shell.redraw_request = redraw_request; + } + /// Returns whether the current layout is invalid or not. pub fn is_layout_invalid(&self) -> bool { self.is_layout_invalid diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 17cf94cc0d..199b8fc021 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -871,16 +871,19 @@ where shell.request_redraw(); } + let is_visible = + is_hovered || self.is_top_focused || self.is_top_overlay_active; + if matches!( event, Event::Mouse( mouse::Event::CursorMoved { .. } | mouse::Event::ButtonReleased(_) ) - ) || is_hovered - || self.is_top_focused - || self.is_top_overlay_active + ) || is_visible { + let redraw_request = shell.redraw_request(); + self.top.as_widget_mut().update( top_tree, event.clone(), @@ -891,6 +894,11 @@ where shell, viewport, ); + + // Ignore redraw requests of invisible content + if !is_visible { + Shell::replace_redraw_request(shell, redraw_request); + } }; if shell.is_event_captured() { From 0c0651de5b1722194d48c19cd645e747fb72ccb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 3 Feb 2025 05:45:51 +0100 Subject: [PATCH 13/20] Introduce `Levitating` variant for `mouse::Cursor` --- core/src/mouse/cursor.rs | 39 ++++++++++++++++++++++++++++++++------- widget/src/scrollable.rs | 6 ++++-- widget/src/stack.rs | 21 +++++++++------------ 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/core/src/mouse/cursor.rs b/core/src/mouse/cursor.rs index 616cd31514..9388a5785f 100644 --- a/core/src/mouse/cursor.rs +++ b/core/src/mouse/cursor.rs @@ -1,13 +1,14 @@ use crate::{Point, Rectangle, Transformation, Vector}; -use std::ops::Mul; - /// The mouse cursor state. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub enum Cursor { /// The cursor has a defined position. Available(Point), + /// The cursor has a defined position, but it's levitating over a layer above. + Levitating(Point), + /// The cursor is currently unavailable (i.e. out of bounds or busy). #[default] Unavailable, @@ -18,7 +19,7 @@ impl Cursor { pub fn position(self) -> Option { match self { Cursor::Available(position) => Some(position), - Cursor::Unavailable => None, + Cursor::Levitating(_) | Cursor::Unavailable => None, } } @@ -51,17 +52,41 @@ impl Cursor { pub fn is_over(self, bounds: Rectangle) -> bool { self.position_over(bounds).is_some() } + + /// Returns true if the [`Cursor`] is levitating over a layer above. + pub fn is_levitating(self) -> bool { + matches!(self, Self::Levitating(_)) + } + + /// Makes the [`Cursor`] levitate over a layer above. + pub fn levitate(self) -> Self { + match self { + Self::Available(position) => Self::Levitating(position), + _ => self, + } + } + + /// Brings the [`Cursor`] back to the current layer. + pub fn land(self) -> Self { + match self { + Cursor::Levitating(position) => Cursor::Available(position), + _ => self, + } + } } -impl Mul for Cursor { +impl std::ops::Mul for Cursor { type Output = Self; fn mul(self, transformation: Transformation) -> Self { match self { - Cursor::Unavailable => Cursor::Unavailable, - Cursor::Available(point) => { - Cursor::Available(point * transformation) + Self::Available(position) => { + Self::Available(position * transformation) + } + Self::Levitating(position) => { + Self::Levitating(position * transformation) } + Self::Unavailable => Self::Unavailable, } } } diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 312aee2968..2dbe0479e8 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -563,7 +563,8 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { if let Some(scrollbar) = scrollbars.y { - let Some(cursor_position) = cursor.position() + let Some(cursor_position) = + cursor.land().position() else { return; }; @@ -635,7 +636,8 @@ where match event { Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { - let Some(cursor_position) = cursor.position() else { + let Some(cursor_position) = cursor.land().position() + else { return; }; diff --git a/widget/src/stack.rs b/widget/src/stack.rs index 12ed941dbb..d5cf1ecf3a 100644 --- a/widget/src/stack.rs +++ b/widget/src/stack.rs @@ -216,15 +216,15 @@ where viewport: &Rectangle, ) { let is_over = cursor.is_over(layout.bounds()); - let is_mouse_movement = - matches!(event, Event::Mouse(mouse::Event::CursorMoved { .. })); + let end = self.children.len() - 1; - for ((child, state), layout) in self + for (i, ((child, state), layout)) in self .children .iter_mut() .rev() .zip(tree.children.iter_mut().rev()) .zip(layout.children().rev()) + .enumerate() { child.as_widget_mut().update( state, @@ -237,22 +237,19 @@ where viewport, ); - if is_over - && !is_mouse_movement - && cursor != mouse::Cursor::Unavailable - { + if shell.is_event_captured() { + return; + } + + if i < end && is_over && !cursor.is_levitating() { let interaction = child.as_widget().mouse_interaction( state, layout, cursor, viewport, renderer, ); if interaction != mouse::Interaction::None { - cursor = mouse::Cursor::Unavailable; + cursor = cursor.levitate(); } } - - if shell.is_event_captured() { - return; - } } } From e8c680ce66b6b766a196e799b209e73e0bf416ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 3 Feb 2025 16:55:10 +0100 Subject: [PATCH 14/20] Request redraws on `InputMethod` events --- widget/src/text_editor.rs | 4 ++++ widget/src/text_input.rs | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 26d05ccdfc..486741c638 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -754,6 +754,8 @@ where Ime::Toggle(is_open) => { state.preedit = is_open.then(input_method::Preedit::new); + + shell.request_redraw(); } Ime::Preedit { content, selection } => { if state.focus.is_some() { @@ -761,6 +763,8 @@ where content, selection, }); + + shell.request_redraw(); } } Ime::Commit(text) => { diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 4c9e46c196..b22ee1ca81 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -36,13 +36,13 @@ mod value; pub mod cursor; pub use cursor::Cursor; -use iced_runtime::core::input_method; pub use value::Value; use editor::Editor; use crate::core::alignment; use crate::core::clipboard::{self, Clipboard}; +use crate::core::input_method; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout; @@ -1257,6 +1257,8 @@ where state.is_ime_open = matches!(event, input_method::Event::Opened) .then(input_method::Preedit::new); + + shell.request_redraw(); } input_method::Event::Preedit(content, selection) => { let state = state::(tree); @@ -1266,6 +1268,8 @@ where content: content.to_owned(), selection: selection.clone(), }); + + shell.request_redraw(); } } input_method::Event::Commit(text) => { From 141290c7402a4e087ce18d60b210f4feeafcebee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 3 Feb 2025 17:12:08 +0100 Subject: [PATCH 15/20] Fix `InputMethod` conflicts with multiple scrollables --- core/src/input_method.rs | 64 +++++++++++++++++++++++++--------------- widget/src/scrollable.rs | 12 +++++--- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/core/src/input_method.rs b/core/src/input_method.rs index f10a1c3b31..4e8c383b7d 100644 --- a/core/src/input_method.rs +++ b/core/src/input_method.rs @@ -82,21 +82,21 @@ pub enum Purpose { } impl InputMethod { - /// Merges two [`InputMethod`] strategies, prioritizing the second one when both ready: + /// Merges two [`InputMethod`] strategies, prioritizing the first one when both open: /// ``` - /// # use iced_core::input_method::{InputMethod, Purpose}; + /// # use iced_core::input_method::{InputMethod, Purpose, Preedit}; /// # use iced_core::Point; /// /// let open = InputMethod::Open { /// position: Point::ORIGIN, /// purpose: Purpose::Normal, - /// preedit: None, + /// preedit: Some(Preedit { content: "1".to_owned(), selection: None }), /// }; /// /// let open_2 = InputMethod::Open { /// position: Point::ORIGIN, /// purpose: Purpose::Secure, - /// preedit: None, + /// preedit: Some(Preedit { content: "2".to_owned(), selection: None }), /// }; /// /// let mut ime = InputMethod::Disabled; @@ -111,31 +111,47 @@ impl InputMethod { /// assert_eq!(ime, open); /// /// ime.merge(&open_2); - /// assert_eq!(ime, open_2); + /// assert_eq!(ime, open); /// ``` pub fn merge>(&mut self, other: &InputMethod) { - match other { - InputMethod::None => {} - InputMethod::Open { + match (&self, other) { + (InputMethod::Open { .. }, _) + | ( + InputMethod::Allowed, + InputMethod::None | InputMethod::Disabled, + ) + | (InputMethod::Disabled, InputMethod::None) => {} + _ => { + *self = other.to_owned(); + } + } + } + + /// Returns true if the [`InputMethod`] is open. + pub fn is_open(&self) -> bool { + matches!(self, Self::Open { .. }) + } +} + +impl InputMethod { + /// Turns an [`InputMethod`] into its owned version. + pub fn to_owned(&self) -> InputMethod + where + T: AsRef, + { + match self { + Self::None => InputMethod::None, + Self::Disabled => InputMethod::Disabled, + Self::Allowed => InputMethod::Allowed, + Self::Open { position, purpose, preedit, - } => { - *self = Self::Open { - position: *position, - purpose: *purpose, - preedit: preedit.as_ref().map(Preedit::to_owned), - }; - } - InputMethod::Allowed - if matches!(self, Self::None | Self::Disabled) => - { - *self = Self::Allowed; - } - InputMethod::Disabled if matches!(self, Self::None) => { - *self = Self::Disabled; - } - _ => {} + } => InputMethod::Open { + position: *position, + purpose: *purpose, + preedit: preedit.as_ref().map(Preedit::to_owned), + }, } } } diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 0a93584e8d..053b7df631 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -727,6 +727,8 @@ where _ => mouse::Cursor::Unavailable, }; + let had_input_method = shell.input_method().is_open(); + let translation = state.translation(self.direction, bounds, content_bounds); @@ -745,10 +747,12 @@ where }, ); - if let InputMethod::Open { position, .. } = - shell.input_method_mut() - { - *position = *position + translation; + if !had_input_method { + if let InputMethod::Open { position, .. } = + shell.input_method_mut() + { + *position = *position + translation; + } } }; From bab18858cd60168b63ae442026f45a90eb6be731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 3 Feb 2025 18:38:20 +0100 Subject: [PATCH 16/20] Handle pre-edits and commits only if `text_editor` is focused --- widget/src/text_editor.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 486741c638..cfdf6b5ddc 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -758,14 +758,10 @@ where shell.request_redraw(); } Ime::Preedit { content, selection } => { - if state.focus.is_some() { - state.preedit = Some(input_method::Preedit { - content, - selection, - }); + state.preedit = + Some(input_method::Preedit { content, selection }); - shell.request_redraw(); - } + shell.request_redraw(); } Ime::Commit(text) => { shell.publish(on_edit(Action::Edit(Edit::Paste( @@ -1284,15 +1280,20 @@ impl Update { input_method::Event::Opened )))) } - input_method::Event::Preedit(content, selection) => { + input_method::Event::Preedit(content, selection) + if state.focus.is_some() => + { Some(Update::InputMethod(Ime::Preedit { content, selection, })) } - input_method::Event::Commit(content) => { + input_method::Event::Commit(content) + if state.focus.is_some() => + { Some(Update::InputMethod(Ime::Commit(content))) } + _ => None, }, Event::Keyboard(keyboard::Event::KeyPressed { key, From d5d60ad7ea8bd7f6e8fbc7484cf7facd0f851756 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 18:52:39 +0000 Subject: [PATCH 17/20] Bump openssl from 0.10.69 to 0.10.70 Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.69 to 0.10.70. - [Release notes](https://github.com/sfackler/rust-openssl/releases) - [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.69...openssl-v0.10.70) --- updated-dependencies: - dependency-name: openssl dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c71d6d8df0..cea23889f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3957,9 +3957,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.69" +version = "0.10.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" dependencies = [ "bitflags 2.8.0", "cfg-if", @@ -3989,9 +3989,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", From 1b01d6718bf0c6b652a3256df51727c3e72fdd9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 4 Feb 2025 01:18:14 +0100 Subject: [PATCH 18/20] Update `Cargo.lock` dependencies --- Cargo.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cea23889f2..c9c7ddc5f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -716,9 +716,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "bytesize" @@ -4235,18 +4235,18 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" dependencies = [ "proc-macro2", "quote", @@ -5920,7 +5920,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.7.0", + "winnow 0.7.1", ] [[package]] @@ -7166,9 +7166,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e49d2d35d3fad69b39b94139037ecfb4f359f08958b9c11e7315ce770462419" +checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" dependencies = [ "memchr", ] From 8ca04bf69d0aa45e67297d82ae069815dd7c0043 Mon Sep 17 00:00:00 2001 From: edwloef Date: Mon, 3 Feb 2025 11:53:45 +0100 Subject: [PATCH 19/20] Add debug assertions to `draw_mesh` in `wgpu` renderer --- wgpu/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 2283cf711e..9c9fcb31d4 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -557,6 +557,16 @@ impl core::svg::Renderer for Renderer { impl graphics::mesh::Renderer for Renderer { fn draw_mesh(&mut self, mesh: graphics::Mesh) { + debug_assert!( + !mesh.indices().is_empty(), + "Mesh must not have empty indices" + ); + + debug_assert!( + mesh.indices().len() % 3 == 0, + "Mesh indices length must be a multiple of 3" + ); + let (layer, transformation) = self.layers.current_mut(); layer.draw_mesh(mesh, transformation); } From afef368d8a920c7702048d3b1604b2046fe46ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 3 Feb 2025 03:22:10 +0100 Subject: [PATCH 20/20] Take `Event` by reference in `Widget::update` --- core/src/element.rs | 4 ++-- core/src/overlay.rs | 2 +- core/src/overlay/element.rs | 4 ++-- core/src/overlay/group.rs | 11 ++------- core/src/widget.rs | 2 +- examples/bezier_tool/src/main.rs | 2 +- examples/game_of_life/src/main.rs | 4 ++-- examples/loading_spinners/src/circular.rs | 4 ++-- examples/loading_spinners/src/linear.rs | 4 ++-- examples/multitouch/src/main.rs | 4 ++-- examples/sierpinski_triangle/src/main.rs | 2 +- examples/toast/src/main.rs | 6 ++--- runtime/src/overlay/nested.rs | 6 ++--- runtime/src/user_interface.rs | 3 +-- widget/src/button.rs | 4 ++-- widget/src/canvas.rs | 2 +- widget/src/canvas/program.rs | 4 ++-- widget/src/checkbox.rs | 2 +- widget/src/column.rs | 10 ++------- widget/src/combo_box.rs | 6 ++--- widget/src/container.rs | 2 +- widget/src/helpers.rs | 16 +++++--------- widget/src/image/viewer.rs | 6 ++--- widget/src/keyed/column.rs | 10 ++------- widget/src/lazy.rs | 4 ++-- widget/src/lazy/component.rs | 4 ++-- widget/src/lazy/responsive.rs | 4 ++-- widget/src/mouse_area.rs | 8 +++---- widget/src/overlay/menu.rs | 4 ++-- widget/src/pane_grid.rs | 13 +++-------- widget/src/pane_grid/content.rs | 4 ++-- widget/src/pane_grid/title_bar.rs | 8 +++---- widget/src/pick_list.rs | 8 +++---- widget/src/pin.rs | 2 +- widget/src/pop.rs | 2 +- widget/src/radio.rs | 2 +- widget/src/row.rs | 12 +++------- widget/src/scrollable.rs | 8 +++---- widget/src/shader.rs | 4 ++-- widget/src/shader/program.rs | 2 +- widget/src/slider.rs | 2 +- widget/src/stack.rs | 10 ++------- widget/src/text/rich.rs | 2 +- widget/src/text_editor.rs | 27 ++++++++++++----------- widget/src/text_input.rs | 2 +- widget/src/themer.rs | 4 ++-- widget/src/toggler.rs | 2 +- widget/src/tooltip.rs | 2 +- widget/src/vertical_slider.rs | 6 ++--- 49 files changed, 111 insertions(+), 155 deletions(-) diff --git a/core/src/element.rs b/core/src/element.rs index 82ba753b48..ede9e16cfe 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -311,7 +311,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -447,7 +447,7 @@ where fn update( &mut self, state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/core/src/overlay.rs b/core/src/overlay.rs index 383663af37..94239152b1 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -58,7 +58,7 @@ where /// By default, it does nothing. fn update( &mut self, - _event: Event, + _event: &Event, _layout: Layout<'_>, _cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 7a1796634a..de6e73fdad 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -51,7 +51,7 @@ where /// Processes a runtime [`Event`]. pub fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -150,7 +150,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index e07744e352..970c1b0e89 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -74,7 +74,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -82,14 +82,7 @@ where shell: &mut Shell<'_, Message>, ) { for (child, layout) in self.children.iter_mut().zip(layout.children()) { - child.update( - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - ); + child.update(event, layout, cursor, renderer, clipboard, shell); } } diff --git a/core/src/widget.rs b/core/src/widget.rs index 2a40f8234a..3c9c50ab28 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -114,7 +114,7 @@ where fn update( &mut self, _state: &mut Tree, - _event: Event, + _event: &Event, _layout: Layout<'_>, _cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 4d438bd9e2..95ad299dbc 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -93,7 +93,7 @@ mod bezier { fn update( &self, state: &mut Self::State, - event: Event, + event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option> { diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 1008e477d2..dec3df7f83 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -380,7 +380,7 @@ mod grid { fn update( &self, interaction: &mut Interaction, - event: Event, + event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option> { @@ -471,7 +471,7 @@ mod grid { _ => action.and_capture(), }) } - mouse::Event::WheelScrolled { delta } => match delta { + mouse::Event::WheelScrolled { delta } => match *delta { mouse::ScrollDelta::Lines { y, .. } | mouse::ScrollDelta::Pixels { y, .. } => { if y < 0.0 && self.scaling > Self::MIN_SCALING diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 33232fac7e..24293138bc 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -264,7 +264,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, _layout: Layout<'_>, _cursor: mouse::Cursor, _renderer: &Renderer, @@ -278,7 +278,7 @@ where state.animation = state.animation.timed_transition( self.cycle_duration, self.rotation_duration, - now, + *now, ); state.cache.clear(); diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index a10b64f03e..a6713c7a7a 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -178,7 +178,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, _layout: Layout<'_>, _cursor: mouse::Cursor, _renderer: &Renderer, @@ -189,7 +189,7 @@ where let state = tree.state.downcast_mut::(); if let Event::Window(window::Event::RedrawRequested(now)) = event { - *state = state.timed_transition(self.cycle_duration, now); + *state = state.timed_transition(self.cycle_duration, *now); shell.request_redraw(); } diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index 5f4a5c90ec..bda3b8f759 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -55,11 +55,11 @@ impl canvas::Program for Multitouch { fn update( &self, _state: &mut Self::State, - event: Event, + event: &Event, _bounds: Rectangle, _cursor: mouse::Cursor, ) -> Option> { - let message = match event { + let message = match event.clone() { Event::Touch( touch::Event::FingerPressed { id, position } | touch::Event::FingerMoved { id, position }, diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index d4d483f5d4..a4a89455b3 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -76,7 +76,7 @@ impl canvas::Program for SierpinskiGraph { fn update( &self, _state: &mut Self::State, - event: Event, + event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option> { diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 2ae9bfe23e..dc314df85f 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -361,7 +361,7 @@ mod toast { fn update( &mut self, state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -491,7 +491,7 @@ mod toast { fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -530,7 +530,7 @@ mod toast { child.as_widget_mut().update( state, - event.clone(), + event, layout, cursor, renderer, diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index 342ad70c69..38054d7b4a 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -160,7 +160,7 @@ where /// Processes a runtime [`Event`]. pub fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -170,7 +170,7 @@ where fn recurse( element: &mut overlay::Element<'_, Message, Theme, Renderer>, layout: Layout<'_>, - event: Event, + event: &Event, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, @@ -188,7 +188,7 @@ where recurse( &mut nested, nested_layout, - event.clone(), + event, cursor, renderer, clipboard, diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 4bb7bba777..cb4416788f 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -210,7 +210,7 @@ where let mut layout = overlay.layout(renderer, bounds); let mut event_statuses = Vec::new(); - for event in events.iter().cloned() { + for event in events { let mut shell = Shell::new(messages); overlay.update( @@ -294,7 +294,6 @@ where let event_statuses = events .iter() - .cloned() .zip(overlay_statuses) .map(|(event, overlay_status)| { if matches!(overlay_status, event::Status::Captured) { diff --git a/widget/src/button.rs b/widget/src/button.rs index 11839d5eb2..0e24328f10 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -275,7 +275,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -285,7 +285,7 @@ where ) { self.content.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, layout.children().next().unwrap(), cursor, renderer, diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index d10771f005..046abddfbb 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -218,7 +218,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index c68b28308a..43446b6424 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -32,7 +32,7 @@ where fn update( &self, _state: &mut Self::State, - _event: Event, + _event: &Event, _bounds: Rectangle, _cursor: mouse::Cursor, ) -> Option> { @@ -82,7 +82,7 @@ where fn update( &self, state: &mut Self::State, - event: Event, + event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option> { diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 663bfad16f..6ed3e080aa 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -305,7 +305,7 @@ where fn update( &mut self, _tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/column.rs b/widget/src/column.rs index c729cbdbae..7200690b82 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -260,7 +260,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -275,13 +275,7 @@ where .zip(layout.children()) { child.as_widget_mut().update( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, + state, event, layout, cursor, renderer, clipboard, shell, viewport, ); } diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 057931551b..f71e4a6e40 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -512,7 +512,7 @@ where fn update( &mut self, tree: &mut widget::Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -540,7 +540,7 @@ where // Provide it to the widget self.text_input.update( &mut tree.children[0], - event.clone(), + event, layout, cursor, renderer, @@ -737,7 +737,7 @@ where let mut local_shell = Shell::new(&mut local_messages); self.text_input.update( &mut tree.children[0], - Event::Mouse(mouse::Event::ButtonPressed( + &Event::Mouse(mouse::Event::ButtonPressed( mouse::Button::Left, )), layout, diff --git a/widget/src/container.rs b/widget/src/container.rs index 852481f1d7..82dc314134 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -300,7 +300,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 199b8fc021..4cba197d06 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -633,7 +633,7 @@ where fn update( &mut self, state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -836,7 +836,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -885,14 +885,8 @@ where let redraw_request = shell.redraw_request(); self.top.as_widget_mut().update( - top_tree, - event.clone(), - top_layout, - cursor, - renderer, - clipboard, - shell, - viewport, + top_tree, event, top_layout, cursor, renderer, clipboard, + shell, viewport, ); // Ignore redraw requests of invisible content @@ -907,7 +901,7 @@ where self.base.as_widget_mut().update( base_tree, - event.clone(), + event, base_layout, cursor, renderer, diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 20a7955f3a..06652ff731 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -151,7 +151,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -167,7 +167,7 @@ where return; }; - match delta { + match *delta { mouse::ScrollDelta::Lines { y, .. } | mouse::ScrollDelta::Pixels { y, .. } => { let state = tree.state.downcast_mut::(); @@ -256,7 +256,7 @@ where .max(0.0) .round(); - let delta = position - origin; + let delta = *position - origin; let x = if bounds.width < scaled_size.width { (state.starting_offset.x - delta.x) diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index ab0b0bde5a..313b728ab8 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -300,7 +300,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -315,13 +315,7 @@ where .zip(layout.children()) { child.as_widget_mut().update( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, + state, event, layout, cursor, renderer, clipboard, shell, viewport, ); } diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index c6710e300a..6df026de34 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -198,7 +198,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -386,7 +386,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index c93b7c4294..30822b7d52 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -313,7 +313,7 @@ where fn update( &mut self, tree: &mut Tree, - event: core::Event, + event: &core::Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -593,7 +593,7 @@ where fn update( &mut self, - event: core::Event, + event: &core::Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index 8129336e09..e7c937afc6 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -188,7 +188,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -416,7 +416,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 9ba3cff5cf..c1c3ba0fb6 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -218,7 +218,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -228,7 +228,7 @@ where ) { self.content.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, layout, cursor, renderer, @@ -326,7 +326,7 @@ where fn update( widget: &mut MouseArea<'_, Message, Theme, Renderer>, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, @@ -425,7 +425,7 @@ fn update( } Event::Mouse(mouse::Event::WheelScrolled { delta }) => { if let Some(on_scroll) = widget.on_scroll.as_ref() { - shell.publish(on_scroll(delta)); + shell.publish(on_scroll(*delta)); shell.capture_event(); } } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 611476ce36..9d0539ffc6 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -263,7 +263,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -388,7 +388,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index e972b983ad..3ae1dfc7e6 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -474,7 +474,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -509,15 +509,8 @@ where let is_picked = picked_pane == Some(pane); content.update( - tree, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - viewport, - is_picked, + tree, event, layout, cursor, renderer, clipboard, shell, + viewport, is_picked, ); } diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index be5e506609..4d63dd1843 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -242,7 +242,7 @@ where pub(crate) fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -256,7 +256,7 @@ where title_bar.update( &mut tree.children[1], - event.clone(), + event, children.next().unwrap(), cursor, renderer, diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 4bd2c2f6da..611c3d6703 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -430,7 +430,7 @@ where pub(crate) fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -456,7 +456,7 @@ where compact.as_widget_mut().update( &mut tree.children[2], - event.clone(), + event, compact_layout, cursor, renderer, @@ -469,7 +469,7 @@ where controls.full.as_widget_mut().update( &mut tree.children[1], - event.clone(), + event, controls_layout, cursor, renderer, @@ -481,7 +481,7 @@ where } else { controls.full.as_widget_mut().update( &mut tree.children[1], - event.clone(), + event, controls_layout, cursor, renderer, diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 6708e7cd14..b751fcc34e 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -430,7 +430,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, @@ -489,13 +489,13 @@ where let options = self.options.borrow(); let selected = self.selected.as_ref().map(Borrow::borrow); - let next_option = if y < 0.0 { + let next_option = if *y < 0.0 { if let Some(selected) = selected { find_next(selected, options.iter()) } else { options.first() } - } else if y > 0.0 { + } else if *y > 0.0 { if let Some(selected) = selected { find_next(selected, options.iter().rev()) } else { @@ -513,7 +513,7 @@ where } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; } _ => {} }; diff --git a/widget/src/pin.rs b/widget/src/pin.rs index 7c1aca612f..afa2939840 100644 --- a/widget/src/pin.rs +++ b/widget/src/pin.rs @@ -177,7 +177,7 @@ where fn update( &mut self, tree: &mut widget::Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/pop.rs b/widget/src/pop.rs index 146cfb2b26..6e9df4be6a 100644 --- a/widget/src/pop.rs +++ b/widget/src/pop.rs @@ -108,7 +108,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 15c983df68..0df4d7157d 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -326,7 +326,7 @@ where fn update( &mut self, _state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/row.rs b/widget/src/row.rs index 3b605f0754..5ffeab498c 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -256,7 +256,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -271,13 +271,7 @@ where .zip(layout.children()) { child.as_widget_mut().update( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, + state, event, layout, cursor, renderer, clipboard, shell, viewport, ); } @@ -495,7 +489,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index a51a8625d8..8adf51362f 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -517,7 +517,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -736,7 +736,7 @@ where self.content.as_widget_mut().update( &mut tree.children[0], - event.clone(), + event, content, cursor, renderer, @@ -781,7 +781,7 @@ where modifiers, )) = event { - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; return; } @@ -792,7 +792,7 @@ where return; } - let delta = match delta { + let delta = match *delta { mouse::ScrollDelta::Lines { x, y } => { let is_shift_pressed = state.keyboard_modifiers.shift(); diff --git a/widget/src/shader.rs b/widget/src/shader.rs index 48c9632198..06254a1c1d 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -88,7 +88,7 @@ where fn update( &mut self, tree: &mut Tree, - event: crate::core::Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, @@ -174,7 +174,7 @@ where fn update( &self, state: &mut Self::State, - event: Event, + event: &Event, bounds: Rectangle, cursor: mouse::Cursor, ) -> Option> { diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs index 0fc110afeb..81ecc9b122 100644 --- a/widget/src/shader/program.rs +++ b/widget/src/shader/program.rs @@ -26,7 +26,7 @@ pub trait Program { fn update( &self, _state: &mut Self::State, - _event: shader::Event, + _event: &shader::Event, _bounds: Rectangle, _cursor: mouse::Cursor, ) -> Option> { diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 5250085473..1908abc994 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -245,7 +245,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/stack.rs b/widget/src/stack.rs index d5cf1ecf3a..df9f616288 100644 --- a/widget/src/stack.rs +++ b/widget/src/stack.rs @@ -207,7 +207,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, mut cursor: mouse::Cursor, renderer: &Renderer, @@ -227,13 +227,7 @@ where .enumerate() { child.as_widget_mut().update( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, + state, event, layout, cursor, renderer, clipboard, shell, viewport, ); diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index 0b499ec6c0..a9e544d14e 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -358,7 +358,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index cfdf6b5ddc..e685256bff 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -651,7 +651,7 @@ where fn update( &mut self, tree: &mut widget::Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -686,17 +686,18 @@ where Event::Window(window::Event::RedrawRequested(now)) => { if let Some(focus) = &mut state.focus { if focus.is_window_focused { - focus.now = now; + focus.now = *now; let millis_until_redraw = Focus::CURSOR_BLINK_INTERVAL_MILLIS - - (now - focus.updated_at).as_millis() + - (focus.now - focus.updated_at).as_millis() % Focus::CURSOR_BLINK_INTERVAL_MILLIS; shell.request_redraw_at( - now + Duration::from_millis( - millis_until_redraw as u64, - ), + focus.now + + Duration::from_millis( + millis_until_redraw as u64, + ), ); } } @@ -1216,7 +1217,7 @@ enum Ime { impl Update { fn from_event( - event: Event, + event: &Event, state: &State, bounds: Rectangle, padding: Padding, @@ -1284,14 +1285,14 @@ impl Update { if state.focus.is_some() => { Some(Update::InputMethod(Ime::Preedit { - content, - selection, + content: content.clone(), + selection: selection.clone(), })) } input_method::Event::Commit(content) if state.focus.is_some() => { - Some(Update::InputMethod(Ime::Commit(content))) + Some(Update::InputMethod(Ime::Commit(content.clone()))) } _ => None, }, @@ -1310,9 +1311,9 @@ impl Update { }; let key_press = KeyPress { - key, - modifiers, - text, + key: key.clone(), + modifiers: *modifiers, + text: text.clone(), status, }; diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index b22ee1ca81..215ecbd6a3 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -692,7 +692,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/themer.rs b/widget/src/themer.rs index 769cc4ca6a..4e583882af 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -113,7 +113,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -220,7 +220,7 @@ where fn update( &mut self, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 56c2be1fcd..b711432e10 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -309,7 +309,7 @@ where fn update( &mut self, _state: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index a0ffe39206..5bebeeace2 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -192,7 +192,7 @@ where fn update( &mut self, tree: &mut widget::Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 2ed9419ab8..6f878fde37 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -249,7 +249,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, @@ -379,7 +379,7 @@ where if state.keyboard_modifiers.control() => { if cursor.is_over(layout.bounds()) { - let delta = match delta { + let delta = match *delta { mouse::ScrollDelta::Lines { x: _, y } => y, mouse::ScrollDelta::Pixels { x: _, y } => y, }; @@ -411,7 +411,7 @@ where } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; } _ => {} }