Refactor and simplify input_method API
This commit is contained in:
parent
d5ee9c2795
commit
ae10adda74
19 changed files with 540 additions and 472 deletions
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
self.redraw_request = 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));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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(|| {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
let cursor = match internal.editor.cursor() {
|
||||
Cursor::Caret(position) => position,
|
||||
Cursor::Selection(ranges) => ranges
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap_or(Rectangle::default())
|
||||
.position(),
|
||||
Cursor::Selection(ranges) => {
|
||||
ranges.first().cloned().unwrap_or_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 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) => {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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,21 +417,14 @@ 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 text = state.value.raw();
|
||||
let (caret_x, offset) = measure_cursor_and_scroll_offset(
|
||||
text,
|
||||
text_bounds,
|
||||
caret_index,
|
||||
);
|
||||
let (cursor_x, scroll_offset) =
|
||||
measure_cursor_and_scroll_offset(text, text_bounds, caret_index);
|
||||
|
||||
let alignment_offset = alignment_offset(
|
||||
text_bounds.width,
|
||||
|
|
@ -429,16 +432,17 @@ where
|
|||
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,
|
||||
})
|
||||
InputMethod::Open {
|
||||
position: Point::new(x, text_bounds.y),
|
||||
purpose: if self.is_secure {
|
||||
input_method::Purpose::Secure
|
||||
} else {
|
||||
None
|
||||
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,7 +1253,24 @@ where
|
|||
|
||||
state.keyboard_modifiers = *modifiers;
|
||||
}
|
||||
Event::InputMethod(input_method::Event::Commit(text)) => {
|
||||
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 {
|
||||
focus.is_ime_open =
|
||||
matches!(event, input_method::Event::Opened)
|
||||
.then(String::new);
|
||||
}
|
||||
}
|
||||
input_method::Event::Preedit(content, _range) => {
|
||||
let state = state::<Renderer>(tree);
|
||||
|
||||
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);
|
||||
|
||||
if let Some(focus) = &mut state.is_focused {
|
||||
|
|
@ -1270,6 +1292,7 @@ where
|
|||
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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
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(s) => input_method::Event::Commit(s),
|
||||
Ime::Disabled => input_method::Event::Disabled,
|
||||
}))
|
||||
}
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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,28 +874,14 @@ 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 => {}
|
||||
window.request_redraw(redraw_request);
|
||||
window.request_input_method(input_method);
|
||||
}
|
||||
|
||||
if let Some(caret_info) = caret_info {
|
||||
update_input_method(
|
||||
window,
|
||||
&mut preedit,
|
||||
&caret_info,
|
||||
);
|
||||
}
|
||||
}
|
||||
window.draw_preedit();
|
||||
|
||||
debug.render_started();
|
||||
match compositor.present(
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue