summaryrefslogtreecommitdiffstats
path: root/widget
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2025-02-02 20:45:29 +0100
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2025-02-02 20:45:29 +0100
commitae10adda74320e8098bfeb401f12a278e1e7b3e2 (patch)
tree1827aabad023b06a6cb9dd6ec50093af969ecf0c /widget
parentd5ee9c27955e6dfeb645e2641f3d24b006685484 (diff)
downloadiced-ae10adda74320e8098bfeb401f12a278e1e7b3e2.tar.gz
iced-ae10adda74320e8098bfeb401f12a278e1e7b3e2.tar.bz2
iced-ae10adda74320e8098bfeb401f12a278e1e7b3e2.zip
Refactor and simplify `input_method` API
Diffstat (limited to 'widget')
-rw-r--r--widget/src/action.rs14
-rw-r--r--widget/src/canvas.rs15
-rw-r--r--widget/src/combo_box.rs16
-rw-r--r--widget/src/lazy/component.rs29
-rw-r--r--widget/src/pane_grid.rs2
-rw-r--r--widget/src/scrollable.rs18
-rw-r--r--widget/src/shader.rs14
-rw-r--r--widget/src/text_editor.rs133
-rw-r--r--widget/src/text_input.rs148
9 files changed, 186 insertions, 203 deletions
diff --git a/widget/src/action.rs b/widget/src/action.rs
index 1dd3a787..cc31e76a 100644
--- a/widget/src/action.rs
+++ b/widget/src/action.rs
@@ -6,7 +6,7 @@ use crate::core::window;
#[derive(Debug, Clone)]
pub struct Action<Message> {
message_to_publish: Option<Message>,
- redraw_request: Option<window::RedrawRequest>,
+ redraw_request: window::RedrawRequest,
event_status: event::Status,
}
@@ -14,7 +14,7 @@ impl<Message> Action<Message> {
fn new() -> Self {
Self {
message_to_publish: None,
- redraw_request: None,
+ redraw_request: window::RedrawRequest::Wait,
event_status: event::Status::Ignored,
}
}
@@ -46,7 +46,7 @@ impl<Message> Action<Message> {
/// soon as possible; without publishing any `Message`.
pub fn request_redraw() -> Self {
Self {
- redraw_request: Some(window::RedrawRequest::NextFrame),
+ redraw_request: window::RedrawRequest::NextFrame,
..Self::new()
}
}
@@ -58,7 +58,7 @@ impl<Message> Action<Message> {
/// blinking caret on a text input.
pub fn request_redraw_at(at: Instant) -> Self {
Self {
- redraw_request: Some(window::RedrawRequest::At(at)),
+ redraw_request: window::RedrawRequest::At(at),
..Self::new()
}
}
@@ -75,11 +75,7 @@ impl<Message> Action<Message> {
/// widget implementations.
pub fn into_inner(
self,
- ) -> (
- Option<Message>,
- Option<window::RedrawRequest>,
- event::Status,
- ) {
+ ) -> (Option<Message>, window::RedrawRequest, event::Status) {
(
self.message_to_publish,
self.redraw_request,
diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs
index 23cc3f2b..d10771f0 100644
--- a/widget/src/canvas.rs
+++ b/widget/src/canvas.rs
@@ -238,27 +238,18 @@ where
{
let (message, redraw_request, event_status) = action.into_inner();
+ shell.request_redraw_at(redraw_request);
+
if let Some(message) = message {
shell.publish(message);
}
- if let Some(redraw_request) = redraw_request {
- match redraw_request {
- window::RedrawRequest::NextFrame => {
- shell.request_redraw();
- }
- window::RedrawRequest::At(at) => {
- shell.request_redraw_at(at);
- }
- }
- }
-
if event_status == event::Status::Captured {
shell.capture_event();
}
}
- if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) {
+ if shell.redraw_request() != window::RedrawRequest::NextFrame {
let mouse_interaction = self
.mouse_interaction(tree, layout, cursor, viewport, renderer);
diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs
index d7c7c922..05793155 100644
--- a/widget/src/combo_box.rs
+++ b/widget/src/combo_box.rs
@@ -63,7 +63,6 @@ use crate::core::renderer;
use crate::core::text;
use crate::core::time::Instant;
use crate::core::widget::{self, Widget};
-use crate::core::window;
use crate::core::{
Clipboard, Element, Event, Length, Padding, Rectangle, Shell, Size, Theme,
Vector,
@@ -554,17 +553,8 @@ where
shell.capture_event();
}
- if let Some(redraw_request) = local_shell.redraw_request() {
- match redraw_request {
- window::RedrawRequest::NextFrame => {
- shell.request_redraw();
- }
- window::RedrawRequest::At(at) => {
- shell.request_redraw_at(at);
- }
- }
- }
- shell.update_caret_info(local_shell.caret_info());
+ shell.request_redraw_at(local_shell.redraw_request());
+ shell.request_input_method(local_shell.input_method());
// Then finally react to them here
for message in local_messages {
@@ -757,7 +747,7 @@ where
&mut local_shell,
viewport,
);
- shell.update_caret_info(local_shell.caret_info());
+ shell.request_input_method(local_shell.input_method());
}
});
diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs
index b9fbde58..c93b7c42 100644
--- a/widget/src/lazy/component.rs
+++ b/widget/src/lazy/component.rs
@@ -6,7 +6,6 @@ use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget;
use crate::core::widget::tree::{self, Tree};
-use crate::core::window;
use crate::core::{
self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector,
Widget,
@@ -344,18 +343,8 @@ where
}
local_shell.revalidate_layout(|| shell.invalidate_layout());
-
- if let Some(redraw_request) = local_shell.redraw_request() {
- match redraw_request {
- window::RedrawRequest::NextFrame => {
- shell.request_redraw();
- }
- window::RedrawRequest::At(at) => {
- shell.request_redraw_at(at);
- }
- }
- }
- shell.update_caret_info(local_shell.caret_info());
+ shell.request_redraw_at(local_shell.redraw_request());
+ shell.request_input_method(local_shell.input_method());
if !local_messages.is_empty() {
let mut heads = self.state.take().unwrap().into_heads();
@@ -630,18 +619,8 @@ where
}
local_shell.revalidate_layout(|| shell.invalidate_layout());
-
- if let Some(redraw_request) = local_shell.redraw_request() {
- match redraw_request {
- window::RedrawRequest::NextFrame => {
- shell.request_redraw();
- }
- window::RedrawRequest::At(at) => {
- shell.request_redraw_at(at);
- }
- }
- }
- shell.update_caret_info(local_shell.caret_info());
+ shell.request_redraw_at(local_shell.redraw_request());
+ shell.request_input_method(local_shell.input_method());
if !local_messages.is_empty() {
let mut inner =
diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs
index 5c3b343c..e972b983 100644
--- a/widget/src/pane_grid.rs
+++ b/widget/src/pane_grid.rs
@@ -687,7 +687,7 @@ where
_ => {}
}
- if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) {
+ if shell.redraw_request() != window::RedrawRequest::NextFrame {
let interaction = self
.grid_interaction(action, layout, cursor)
.or_else(|| {
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index 7df7a0e5..0a93584e 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -33,7 +33,7 @@ use crate::core::widget::operation::{self, Operation};
use crate::core::widget::tree::{self, Tree};
use crate::core::window;
use crate::core::{
- self, Background, CaretInfo, Clipboard, Color, Element, Event, Layout,
+ self, Background, Clipboard, Color, Element, Event, InputMethod, Layout,
Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector,
Widget,
};
@@ -730,7 +730,6 @@ 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(),
@@ -746,17 +745,10 @@ where
},
);
- 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 let InputMethod::Open { position, .. } =
+ shell.input_method_mut()
+ {
+ *position = *position + translation;
}
};
diff --git a/widget/src/shader.rs b/widget/src/shader.rs
index 8ec57482..48c96321 100644
--- a/widget/src/shader.rs
+++ b/widget/src/shader.rs
@@ -9,7 +9,6 @@ use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::widget::{self, Widget};
-use crate::core::window;
use crate::core::{Clipboard, Element, Event, Length, Rectangle, Shell, Size};
use crate::renderer::wgpu::primitive;
@@ -105,21 +104,12 @@ where
{
let (message, redraw_request, event_status) = action.into_inner();
+ shell.request_redraw_at(redraw_request);
+
if let Some(message) = message {
shell.publish(message);
}
- if let Some(redraw_request) = redraw_request {
- match redraw_request {
- window::RedrawRequest::NextFrame => {
- shell.request_redraw();
- }
- window::RedrawRequest::At(at) => {
- shell.request_redraw_at(at);
- }
- }
- }
-
if event_status == event::Status::Captured {
shell.capture_event();
}
diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs
index 529c8b90..4f985f28 100644
--- a/widget/src/text_editor.rs
+++ b/widget/src/text_editor.rs
@@ -47,7 +47,7 @@ use crate::core::widget::operation;
use crate::core::widget::{self, Widget};
use crate::core::window;
use crate::core::{
- Background, Border, CaretInfo, Color, Element, Event, Length, Padding,
+ Background, Border, Color, Element, Event, InputMethod, Length, Padding,
Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector,
};
@@ -324,43 +324,49 @@ where
self
}
- fn caret_rect(
+ fn input_method<'b>(
&self,
- tree: &widget::Tree,
+ state: &'b State<Highlighter>,
renderer: &Renderer,
layout: Layout<'_>,
- ) -> Option<Rectangle> {
- let bounds = layout.bounds();
+ ) -> InputMethod<&'b str> {
+ let Some(Focus {
+ is_window_focused: true,
+ is_ime_open,
+ ..
+ }) = &state.focus
+ else {
+ return InputMethod::Disabled;
+ };
+
+ let Some(preedit) = &is_ime_open else {
+ return InputMethod::Allowed;
+ };
+ 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 state.focus.is_some() {
- 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
+ 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),
}
}
}
@@ -499,11 +505,12 @@ pub struct State<Highlighter: text::Highlighter> {
highlighter_format_address: usize,
}
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone)]
struct Focus {
updated_at: Instant,
now: Instant,
is_window_focused: bool,
+ is_ime_open: Option<String>,
}
impl Focus {
@@ -516,6 +523,7 @@ impl Focus {
updated_at: now,
now,
is_window_focused: true,
+ is_ime_open: None,
}
}
@@ -742,11 +750,23 @@ where
}));
shell.capture_event();
}
- Update::Commit(text) => {
- shell.publish(on_edit(Action::Edit(Edit::Paste(
- Arc::new(text),
- ))));
- }
+ Update::InputMethod(update) => match update {
+ Ime::Toggle(is_open) => {
+ if let Some(focus) = &mut state.focus {
+ focus.is_ime_open = is_open.then(String::new);
+ }
+ }
+ Ime::Preedit(text) => {
+ if let Some(focus) = &mut state.focus {
+ focus.is_ime_open = Some(text);
+ }
+ }
+ Ime::Commit(text) => {
+ shell.publish(on_edit(Action::Edit(Edit::Paste(
+ Arc::new(text),
+ ))));
+ }
+ },
Update::Binding(binding) => {
fn apply_binding<
H: text::Highlighter,
@@ -871,22 +891,12 @@ where
}
};
- shell.update_caret_info(if state.is_focused() {
- let rect =
- self.caret_rect(tree, renderer, layout).unwrap_or_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);
+
+ shell.request_input_method(
+ &self.input_method(state, renderer, layout),
+ );
} else if self
.last_status
.is_some_and(|last_status| status != last_status)
@@ -1189,10 +1199,16 @@ enum Update<Message> {
Drag(Point),
Release,
Scroll(f32),
- Commit(String),
+ InputMethod(Ime),
Binding(Binding<Message>),
}
+enum Ime {
+ Toggle(bool),
+ Preedit(String),
+ Commit(String),
+}
+
impl<Message> Update<Message> {
fn from_event<H: Highlighter>(
event: Event,
@@ -1252,9 +1268,20 @@ impl<Message> Update<Message> {
}
_ => None,
},
- Event::InputMethod(input_method::Event::Commit(text)) => {
- Some(Update::Commit(text))
- }
+ 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, _range) => {
+ Some(Update::InputMethod(Ime::Preedit(content)))
+ }
+ input_method::Event::Commit(content) => {
+ Some(Update::InputMethod(Ime::Commit(content)))
+ }
+ },
Event::Keyboard(keyboard::Event::KeyPressed {
key,
modifiers,
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index ba5d1843..d0e93927 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -57,7 +57,7 @@ use crate::core::widget::operation::{self, Operation};
use crate::core::widget::tree::{self, Tree};
use crate::core::window;
use crate::core::{
- Background, Border, CaretInfo, Color, Element, Event, Layout, Length,
+ Background, Border, Color, Element, Event, InputMethod, Layout, Length,
Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
};
use crate::runtime::task::{self, Task};
@@ -392,14 +392,24 @@ where
}
}
- fn caret_rect(
+ fn input_method<'b>(
&self,
- tree: &Tree,
+ state: &'b State<Renderer::Paragraph>,
layout: Layout<'_>,
- value: Option<&Value>,
- ) -> Option<Rectangle> {
- let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
- let value = value.unwrap_or(&self.value);
+ value: &Value,
+ ) -> InputMethod<&'b str> {
+ let Some(Focus {
+ is_window_focused: true,
+ is_ime_open,
+ ..
+ }) = &state.is_focused
+ else {
+ return InputMethod::Disabled;
+ };
+
+ let Some(preedit) = is_ime_open else {
+ return InputMethod::Allowed;
+ };
let secure_value = self.is_secure.then(|| value.secure());
let value = secure_value.as_ref().unwrap_or(value);
@@ -407,38 +417,32 @@ where
let mut children_layout = layout.children();
let text_bounds = children_layout.next().unwrap().bounds();
- if state
- .is_focused
- .is_some_and(|focus| focus.is_window_focused)
- {
- let caret_index = match state.cursor.state(value) {
- cursor::State::Index(position) => position,
- cursor::State::Selection { start, end } => start.min(end),
- };
+ 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 (caret_x, offset) = measure_cursor_and_scroll_offset(
- text,
- text_bounds,
- caret_index,
- );
+ 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 alignment_offset = alignment_offset(
+ text_bounds.width,
+ text.min_width(),
+ self.alignment,
+ );
- let x = (text_bounds.x + caret_x).floor();
+ let x = (text_bounds.x + cursor_x).floor() - scroll_offset
+ + alignment_offset;
- Some(Rectangle {
- x: (alignment_offset - offset) + x,
- y: text_bounds.y,
- width: 1.0,
- height: text_bounds.height,
- })
- } else {
- None
+ InputMethod::Open {
+ position: Point::new(x, text_bounds.y),
+ purpose: if self.is_secure {
+ input_method::Purpose::Secure
+ } else {
+ input_method::Purpose::Normal
+ },
+ preedit: Some(preedit),
}
}
@@ -725,6 +729,7 @@ where
updated_at: now,
now,
is_window_focused: true,
+ is_ime_open: None,
})
} else {
None
@@ -1248,28 +1253,46 @@ where
state.keyboard_modifiers = *modifiers;
}
- Event::InputMethod(input_method::Event::Commit(text)) => {
- let state = state::<Renderer>(tree);
+ Event::InputMethod(event) => match event {
+ input_method::Event::Opened | input_method::Event::Closed => {
+ let state = state::<Renderer>(tree);
- if let Some(focus) = &mut state.is_focused {
- let Some(on_input) = &self.on_input else {
- return;
- };
+ if let Some(focus) = &mut state.is_focused {
+ focus.is_ime_open =
+ matches!(event, input_method::Event::Opened)
+ .then(String::new);
+ }
+ }
+ input_method::Event::Preedit(content, _range) => {
+ let state = state::<Renderer>(tree);
- let mut editor =
- Editor::new(&mut self.value, &mut state.cursor);
- editor.paste(Value::new(text));
+ if let Some(focus) = &mut state.is_focused {
+ focus.is_ime_open = Some(content.to_owned());
+ }
+ }
+ input_method::Event::Commit(text) => {
+ let state = state::<Renderer>(tree);
- focus.updated_at = Instant::now();
- state.is_pasting = None;
+ if let Some(focus) = &mut state.is_focused {
+ let Some(on_input) = &self.on_input else {
+ return;
+ };
- let message = (on_input)(editor.contents());
- shell.publish(message);
- shell.capture_event();
+ 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;
- update_cache(state, &self.value);
+ 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);
@@ -1329,21 +1352,14 @@ 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);
+
+ shell.request_input_method(&self.input_method(
+ state,
+ layout,
+ &self.value,
+ ));
} else if self
.last_status
.is_some_and(|last_status| status != last_status)
@@ -1517,11 +1533,12 @@ 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,
is_window_focused: bool,
+ is_ime_open: Option<String>,
}
impl<P: text::Paragraph> State<P> {
@@ -1548,6 +1565,7 @@ impl<P: text::Paragraph> State<P> {
updated_at: now,
now,
is_window_focused: true,
+ is_ime_open: None,
});
self.move_cursor_to_end();