summaryrefslogtreecommitdiffstats
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
parentd5ee9c27955e6dfeb645e2641f3d24b006685484 (diff)
downloadiced-ae10adda74320e8098bfeb401f12a278e1e7b3e2.tar.gz
iced-ae10adda74320e8098bfeb401f12a278e1e7b3e2.tar.bz2
iced-ae10adda74320e8098bfeb401f12a278e1e7b3e2.zip
Refactor and simplify `input_method` API
-rw-r--r--core/src/input_method.rs129
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/shell.rs78
-rw-r--r--core/src/text/paragraph.rs6
-rw-r--r--core/src/window/redraw_request.rs12
-rw-r--r--runtime/src/user_interface.rs43
-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
-rw-r--r--winit/src/conversion.rs36
-rw-r--r--winit/src/program.rs156
-rw-r--r--winit/src/program/state.rs12
-rw-r--r--winit/src/program/window_manager.rs145
19 files changed, 538 insertions, 470 deletions
diff --git a/core/src/input_method.rs b/core/src/input_method.rs
index d282d404..c293582d 100644
--- a/core/src/input_method.rs
+++ b/core/src/input_method.rs
@@ -1,17 +1,112 @@
//! Listen to input method events.
+use crate::Point;
+
use std::ops::Range;
+/// The input method strategy of a widget.
+#[derive(Debug, Clone, PartialEq)]
+pub enum InputMethod<T = String> {
+ /// No input method is allowed.
+ Disabled,
+ /// Input methods are allowed, but not open yet.
+ Allowed,
+ /// Input method is open.
+ Open {
+ /// The position at which the input method dialog should be placed.
+ position: Point,
+ /// The [`Purpose`] of the input method.
+ purpose: Purpose,
+ /// The preedit to overlay on top of the input method dialog, if needed.
+ ///
+ /// Ideally, your widget will show pre-edits on-the-spot; but, since that can
+ /// be tricky, you can instead provide the current pre-edit here and the
+ /// runtime will display it as an overlay (i.e. "Over-the-spot IME").
+ preedit: Option<T>,
+ },
+}
+
+/// The purpose of an [`InputMethod`].
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum Purpose {
+ /// No special hints for the IME (default).
+ #[default]
+ Normal,
+ /// The IME is used for secure input (e.g. passwords).
+ Secure,
+ /// The IME is used to input into a terminal.
+ ///
+ /// For example, that could alter OSK on Wayland to show extra buttons.
+ Terminal,
+}
+
+impl InputMethod {
+ /// Merges two [`InputMethod`] strategies, prioritizing the second one when both ready:
+ /// ```
+ /// # use iced_core::input_method::{InputMethod, Purpose};
+ /// # use iced_core::Point;
+ ///
+ /// let open = InputMethod::Open {
+ /// position: Point::ORIGIN,
+ /// purpose: Purpose::Normal,
+ /// preedit: None,
+ /// };
+ ///
+ /// let open_2 = InputMethod::Open {
+ /// position: Point::ORIGIN,
+ /// purpose: Purpose::Secure,
+ /// preedit: None,
+ /// };
+ ///
+ /// let mut ime = InputMethod::Disabled;
+ ///
+ /// ime.merge(&InputMethod::<String>::Allowed);
+ /// assert_eq!(ime, InputMethod::Allowed);
+ ///
+ /// ime.merge(&InputMethod::<String>::Disabled);
+ /// assert_eq!(ime, InputMethod::Allowed);
+ ///
+ /// ime.merge(&open);
+ /// assert_eq!(ime, open);
+ ///
+ /// ime.merge(&open_2);
+ /// assert_eq!(ime, open_2);
+ /// ```
+ pub fn merge<T: AsRef<str>>(&mut self, other: &InputMethod<T>) {
+ match other {
+ InputMethod::Disabled => {}
+ InputMethod::Open {
+ position,
+ purpose,
+ preedit,
+ } => {
+ *self = Self::Open {
+ position: *position,
+ purpose: *purpose,
+ preedit: preedit
+ .as_ref()
+ .map(AsRef::as_ref)
+ .map(str::to_owned),
+ };
+ }
+ InputMethod::Allowed if matches!(self, Self::Disabled) => {
+ *self = Self::Allowed;
+ }
+ InputMethod::Allowed => {}
+ }
+ }
+}
+
/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events.
///
/// This is also called a "composition event".
///
/// Most keypresses using a latin-like keyboard layout simply generate a
-/// [`WindowEvent::KeyboardInput`]. However, one couldn't possibly have a key for every single
-/// unicode character that the user might want to type
-/// - so the solution operating systems employ is to allow the user to type these using _a sequence
-/// of keypresses_ instead.
+/// [`keyboard::Event::KeyPressed`](crate::keyboard::Event::KeyPressed).
+/// However, one couldn't possibly have a key for every single
+/// unicode character that the user might want to type. The solution operating systems employ is
+/// to allow the user to type these using _a sequence of keypresses_ instead.
///
-/// A prominent example of this is accents - many keyboard layouts allow you to first click the
+/// A prominent example of this is accents—many keyboard layouts allow you to first click the
/// "accent key", and then the character you want to apply the accent to. In this case, some
/// platforms will generate the following event sequence:
///
@@ -19,13 +114,13 @@ use std::ops::Range;
/// // Press "`" key
/// Ime::Preedit("`", Some((0, 0)))
/// // Press "E" key
-/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit.
+/// Ime::Preedit("", None) // Synthetic event generated to clear preedit.
/// Ime::Commit("é")
/// ```
///
/// Additionally, certain input devices are configured to display a candidate box that allow the
/// user to select the desired character interactively. (To properly position this box, you must use
-/// [`Window::set_ime_cursor_area`].)
+/// [`Shell::request_input_method`](crate::Shell::request_input_method).)
///
/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the
/// following event sequence could be obtained:
@@ -40,17 +135,19 @@ use std::ops::Range;
/// // Press space key
/// Ime::Preedit("啊b", Some((3, 3)))
/// // Press space key
-/// Ime::Preedit("", None) // Synthetic event generated by winit to clear preedit.
+/// Ime::Preedit("", None) // Synthetic event generated to clear preedit.
/// Ime::Commit("啊不")
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Event {
- /// Notifies when the IME was enabled.
+ /// Notifies when the IME was opened.
///
/// After getting this event you could receive [`Preedit`][Self::Preedit] and
/// [`Commit`][Self::Commit] events. You should also start performing IME related requests
- /// like [`Window::set_ime_cursor_area`].
- Enabled,
+ /// like [`Shell::request_input_method`].
+ ///
+ /// [`Shell::request_input_method`]: crate::Shell::request_input_method
+ Opened,
/// Notifies when a new composing text should be set at the cursor position.
///
@@ -63,14 +160,16 @@ pub enum Event {
/// Notifies when text should be inserted into the editor widget.
///
- /// Right before this event winit will send empty [`Self::Preedit`] event.
+ /// Right before this event, an empty [`Self::Preedit`] event will be issued.
Commit(String),
/// Notifies when the IME was disabled.
///
/// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or
- /// [`Commit`][Self::Commit] events until the next [`Enabled`][Self::Enabled] event. You should
- /// also stop issuing IME related requests like [`Window::set_ime_cursor_area`] and clear
+ /// [`Commit`][Self::Commit] events until the next [`Opened`][Self::Opened] event. You should
+ /// also stop issuing IME related requests like [`Shell::request_input_method`] and clear
/// pending preedit text.
- Disabled,
+ ///
+ /// [`Shell::request_input_method`]: crate::Shell::request_input_method
+ Closed,
}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index c7c38044..c31a8da7 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -62,6 +62,7 @@ pub use event::Event;
pub use font::Font;
pub use gradient::Gradient;
pub use image::Image;
+pub use input_method::InputMethod;
pub use layout::Layout;
pub use length::Length;
pub use overlay::Overlay;
@@ -73,7 +74,6 @@ pub use renderer::Renderer;
pub use rotation::Rotation;
pub use settings::Settings;
pub use shadow::Shadow;
-pub use shell::CaretInfo;
pub use shell::Shell;
pub use size::Size;
pub use svg::Svg;
diff --git a/core/src/shell.rs b/core/src/shell.rs
index d2c1b9ec..e87d1696 100644
--- a/core/src/shell.rs
+++ b/core/src/shell.rs
@@ -1,15 +1,6 @@
-use crate::time::Instant;
+use crate::event;
use crate::window;
-use crate::{event, Point};
-
-/// TODO
-#[derive(Clone, Copy, Debug)]
-pub struct CaretInfo {
- /// TODO
- pub position: Point,
- /// TODO
- pub input_method_allowed: bool,
-}
+use crate::InputMethod;
/// A connection to the state of a shell.
///
@@ -21,10 +12,10 @@ pub struct CaretInfo {
pub struct Shell<'a, Message> {
messages: &'a mut Vec<Message>,
event_status: event::Status,
- redraw_request: Option<window::RedrawRequest>,
+ redraw_request: window::RedrawRequest,
+ input_method: InputMethod,
is_layout_invalid: bool,
are_widgets_invalid: bool,
- caret_info: Option<CaretInfo>,
}
impl<'a, Message> Shell<'a, Message> {
@@ -33,10 +24,10 @@ impl<'a, Message> Shell<'a, Message> {
Self {
messages,
event_status: event::Status::Ignored,
- redraw_request: None,
+ redraw_request: window::RedrawRequest::Wait,
is_layout_invalid: false,
are_widgets_invalid: false,
- caret_info: None,
+ input_method: InputMethod::Disabled,
}
}
@@ -70,35 +61,38 @@ impl<'a, Message> Shell<'a, Message> {
/// Requests a new frame to be drawn as soon as possible.
pub fn request_redraw(&mut self) {
- self.redraw_request = Some(window::RedrawRequest::NextFrame);
- }
-
- /// Requests a new frame to be drawn at the given [`Instant`].
- pub fn request_redraw_at(&mut self, at: Instant) {
- match self.redraw_request {
- None => {
- self.redraw_request = Some(window::RedrawRequest::At(at));
- }
- Some(window::RedrawRequest::At(current)) if at < current => {
- self.redraw_request = Some(window::RedrawRequest::At(at));
- }
- _ => {}
- }
+ self.redraw_request = window::RedrawRequest::NextFrame;
+ }
+
+ /// Requests a new frame to be drawn at the given [`window::RedrawRequest`].
+ pub fn request_redraw_at(
+ &mut self,
+ redraw_request: impl Into<window::RedrawRequest>,
+ ) {
+ self.redraw_request = self.redraw_request.min(redraw_request.into());
}
/// Returns the request a redraw should happen, if any.
- pub fn redraw_request(&self) -> Option<window::RedrawRequest> {
+ pub fn redraw_request(&self) -> window::RedrawRequest {
self.redraw_request
}
- /// TODO
- pub fn update_caret_info(&mut self, caret_info: Option<CaretInfo>) {
- self.caret_info = caret_info.or(self.caret_info);
+ /// Requests the current [`InputMethod`] strategy.
+ pub fn request_input_method<T: AsRef<str>>(
+ &mut self,
+ ime: &InputMethod<T>,
+ ) {
+ self.input_method.merge(ime);
}
- /// TODO
- pub fn caret_info(&self) -> Option<CaretInfo> {
- self.caret_info
+ /// Returns the current [`InputMethod`] strategy.
+ pub fn input_method(&self) -> &InputMethod {
+ &self.input_method
+ }
+
+ /// Returns the current [`InputMethod`] strategy.
+ pub fn input_method_mut(&mut self) -> &mut InputMethod {
+ &mut self.input_method
}
/// Returns whether the current layout is invalid or not.
@@ -143,22 +137,14 @@ impl<'a, Message> Shell<'a, Message> {
pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) {
self.messages.extend(other.messages.drain(..).map(f));
- if let Some(new) = other.redraw_request {
- self.redraw_request = Some(
- self.redraw_request
- .map(|current| if current < new { current } else { new })
- .unwrap_or(new),
- );
- }
-
- self.update_caret_info(other.caret_info());
-
self.is_layout_invalid =
self.is_layout_invalid || other.is_layout_invalid;
self.are_widgets_invalid =
self.are_widgets_invalid || other.are_widgets_invalid;
+ self.redraw_request = self.redraw_request.min(other.redraw_request);
self.event_status = self.event_status.merge(other.event_status);
+ self.input_method.merge(&other.input_method);
}
}
diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs
index 924276c3..700c2c75 100644
--- a/core/src/text/paragraph.rs
+++ b/core/src/text/paragraph.rs
@@ -129,6 +129,12 @@ impl<P: Paragraph> Plain<P> {
self.raw.min_width()
}
+ /// Returns the minimum height that can fit the contents of the
+ /// [`Paragraph`].
+ pub fn min_height(&self) -> f32 {
+ self.raw.min_height()
+ }
+
/// Returns the cached [`Paragraph`].
pub fn raw(&self) -> &P {
&self.raw
diff --git a/core/src/window/redraw_request.rs b/core/src/window/redraw_request.rs
index b0c000d6..0ae4face 100644
--- a/core/src/window/redraw_request.rs
+++ b/core/src/window/redraw_request.rs
@@ -8,6 +8,15 @@ pub enum RedrawRequest {
/// Redraw at the given time.
At(Instant),
+
+ /// No redraw is needed.
+ Wait,
+}
+
+impl From<Instant> for RedrawRequest {
+ fn from(time: Instant) -> Self {
+ Self::At(time)
+ }
}
#[cfg(test)]
@@ -34,5 +43,8 @@ mod tests {
assert!(RedrawRequest::At(now) <= RedrawRequest::At(now));
assert!(RedrawRequest::At(now) <= RedrawRequest::At(later));
assert!(RedrawRequest::At(later) >= RedrawRequest::At(now));
+
+ assert!(RedrawRequest::Wait > RedrawRequest::NextFrame);
+ assert!(RedrawRequest::Wait > RedrawRequest::At(later));
}
}
diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs
index 6d3360d0..59e497c5 100644
--- a/runtime/src/user_interface.rs
+++ b/runtime/src/user_interface.rs
@@ -6,7 +6,7 @@ use crate::core::renderer;
use crate::core::widget;
use crate::core::window;
use crate::core::{
- CaretInfo, Clipboard, Element, Layout, Rectangle, Shell, Size, Vector,
+ Clipboard, Element, InputMethod, Layout, Rectangle, Shell, Size, Vector,
};
use crate::overlay;
@@ -188,8 +188,8 @@ where
use std::mem::ManuallyDrop;
let mut outdated = false;
- let mut redraw_request = None;
- let mut caret_info = None;
+ let mut redraw_request = window::RedrawRequest::Wait;
+ let mut input_method = InputMethod::Disabled;
let mut manual_overlay = ManuallyDrop::new(
self.root
@@ -223,17 +223,8 @@ where
);
event_statuses.push(shell.event_status());
-
- match (redraw_request, shell.redraw_request()) {
- (None, Some(at)) => {
- redraw_request = Some(at);
- }
- (Some(current), Some(new)) if new < current => {
- redraw_request = Some(new);
- }
- _ => {}
- }
- caret_info = caret_info.or(shell.caret_info());
+ redraw_request = redraw_request.min(shell.redraw_request());
+ input_method.merge(shell.input_method());
if shell.is_layout_invalid() {
let _ = ManuallyDrop::into_inner(manual_overlay);
@@ -327,16 +318,8 @@ where
self.overlay = None;
}
- match (redraw_request, shell.redraw_request()) {
- (None, Some(at)) => {
- redraw_request = Some(at);
- }
- (Some(current), Some(new)) if new < current => {
- redraw_request = Some(new);
- }
- _ => {}
- }
- caret_info = caret_info.or(shell.caret_info());
+ redraw_request = redraw_request.min(shell.redraw_request());
+ input_method.merge(shell.input_method());
shell.revalidate_layout(|| {
self.base = self.root.as_widget().layout(
@@ -362,7 +345,7 @@ where
} else {
State::Updated {
redraw_request,
- caret_info,
+ input_method,
}
},
event_statuses,
@@ -644,7 +627,7 @@ impl Default for Cache {
}
/// The resulting state after updating a [`UserInterface`].
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone)]
pub enum State {
/// The [`UserInterface`] is outdated and needs to be rebuilt.
Outdated,
@@ -652,9 +635,9 @@ pub enum State {
/// The [`UserInterface`] is up-to-date and can be reused without
/// rebuilding.
Updated {
- /// The [`window::RedrawRequest`] when a redraw should be performed.
- redraw_request: Option<window::RedrawRequest>,
- /// TODO
- caret_info: Option<CaretInfo>,
+ /// The [`window::RedrawRequest`] describing when a redraw should be performed.
+ redraw_request: window::RedrawRequest,
+ /// The current [`InputMethod`] strategy of the user interface.
+ input_method: InputMethod,
},
}
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();
diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs
index c7f9aaaf..ab84afff 100644
--- a/winit/src/conversion.rs
+++ b/winit/src/conversion.rs
@@ -141,6 +141,7 @@ pub fn window_event(
scale_factor: f64,
modifiers: winit::keyboard::ModifiersState,
) -> Option<Event> {
+ use winit::event::Ime;
use winit::event::WindowEvent;
match event {
@@ -284,19 +285,15 @@ pub fn window_event(
self::modifiers(new_modifiers.state()),
)))
}
- WindowEvent::Ime(ime) => {
- use winit::event::Ime;
- println!("ime event: {:?}", ime);
- Some(Event::InputMethod(match ime {
- Ime::Enabled => input_method::Event::Enabled,
- Ime::Preedit(s, size) => input_method::Event::Preedit(
- s,
- size.map(|(start, end)| (start..end)),
- ),
- Ime::Commit(s) => input_method::Event::Commit(s),
- Ime::Disabled => input_method::Event::Disabled,
- }))
- }
+ WindowEvent::Ime(event) => Some(Event::InputMethod(match event {
+ Ime::Enabled => input_method::Event::Opened,
+ Ime::Preedit(content, size) => input_method::Event::Preedit(
+ content,
+ size.map(|(start, end)| (start..end)),
+ ),
+ Ime::Commit(content) => input_method::Event::Commit(content),
+ Ime::Disabled => input_method::Event::Closed,
+ })),
WindowEvent::Focused(focused) => Some(Event::Window(if focused {
window::Event::Focused
} else {
@@ -1174,7 +1171,7 @@ pub fn resize_direction(
}
}
-/// Converts some [`window::Icon`] into it's `winit` counterpart.
+/// Converts some [`window::Icon`] into its `winit` counterpart.
///
/// Returns `None` if there is an error during the conversion.
pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {
@@ -1183,6 +1180,17 @@ pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {
winit::window::Icon::from_rgba(pixels, size.width, size.height).ok()
}
+/// Convertions some [`input_method::Purpose`] to its `winit` counterpart.
+pub fn ime_purpose(
+ purpose: input_method::Purpose,
+) -> winit::window::ImePurpose {
+ match purpose {
+ input_method::Purpose::Normal => winit::window::ImePurpose::Normal,
+ input_method::Purpose::Secure => winit::window::ImePurpose::Password,
+ input_method::Purpose::Terminal => winit::window::ImePurpose::Terminal,
+ }
+}
+
// See: https://en.wikipedia.org/wiki/Private_Use_Areas
fn is_private_use(c: char) -> bool {
('\u{E000}'..='\u{F8FF}').contains(&c)
diff --git a/winit/src/program.rs b/winit/src/program.rs
index 688d6731..302bc6c3 100644
--- a/winit/src/program.rs
+++ b/winit/src/program.rs
@@ -3,8 +3,6 @@ mod state;
mod window_manager;
pub use state::State;
-use winit::dpi::LogicalPosition;
-use winit::dpi::LogicalSize;
use crate::conversion;
use crate::core;
@@ -581,8 +579,6 @@ async fn run_instance<P, C>(
let mut clipboard = Clipboard::unconnected();
let mut compositor_receiver: Option<oneshot::Receiver<_>> = None;
- let mut preedit = Preedit::<P>::new();
-
debug.startup_finished();
loop {
@@ -878,29 +874,15 @@ async fn run_instance<P, C>(
if let user_interface::State::Updated {
redraw_request,
- caret_info,
+ input_method,
} = ui_state
{
- match redraw_request {
- Some(window::RedrawRequest::NextFrame) => {
- window.raw.request_redraw();
- window.redraw_at = None;
- }
- Some(window::RedrawRequest::At(at)) => {
- window.redraw_at = Some(at);
- }
- None => {}
- }
-
- if let Some(caret_info) = caret_info {
- update_input_method(
- window,
- &mut preedit,
- &caret_info,
- );
- }
+ window.request_redraw(redraw_request);
+ window.request_input_method(input_method);
}
+ window.draw_preedit();
+
debug.render_started();
match compositor.present(
&mut window.renderer,
@@ -1048,31 +1030,14 @@ async fn run_instance<P, C>(
match ui_state {
user_interface::State::Updated {
redraw_request: _redraw_request,
- caret_info,
+ input_method,
} => {
#[cfg(not(
feature = "unconditional-rendering"
))]
- match _redraw_request {
- Some(
- window::RedrawRequest::NextFrame,
- ) => {
- window.raw.request_redraw();
- window.redraw_at = None;
- }
- Some(window::RedrawRequest::At(at)) => {
- window.redraw_at = Some(at);
- }
- None => {}
- }
+ window.request_redraw(_redraw_request);
- if let Some(caret_info) = caret_info {
- update_input_method(
- window,
- &mut preedit,
- &caret_info,
- );
- }
+ window.request_input_method(input_method);
}
user_interface::State::Outdated => {
uis_stale = true;
@@ -1165,111 +1130,6 @@ async fn run_instance<P, C>(
let _ = ManuallyDrop::into_inner(user_interfaces);
}
-fn update_input_method<P, C>(
- window: &mut crate::program::window_manager::Window<P, C>,
- preedit: &mut Preedit<P>,
- caret_info: &crate::core::CaretInfo,
-) where
- P: Program,
- C: Compositor<Renderer = P::Renderer> + 'static,
-{
- window.raw.set_ime_allowed(caret_info.input_method_allowed);
- window.raw.set_ime_cursor_area(
- LogicalPosition::new(caret_info.position.x, caret_info.position.y),
- LogicalSize::new(10, 10),
- );
-
- let text = window.state.preedit();
- if !text.is_empty() {
- preedit.update(text.as_str(), &window.renderer);
- preedit.fill(
- &mut window.renderer,
- window.state.text_color(),
- window.state.background_color(),
- caret_info.position,
- );
- }
-}
-
-struct Preedit<P: Program> {
- content: Option<<P::Renderer as core::text::Renderer>::Paragraph>,
-}
-
-impl<P: Program> Preedit<P> {
- fn new() -> Self {
- Self { content: None }
- }
-
- fn update(&mut self, text: &str, renderer: &P::Renderer) {
- use core::text::Paragraph as _;
- use core::text::Renderer as _;
-
- self.content = Some(
- <P::Renderer as core::text::Renderer>::Paragraph::with_text(
- core::Text::<&str, <P::Renderer as core::text::Renderer>::Font> {
- content: text,
- bounds: Size::INFINITY,
- size: renderer.default_size(),
- line_height: core::text::LineHeight::default(),
- font: renderer.default_font(),
- horizontal_alignment: core::alignment::Horizontal::Left,
- vertical_alignment: core::alignment::Vertical::Top, //Bottom,
- shaping: core::text::Shaping::Advanced,
- wrapping: core::text::Wrapping::None,
- },
- ),
- );
- }
-
- fn fill(
- &self,
- renderer: &mut P::Renderer,
- fore_color: core::Color,
- bg_color: core::Color,
- caret_position: Point,
- ) {
- use core::text::Paragraph as _;
- use core::text::Renderer as _;
- use core::Renderer as _;
-
- let Some(ref content) = self.content else {
- return;
- };
- if content.min_width() < 1.0 {
- return;
- }
-
- let top_left = Point::new(
- caret_position.x,
- caret_position.y - content.min_height(),
- );
- let bounds = core::Rectangle::new(top_left, content.min_bounds());
- renderer.with_layer(bounds, |renderer| {
- renderer.fill_quad(
- core::renderer::Quad {
- bounds,
- ..Default::default()
- },
- core::Background::Color(bg_color),
- );
-
- let underline = 2.;
- renderer.fill_quad(
- core::renderer::Quad {
- bounds: bounds.shrink(core::Padding {
- top: bounds.height - underline,
- ..Default::default()
- }),
- ..Default::default()
- },
- core::Background::Color(fore_color),
- );
-
- renderer.fill_paragraph(content, top_left, fore_color, bounds);
- });
- }
-}
-
/// Builds a window's [`UserInterface`] for the [`Program`].
fn build_user_interface<'a, P: Program>(
program: &'a P,
diff --git a/winit/src/program/state.rs b/winit/src/program/state.rs
index 6361d1ba..e883d04a 100644
--- a/winit/src/program/state.rs
+++ b/winit/src/program/state.rs
@@ -4,7 +4,7 @@ use crate::core::{Color, Size};
use crate::graphics::Viewport;
use crate::program::Program;
-use winit::event::{Ime, Touch, WindowEvent};
+use winit::event::{Touch, WindowEvent};
use winit::window::Window;
use std::fmt::{Debug, Formatter};
@@ -22,7 +22,6 @@ where
modifiers: winit::keyboard::ModifiersState,
theme: P::Theme,
style: theme::Style,
- preedit: String,
}
impl<P: Program> Debug for State<P>
@@ -74,7 +73,6 @@ where
modifiers: winit::keyboard::ModifiersState::default(),
theme,
style,
- preedit: String::default(),
}
}
@@ -138,11 +136,6 @@ where
self.style.text_color
}
- /// TODO
- pub fn preedit(&self) -> String {
- self.preedit.clone()
- }
-
/// Processes the provided window event and updates the [`State`] accordingly.
pub fn update(
&mut self,
@@ -186,9 +179,6 @@ where
WindowEvent::ModifiersChanged(new_modifiers) => {
self.modifiers = new_modifiers.state();
}
- WindowEvent::Ime(Ime::Preedit(text, _)) => {
- self.preedit = text.clone();
- }
#[cfg(feature = "debug")]
WindowEvent::KeyboardInput {
event:
diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs
index a3c991df..cd49a8b4 100644
--- a/winit/src/program/window_manager.rs
+++ b/winit/src/program/window_manager.rs
@@ -1,13 +1,20 @@
+use crate::conversion;
+use crate::core::alignment;
use crate::core::mouse;
+use crate::core::renderer;
+use crate::core::text;
use crate::core::theme;
use crate::core::time::Instant;
-use crate::core::window::Id;
-use crate::core::{Point, Size};
+use crate::core::window::{Id, RedrawRequest};
+use crate::core::{
+ Color, InputMethod, Padding, Point, Rectangle, Size, Text, Vector,
+};
use crate::graphics::Compositor;
use crate::program::{Program, State};
use std::collections::BTreeMap;
use std::sync::Arc;
+use winit::dpi::{LogicalPosition, LogicalSize};
use winit::monitor::MonitorHandle;
#[allow(missing_debug_implementations)]
@@ -65,6 +72,7 @@ where
renderer,
mouse_interaction: mouse::Interaction::None,
redraw_at: None,
+ preedit: None,
},
);
@@ -155,6 +163,7 @@ where
pub surface: C::Surface,
pub renderer: P::Renderer,
pub redraw_at: Option<Instant>,
+ preedit: Option<Preedit<P::Renderer>>,
}
impl<P, C> Window<P, C>
@@ -179,4 +188,136 @@ where
Size::new(size.width, size.height)
}
+
+ pub fn request_redraw(&mut self, redraw_request: RedrawRequest) {
+ match redraw_request {
+ RedrawRequest::NextFrame => {
+ self.raw.request_redraw();
+ self.redraw_at = None;
+ }
+ RedrawRequest::At(at) => {
+ self.redraw_at = Some(at);
+ }
+ RedrawRequest::Wait => {}
+ }
+ }
+
+ pub fn request_input_method(&mut self, input_method: InputMethod) {
+ self.raw.set_ime_allowed(match input_method {
+ InputMethod::Disabled => false,
+ InputMethod::Allowed | InputMethod::Open { .. } => true,
+ });
+
+ if let InputMethod::Open {
+ position,
+ purpose,
+ preedit,
+ } = input_method
+ {
+ self.raw.set_ime_cursor_area(
+ LogicalPosition::new(position.x, position.y),
+ LogicalSize::new(10, 10),
+ );
+
+ self.raw.set_ime_purpose(conversion::ime_purpose(purpose));
+
+ if let Some(content) = preedit {
+ if let Some(preedit) = &mut self.preedit {
+ preedit.update(&content, &self.renderer);
+ } else {
+ let mut preedit = Preedit::new();
+ preedit.update(&content, &self.renderer);
+
+ self.preedit = Some(preedit);
+ }
+ }
+ } else {
+ self.preedit = None;
+ }
+ }
+
+ pub fn draw_preedit(&mut self) {
+ if let Some(preedit) = &self.preedit {
+ preedit.draw(
+ &mut self.renderer,
+ self.state.text_color(),
+ self.state.background_color(),
+ );
+ }
+ }
+}
+
+struct Preedit<Renderer>
+where
+ Renderer: text::Renderer,
+{
+ position: Point,
+ content: text::paragraph::Plain<Renderer::Paragraph>,
+}
+
+impl<Renderer> Preedit<Renderer>
+where
+ Renderer: text::Renderer,
+{
+ fn new() -> Self {
+ Self {
+ position: Point::ORIGIN,
+ content: text::paragraph::Plain::default(),
+ }
+ }
+
+ fn update(&mut self, text: &str, renderer: &Renderer) {
+ self.content.update(Text {
+ content: text,
+ bounds: Size::INFINITY,
+ size: renderer.default_size(),
+ line_height: text::LineHeight::default(),
+ font: renderer.default_font(),
+ horizontal_alignment: alignment::Horizontal::Left,
+ vertical_alignment: alignment::Vertical::Top, //Bottom,
+ shaping: text::Shaping::Advanced,
+ wrapping: text::Wrapping::None,
+ });
+ }
+
+ fn draw(&self, renderer: &mut Renderer, color: Color, background: Color) {
+ if self.content.min_width() < 1.0 {
+ return;
+ }
+
+ let top_left =
+ self.position - Vector::new(0.0, self.content.min_height());
+
+ let bounds = Rectangle::new(top_left, self.content.min_bounds());
+
+ renderer.with_layer(bounds, |renderer| {
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds,
+ ..Default::default()
+ },
+ background,
+ );
+
+ renderer.fill_paragraph(
+ self.content.raw(),
+ top_left,
+ color,
+ bounds,
+ );
+
+ const UNDERLINE: f32 = 2.0;
+
+ renderer.fill_quad(
+ renderer::Quad {
+ bounds: bounds.shrink(Padding {
+ top: bounds.height - UNDERLINE,
+ ..Default::default()
+ }),
+ ..Default::default()
+ },
+ color,
+ );
+ });
+ }
}