diff options
author | 2025-01-10 07:12:31 +0900 | |
---|---|---|
committer | 2025-02-02 17:44:13 +0100 | |
commit | 7db5256b720c3ecbe7c1cce7a1b47fd03151e03a (patch) | |
tree | ccf08e3f76e27d0871185b786ece83f970dddc77 /widget | |
parent | 599d8b560bec8036c5ddda62a7bf0a540bdec396 (diff) | |
download | iced-7db5256b720c3ecbe7c1cce7a1b47fd03151e03a.tar.gz iced-7db5256b720c3ecbe7c1cce7a1b47fd03151e03a.tar.bz2 iced-7db5256b720c3ecbe7c1cce7a1b47fd03151e03a.zip |
Draft `input_method` support
Diffstat (limited to 'widget')
-rw-r--r-- | widget/src/combo_box.rs | 6 | ||||
-rw-r--r-- | widget/src/lazy/component.rs | 2 | ||||
-rw-r--r-- | widget/src/scrollable.rs | 19 | ||||
-rw-r--r-- | widget/src/text_editor.rs | 69 | ||||
-rw-r--r-- | widget/src/text_input.rs | 95 |
5 files changed, 183 insertions, 8 deletions
diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 500d2bec..d7c7c922 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 15b8b62e..b9fbde58 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 312aee29..7df7a0e5 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 f1ec589b..2931e7f6 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<Rectangle> { + let bounds = layout.bounds(); + + let internal = self.content.0.borrow_mut(); + let state = tree.state.downcast_ref::<State<Highlighter>>(); + + 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<Message> { Drag(Point), Release, Scroll(f32), + Commit(String), Binding(Binding<Message>), } @@ -1191,6 +1251,9 @@ impl<Message> Update<Message> { } _ => 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 57ebe46a..f2756b5b 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<Rectangle> { + let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>(); + 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::<Renderer>(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::<Renderer>(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::<f32>::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 |