diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index 2c788235..cd93a7d7 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -22,10 +22,11 @@ use iced::Limits; use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent}; use iced::clipboard::mime::AsMimeTypes; use iced_core::event::{self, Event}; +use iced_core::input_method::{self, InputMethod, Preedit}; use iced_core::mouse::{self, click}; use iced_core::overlay::Group; use iced_core::renderer::{self, Renderer as CoreRenderer}; -use iced_core::text::{self, Paragraph, Renderer, Text}; +use iced_core::text::{self, Affinity, Paragraph, Renderer, Text}; use iced_core::time::{Duration, Instant}; use iced_core::touch; use iced_core::widget::Id; @@ -2083,6 +2084,66 @@ pub fn update<'a, Message: Clone + 'static>( state.keyboard_modifiers = *modifiers; } + Event::InputMethod(event) => { + let state = state(); + + match event { + input_method::Event::Opened | input_method::Event::Closed => { + state.preedit = matches!(event, input_method::Event::Opened) + .then(input_method::Preedit::new); + shell.capture_event(); + return; + } + input_method::Event::Preedit(content, selection) => { + if state.is_focused.is_some() { + state.preedit = Some(input_method::Preedit { + content: content.to_owned(), + selection: selection.clone(), + text_size: Some(size.into()), + }); + shell.capture_event(); + return; + } + } + input_method::Event::Commit(text) => { + let Some(focus) = &mut state.is_focused else { + return; + }; + let Some(on_input) = on_input else { + return; + }; + if state.is_read_only { + return; + } + + focus.updated_at = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); + + let mut editor = Editor::new(unsecured_value, &mut state.cursor); + editor.paste(Value::new(&text)); + + let contents = editor.contents(); + let unsecured_value = Value::new(&contents); + let message = if let Some(paste) = &on_paste { + (paste)(contents) + } else { + (on_input)(contents) + }; + shell.publish(message); + + state.is_pasting = None; + let value = if is_secure { + unsecured_value.secure() + } else { + unsecured_value + }; + + update_cache(state, &value); + shell.capture_event(); + return; + } + } + } Event::Window(window::Event::RedrawRequested(now)) => { let state = state(); @@ -2095,6 +2156,8 @@ pub fn update<'a, Message: Clone + 'static>( now.checked_add(Duration::from_millis(millis_until_redraw as u64)) .unwrap_or(*now), )); + + shell.request_input_method(&input_method(state, text_layout, unsecured_value)); } else if always_active { shell.request_redraw(); } @@ -2269,6 +2332,43 @@ pub fn update<'a, Message: Clone + 'static>( } } +fn input_method<'b>( + state: &'b State, + text_layout: Layout<'_>, + value: &Value, +) -> InputMethod<&'b str> { + if state.is_focused() { + } else { + return InputMethod::Disabled; + }; + + let text_bounds = text_layout.bounds(); + let cursor_index = match state.cursor.state(value) { + cursor::State::Index(position) => position, + cursor::State::Selection { start, end } => start.min(end), + }; + let (cursor, offset) = measure_cursor_and_scroll_offset( + state.value.raw(), + text_bounds, + cursor_index, + value, + state.cursor.affinity(), + state.scroll_offset, + ); + InputMethod::Enabled { + cursor: Rectangle::new( + Point::new(text_bounds.x + cursor - offset, text_bounds.y), + Size::new(1.0, text_bounds.height), + ), + purpose: if state.is_secure { + input_method::Purpose::Secure + } else { + input_method::Purpose::Normal + }, + preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref), + } +} + /// Draws the [`TextInput`] with the given [`Renderer`], overriding its /// [`Value`] if provided. /// @@ -2789,6 +2889,7 @@ pub struct State { is_pasting: Option, last_click: Option, cursor: Cursor, + preedit: Option, keyboard_modifiers: keyboard::Modifiers, scroll_offset: f32, } @@ -2868,6 +2969,7 @@ impl State { is_pasting: None, last_click: None, cursor: Cursor::default(), + preedit: None, keyboard_modifiers: keyboard::Modifiers::default(), scroll_offset: 0.0, dirty: false,