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