summaryrefslogtreecommitdiffstats
path: root/widget
diff options
context:
space:
mode:
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.rs18
-rw-r--r--widget/src/lazy/component.rs27
-rw-r--r--widget/src/pane_grid.rs2
-rw-r--r--widget/src/scrollable.rs15
-rw-r--r--widget/src/shader.rs14
-rw-r--r--widget/src/text_editor.rs111
-rw-r--r--widget/src/text_input.rs111
9 files changed, 249 insertions, 78 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 500d2bec..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,16 +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.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 {
@@ -742,6 +733,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 +744,10 @@ where
mouse::Cursor::Unavailable,
renderer,
clipboard,
- &mut Shell::new(&mut vec![]),
+ &mut local_shell,
viewport,
);
+ shell.request_input_method(local_shell.input_method());
}
});
diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs
index 15b8b62e..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,17 +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.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();
@@ -629,17 +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.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 2dbe0479..a51a8625 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -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, Clipboard, 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;
@@ -728,6 +729,8 @@ where
_ => mouse::Cursor::Unavailable,
};
+ let had_input_method = shell.input_method().is_open();
+
let translation =
state.translation(self.direction, bounds, content_bounds);
@@ -745,6 +748,14 @@ where
..bounds
},
);
+
+ if !had_input_method {
+ if let InputMethod::Open { position, .. } =
+ shell.input_method_mut()
+ {
+ *position = *position + translation;
+ }
+ }
};
if matches!(
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 f1ec589b..486741c6 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,29 @@ 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 } => {
+ if state.focus.is_some() {
+ 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 +899,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 +1205,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 +1277,23 @@ 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) => {
+ Some(Update::InputMethod(Ime::Preedit {
+ content,
+ selection,
+ }))
+ }
+ 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 57ebe46a..b22ee1ca 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,58 @@ 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 Some(preedit) = &state.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);
+
+ 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::Open {
+ 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: Some(preedit.as_ref()),
+ }
+ }
+
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
/// [`Value`] if provided.
///
@@ -1197,6 +1250,51 @@ where
state.keyboard_modifiers = *modifiers;
}
+ Event::InputMethod(event) => match event {
+ input_method::Event::Opened | input_method::Event::Closed => {
+ let state = state::<Renderer>(tree);
+
+ state.is_ime_open =
+ 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.is_ime_open = Some(input_method::Preedit {
+ content: content.to_owned(),
+ selection: selection.clone(),
+ });
+
+ 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);
@@ -1258,6 +1356,12 @@ where
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)
@@ -1417,6 +1521,7 @@ pub struct State<P: text::Paragraph> {
placeholder: paragraph::Plain<P>,
icon: paragraph::Plain<P>,
is_focused: Option<Focus>,
+ is_ime_open: Option<input_method::Preedit>,
is_dragging: bool,
is_pasting: Option<Value>,
last_click: Option<mouse::Click>,
@@ -1431,7 +1536,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,