Draft input_method support

This commit is contained in:
KENZ 2025-01-10 07:12:31 +09:00 committed by Héctor Ramón Jiménez
parent 599d8b560b
commit 7db5256b72
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
13 changed files with 420 additions and 27 deletions

View file

@ -564,6 +564,7 @@ where
}
}
}
shell.update_caret_info(local_shell.caret_info());
// Then finally react to them here
for message in local_messages {
@ -742,6 +743,8 @@ where
published_message_to_shell = true;
// Unfocus the input
let mut local_messages = Vec::new();
let mut local_shell = Shell::new(&mut local_messages);
self.text_input.update(
&mut tree.children[0],
Event::Mouse(mouse::Event::ButtonPressed(
@ -751,9 +754,10 @@ where
mouse::Cursor::Unavailable,
renderer,
clipboard,
&mut Shell::new(&mut vec![]),
&mut local_shell,
viewport,
);
shell.update_caret_info(local_shell.caret_info());
}
});

View file

@ -355,6 +355,7 @@ where
}
}
}
shell.update_caret_info(local_shell.caret_info());
if !local_messages.is_empty() {
let mut heads = self.state.take().unwrap().into_heads();
@ -640,6 +641,7 @@ where
}
}
}
shell.update_caret_info(local_shell.caret_info());
if !local_messages.is_empty() {
let mut inner =

View file

@ -33,8 +33,9 @@ use crate::core::widget::operation::{self, Operation};
use crate::core::widget::tree::{self, Tree};
use crate::core::window;
use crate::core::{
self, Background, Clipboard, Color, Element, Event, Layout, Length,
Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
self, Background, CaretInfo, Clipboard, Color, Element, Event, Layout,
Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector,
Widget,
};
use crate::runtime::task::{self, Task};
use crate::runtime::Action;
@ -729,6 +730,7 @@ where
let translation =
state.translation(self.direction, bounds, content_bounds);
let children_may_have_caret = shell.caret_info().is_none();
self.content.as_widget_mut().update(
&mut tree.children[0],
event.clone(),
@ -743,6 +745,19 @@ where
..bounds
},
);
if children_may_have_caret {
if let Some(caret_info) = shell.caret_info() {
shell.update_caret_info(Some(CaretInfo {
position: Point::new(
caret_info.position.x - translation.x,
caret_info.position.y - translation.y,
),
input_method_allowed: caret_info
.input_method_allowed,
}));
}
}
};
if matches!(

View file

@ -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,8 +47,8 @@ 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, CaretInfo, Color, Element, Event, Length, Padding,
Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector,
};
use std::borrow::Cow;
@ -322,6 +323,46 @@ where
self.class = class.into();
self
}
fn caret_rect(
&self,
tree: &widget::Tree,
renderer: &Renderer,
layout: Layout<'_>,
) -> Option<Rectangle> {
let bounds = layout.bounds();
let internal = self.content.0.borrow_mut();
let state = tree.state.downcast_ref::<State<Highlighter>>();
let text_bounds = bounds.shrink(self.padding);
let translation = text_bounds.position() - Point::ORIGIN;
if let Some(_) = state.focus.as_ref() {
let position = match internal.editor.cursor() {
Cursor::Caret(position) => position,
Cursor::Selection(ranges) => ranges
.first()
.cloned()
.unwrap_or(Rectangle::default())
.position(),
};
Some(Rectangle::new(
position + translation,
Size::new(
1.0,
self.line_height
.to_absolute(
self.text_size
.unwrap_or_else(|| renderer.default_size()),
)
.into(),
),
))
} else {
None
}
}
}
/// The content of a [`TextEditor`].
@ -605,7 +646,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 +742,11 @@ where
}));
shell.capture_event();
}
Update::Commit(text) => {
shell.publish(on_edit(Action::Edit(Edit::Paste(
Arc::new(text),
))));
}
Update::Binding(binding) => {
fn apply_binding<
H: text::Highlighter,
@ -825,6 +871,19 @@ where
}
};
shell.update_caret_info(if state.is_focused() {
let rect = self
.caret_rect(tree, renderer, layout)
.unwrap_or(Rectangle::default());
let bottom_left = Point::new(rect.x, rect.y + rect.height);
Some(CaretInfo {
position: bottom_left,
input_method_allowed: true,
})
} else {
None
});
if is_redraw {
self.last_status = Some(status);
} else if self
@ -1129,6 +1188,7 @@ enum Update<Message> {
Drag(Point),
Release,
Scroll(f32),
Commit(String),
Binding(Binding<Message>),
}
@ -1191,6 +1251,9 @@ impl<Message> Update<Message> {
}
_ => None,
},
Event::InputMethod(input_method::Event::Commit(text)) => {
Some(Update::Commit(text))
}
Event::Keyboard(keyboard::Event::KeyPressed {
key,
modifiers,

View file

@ -36,6 +36,7 @@ mod value;
pub mod cursor;
pub use cursor::Cursor;
use iced_runtime::core::input_method;
pub use value::Value;
use editor::Editor;
@ -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, CaretInfo, Color, Element, Event, Layout, Length,
Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
};
use crate::runtime::task::{self, Task};
use crate::runtime::Action;
@ -391,6 +392,58 @@ where
}
}
fn caret_rect(
&self,
tree: &Tree,
layout: Layout<'_>,
value: Option<&Value>,
) -> Option<Rectangle> {
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
let value = value.unwrap_or(&self.value);
let secure_value = self.is_secure.then(|| value.secure());
let value = secure_value.as_ref().unwrap_or(value);
let mut children_layout = layout.children();
let text_bounds = children_layout.next().unwrap().bounds();
if let Some(_) = state
.is_focused
.as_ref()
.filter(|focus| focus.is_window_focused)
{
let caret_index = match state.cursor.state(value) {
cursor::State::Index(position) => position,
cursor::State::Selection { start, end } => {
let left = start.min(end);
left
}
};
let text = state.value.raw();
let (caret_x, 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 + caret_x).floor();
Some(Rectangle {
x: (alignment_offset - offset) + x,
y: text_bounds.y,
width: 1.0,
height: text_bounds.height,
})
} else {
None
}
}
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
/// [`Value`] if provided.
///
@ -1197,6 +1250,31 @@ where
state.keyboard_modifiers = *modifiers;
}
Event::InputMethod(input_method::Event::Commit(string)) => {
let state = state::<Renderer>(tree);
if let Some(focus) = &mut state.is_focused {
let Some(on_input) = &self.on_input else {
return;
};
state.is_pasting = None;
let mut editor =
Editor::new(&mut self.value, &mut state.cursor);
editor.paste(Value::new(&string));
let message = (on_input)(editor.contents());
shell.publish(message);
focus.updated_at = Instant::now();
update_cache(state, &self.value);
shell.capture_event();
}
}
Event::Window(window::Event::Unfocused) => {
let state = state::<Renderer>(tree);
@ -1256,6 +1334,19 @@ where
Status::Active
};
shell.update_caret_info(if state.is_focused() {
let rect = self
.caret_rect(tree, layout, Some(&self.value))
.unwrap_or(Rectangle::with_size(Size::<f32>::default()));
let bottom_left = Point::new(rect.x, rect.y + rect.height);
Some(CaretInfo {
position: bottom_left,
input_method_allowed: true,
})
} else {
None
});
if let Event::Window(window::Event::RedrawRequested(_now)) = event {
self.last_status = Some(status);
} else if self