summaryrefslogtreecommitdiffstats
path: root/widget/src/text_input.rs
diff options
context:
space:
mode:
Diffstat (limited to 'widget/src/text_input.rs')
-rw-r--r--widget/src/text_input.rs151
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(),
});