diff options
Diffstat (limited to 'widget/src/text_editor.rs')
-rw-r--r-- | widget/src/text_editor.rs | 112 |
1 files changed, 108 insertions, 4 deletions
diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index f1ec589b..cfdf6b5d 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,14 +47,15 @@ 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, Color, Element, Event, InputMethod, Length, Padding, + Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector, }; 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}; @@ -322,6 +324,51 @@ where self.class = class.into(); self } + + fn input_method<'b>( + &self, + state: &'b State<Highlighter>, + renderer: &Renderer, + layout: Layout<'_>, + ) -> InputMethod<&'b str> { + let Some(Focus { + is_window_focused: true, + .. + }) = &state.focus + else { + return InputMethod::Disabled; + }; + + let Some(preedit) = &state.preedit else { + return InputMethod::Allowed; + }; + + let bounds = layout.bounds(); + let internal = self.content.0.borrow_mut(); + + let text_bounds = bounds.shrink(self.padding); + let translation = text_bounds.position() - Point::ORIGIN; + + 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.as_ref()), + } + } } /// The content of a [`TextEditor`]. @@ -450,6 +497,7 @@ where #[derive(Debug)] pub struct State<Highlighter: text::Highlighter> { focus: Option<Focus>, + preedit: Option<input_method::Preedit>, last_click: Option<mouse::Click>, drag_click: Option<mouse::click::Kind>, partial_scroll: f32, @@ -458,7 +506,7 @@ pub struct State<Highlighter: text::Highlighter> { highlighter_format_address: usize, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] struct Focus { updated_at: Instant, now: Instant, @@ -524,6 +572,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, @@ -605,7 +654,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 +750,25 @@ where })); shell.capture_event(); } + Update::InputMethod(update) => match update { + Ime::Toggle(is_open) => { + state.preedit = + is_open.then(input_method::Preedit::new); + + shell.request_redraw(); + } + Ime::Preedit { content, selection } => { + state.preedit = + Some(input_method::Preedit { content, selection }); + + shell.request_redraw(); + } + Ime::Commit(text) => { + shell.publish(on_edit(Action::Edit(Edit::Paste( + Arc::new(text), + )))); + } + }, Update::Binding(binding) => { fn apply_binding< H: text::Highlighter, @@ -827,6 +895,10 @@ where 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) @@ -1129,9 +1201,19 @@ enum Update<Message> { Drag(Point), Release, Scroll(f32), + InputMethod(Ime), Binding(Binding<Message>), } +enum Ime { + Toggle(bool), + Preedit { + content: String, + selection: Option<Range<usize>>, + }, + Commit(String), +} + impl<Message> Update<Message> { fn from_event<H: Highlighter>( event: Event, @@ -1191,6 +1273,28 @@ impl<Message> Update<Message> { } _ => None, }, + 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, selection) + if state.focus.is_some() => + { + Some(Update::InputMethod(Ime::Preedit { + content, + selection, + })) + } + input_method::Event::Commit(content) + if state.focus.is_some() => + { + Some(Update::InputMethod(Ime::Commit(content))) + } + _ => None, + }, Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, |