diff options
Diffstat (limited to 'widget/src/text_input.rs')
-rw-r--r-- | widget/src/text_input.rs | 151 |
1 files changed, 130 insertions, 21 deletions
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 57ebe46a..ae3dfe4c 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -42,6 +42,7 @@ 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; @@ -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, Color, Element, Event, InputMethod, Layout, Length, + Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::runtime::task::{self, Task}; use crate::runtime::Action; @@ -391,6 +392,54 @@ where } } + fn input_method<'b>( + &self, + state: &'b State<Renderer::Paragraph>, + layout: Layout<'_>, + value: &Value, + ) -> InputMethod<&'b str> { + let Some(Focus { + is_window_focused: true, + .. + }) = &state.is_focused + else { + return InputMethod::Disabled; + }; + + let secure_value = self.is_secure.then(|| value.secure()); + let value = secure_value.as_ref().unwrap_or(value); + + let text_bounds = layout.children().next().unwrap().bounds(); + + 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 (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 x = (text_bounds.x + cursor_x).floor() - scroll_offset + + alignment_offset; + + InputMethod::Enabled { + position: Point::new(x, text_bounds.y + text_bounds.height), + purpose: if self.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. /// @@ -529,7 +578,13 @@ where }; let draw = |renderer: &mut Renderer, viewport| { - let paragraph = if text.is_empty() { + let paragraph = if text.is_empty() + && state + .preedit + .as_ref() + .map(|preedit| preedit.content.is_empty()) + .unwrap_or(true) + { state.placeholder.raw() } else { state.value.raw() @@ -639,7 +694,7 @@ where fn update( &mut self, tree: &mut Tree, - event: Event, + event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, @@ -1197,6 +1252,52 @@ where state.keyboard_modifiers = *modifiers; } + Event::InputMethod(event) => match event { + input_method::Event::Opened | input_method::Event::Closed => { + let state = state::<Renderer>(tree); + + state.preedit = + matches!(event, input_method::Event::Opened) + .then(input_method::Preedit::new); + + shell.request_redraw(); + } + input_method::Event::Preedit(content, selection) => { + let state = state::<Renderer>(tree); + + if state.is_focused.is_some() { + state.preedit = Some(input_method::Preedit { + content: content.to_owned(), + selection: selection.clone(), + text_size: self.size, + }); + + shell.request_redraw(); + } + } + input_method::Event::Commit(text) => { + let state = state::<Renderer>(tree); + + if let Some(focus) = &mut state.is_focused { + let Some(on_input) = &self.on_input else { + return; + }; + + 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; + + 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::<Renderer>(tree); @@ -1218,23 +1319,30 @@ where let state = state::<Renderer>(tree); if let Some(focus) = &mut state.is_focused { - if focus.is_window_focused - && matches!( + if focus.is_window_focused { + if matches!( state.cursor.state(&self.value), cursor::State::Index(_) - ) - { - focus.now = *now; - - let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS - - (*now - focus.updated_at).as_millis() - % CURSOR_BLINK_INTERVAL_MILLIS; - - shell.request_redraw_at( - *now + Duration::from_millis( - millis_until_redraw as u64, - ), - ); + ) { + focus.now = *now; + + let millis_until_redraw = + CURSOR_BLINK_INTERVAL_MILLIS + - (*now - focus.updated_at).as_millis() + % CURSOR_BLINK_INTERVAL_MILLIS; + + shell.request_redraw_at( + *now + Duration::from_millis( + millis_until_redraw as u64, + ), + ); + } + + shell.request_input_method(&self.input_method( + state, + layout, + &self.value, + )); } } } @@ -1419,6 +1527,7 @@ pub struct State<P: text::Paragraph> { is_focused: Option<Focus>, is_dragging: bool, is_pasting: Option<Value>, + preedit: Option<input_method::Preedit>, last_click: Option<mouse::Click>, cursor: Cursor, keyboard_modifiers: keyboard::Modifiers, @@ -1431,7 +1540,7 @@ fn state<Renderer: text::Renderer>( tree.state.downcast_mut::<State<Renderer::Paragraph>>() } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] struct Focus { updated_at: Instant, now: Instant, @@ -1614,7 +1723,7 @@ fn replace_paragraph<Renderer>( bounds: Size::new(f32::INFINITY, text_bounds.height), size: text_size, horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, + vertical_alignment: alignment::Vertical::Center, shaping: text::Shaping::Advanced, wrapping: text::Wrapping::default(), }); |