Merge pull request #2777 from kenz-gelsoft/explore-input-method2
Input Method Support
This commit is contained in:
commit
5ab056318e
20 changed files with 848 additions and 166 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
//! Handle events of a user interface.
|
//! Handle events of a user interface.
|
||||||
|
use crate::input_method;
|
||||||
use crate::keyboard;
|
use crate::keyboard;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
|
|
@ -23,6 +24,9 @@ pub enum Event {
|
||||||
|
|
||||||
/// A touch event
|
/// A touch event
|
||||||
Touch(touch::Event),
|
Touch(touch::Event),
|
||||||
|
|
||||||
|
/// An input method event
|
||||||
|
InputMethod(input_method::Event),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The status of an [`Event`] after being processed.
|
/// The status of an [`Event`] after being processed.
|
||||||
|
|
|
||||||
235
core/src/input_method.rs
Normal file
235
core/src/input_method.rs
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
//! 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 strategy has been specified.
|
||||||
|
None,
|
||||||
|
/// 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<Preedit<T>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pre-edit of an [`InputMethod`].
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
|
pub struct Preedit<T = String> {
|
||||||
|
/// The current content.
|
||||||
|
pub content: T,
|
||||||
|
/// The selected range of the content.
|
||||||
|
pub selection: Option<Range<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Preedit<T> {
|
||||||
|
/// Creates a new empty [`Preedit`].
|
||||||
|
pub fn new() -> Self
|
||||||
|
where
|
||||||
|
T: Default,
|
||||||
|
{
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns a [`Preedit`] into its owned version.
|
||||||
|
pub fn to_owned(&self) -> Preedit
|
||||||
|
where
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
|
Preedit {
|
||||||
|
content: self.content.as_ref().to_owned(),
|
||||||
|
selection: self.selection.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Preedit {
|
||||||
|
/// Borrows the contents of a [`Preedit`].
|
||||||
|
pub fn as_ref(&self) -> Preedit<&str> {
|
||||||
|
Preedit {
|
||||||
|
content: &self.content,
|
||||||
|
selection: self.selection.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 first one when both open:
|
||||||
|
/// ```
|
||||||
|
/// # use iced_core::input_method::{InputMethod, Purpose, Preedit};
|
||||||
|
/// # use iced_core::Point;
|
||||||
|
///
|
||||||
|
/// let open = InputMethod::Open {
|
||||||
|
/// position: Point::ORIGIN,
|
||||||
|
/// purpose: Purpose::Normal,
|
||||||
|
/// preedit: Some(Preedit { content: "1".to_owned(), selection: None }),
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let open_2 = InputMethod::Open {
|
||||||
|
/// position: Point::ORIGIN,
|
||||||
|
/// purpose: Purpose::Secure,
|
||||||
|
/// preedit: Some(Preedit { content: "2".to_owned(), selection: 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);
|
||||||
|
/// ```
|
||||||
|
pub fn merge<T: AsRef<str>>(&mut self, other: &InputMethod<T>) {
|
||||||
|
match (&self, other) {
|
||||||
|
(InputMethod::Open { .. }, _)
|
||||||
|
| (
|
||||||
|
InputMethod::Allowed,
|
||||||
|
InputMethod::None | InputMethod::Disabled,
|
||||||
|
)
|
||||||
|
| (InputMethod::Disabled, InputMethod::None) => {}
|
||||||
|
_ => {
|
||||||
|
*self = other.to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the [`InputMethod`] is open.
|
||||||
|
pub fn is_open(&self) -> bool {
|
||||||
|
matches!(self, Self::Open { .. })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> InputMethod<T> {
|
||||||
|
/// Turns an [`InputMethod`] into its owned version.
|
||||||
|
pub fn to_owned(&self) -> InputMethod
|
||||||
|
where
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::None => InputMethod::None,
|
||||||
|
Self::Disabled => InputMethod::Disabled,
|
||||||
|
Self::Allowed => InputMethod::Allowed,
|
||||||
|
Self::Open {
|
||||||
|
position,
|
||||||
|
purpose,
|
||||||
|
preedit,
|
||||||
|
} => InputMethod::Open {
|
||||||
|
position: *position,
|
||||||
|
purpose: *purpose,
|
||||||
|
preedit: preedit.as_ref().map(Preedit::to_owned),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// [`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
|
||||||
|
/// "accent key", and then the character you want to apply the accent to. In this case, some
|
||||||
|
/// platforms will generate the following event sequence:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// // Press "`" key
|
||||||
|
/// Ime::Preedit("`", Some((0, 0)))
|
||||||
|
/// // Press "E" key
|
||||||
|
/// 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
|
||||||
|
/// [`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:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// // Press "A" key
|
||||||
|
/// Ime::Preedit("a", Some((1, 1)))
|
||||||
|
/// // Press "B" key
|
||||||
|
/// Ime::Preedit("a b", Some((3, 3)))
|
||||||
|
/// // Press left arrow key
|
||||||
|
/// Ime::Preedit("a b", Some((1, 1)))
|
||||||
|
/// // Press space key
|
||||||
|
/// Ime::Preedit("啊b", Some((3, 3)))
|
||||||
|
/// // Press space key
|
||||||
|
/// 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 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 [`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.
|
||||||
|
///
|
||||||
|
/// The value represents a pair of the preedit string and the cursor begin position and end
|
||||||
|
/// position. When it's `None`, the cursor should be hidden. When `String` is an empty string
|
||||||
|
/// this indicates that preedit was cleared.
|
||||||
|
///
|
||||||
|
/// The cursor range is byte-wise indexed.
|
||||||
|
Preedit(String, Option<Range<usize>>),
|
||||||
|
|
||||||
|
/// Notifies when text should be inserted into the editor widget.
|
||||||
|
///
|
||||||
|
/// 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 [`Opened`][Self::Opened] event. You should
|
||||||
|
/// also stop issuing IME related requests like [`Shell::request_input_method`] and clear
|
||||||
|
/// pending preedit text.
|
||||||
|
///
|
||||||
|
/// [`Shell::request_input_method`]: crate::Shell::request_input_method
|
||||||
|
Closed,
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ pub mod event;
|
||||||
pub mod font;
|
pub mod font;
|
||||||
pub mod gradient;
|
pub mod gradient;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
|
pub mod input_method;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod mouse;
|
pub mod mouse;
|
||||||
|
|
@ -61,6 +62,7 @@ pub use event::Event;
|
||||||
pub use font::Font;
|
pub use font::Font;
|
||||||
pub use gradient::Gradient;
|
pub use gradient::Gradient;
|
||||||
pub use image::Image;
|
pub use image::Image;
|
||||||
|
pub use input_method::InputMethod;
|
||||||
pub use layout::Layout;
|
pub use layout::Layout;
|
||||||
pub use length::Length;
|
pub use length::Length;
|
||||||
pub use overlay::Overlay;
|
pub use overlay::Overlay;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::event;
|
use crate::event;
|
||||||
use crate::time::Instant;
|
|
||||||
use crate::window;
|
use crate::window;
|
||||||
|
use crate::InputMethod;
|
||||||
|
|
||||||
/// A connection to the state of a shell.
|
/// A connection to the state of a shell.
|
||||||
///
|
///
|
||||||
|
|
@ -12,7 +12,8 @@ use crate::window;
|
||||||
pub struct Shell<'a, Message> {
|
pub struct Shell<'a, Message> {
|
||||||
messages: &'a mut Vec<Message>,
|
messages: &'a mut Vec<Message>,
|
||||||
event_status: event::Status,
|
event_status: event::Status,
|
||||||
redraw_request: Option<window::RedrawRequest>,
|
redraw_request: window::RedrawRequest,
|
||||||
|
input_method: InputMethod,
|
||||||
is_layout_invalid: bool,
|
is_layout_invalid: bool,
|
||||||
are_widgets_invalid: bool,
|
are_widgets_invalid: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -23,9 +24,10 @@ impl<'a, Message> Shell<'a, Message> {
|
||||||
Self {
|
Self {
|
||||||
messages,
|
messages,
|
||||||
event_status: event::Status::Ignored,
|
event_status: event::Status::Ignored,
|
||||||
redraw_request: None,
|
redraw_request: window::RedrawRequest::Wait,
|
||||||
is_layout_invalid: false,
|
is_layout_invalid: false,
|
||||||
are_widgets_invalid: false,
|
are_widgets_invalid: false,
|
||||||
|
input_method: InputMethod::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,24 +61,19 @@ impl<'a, Message> Shell<'a, Message> {
|
||||||
|
|
||||||
/// Requests a new frame to be drawn as soon as possible.
|
/// Requests a new frame to be drawn as soon as possible.
|
||||||
pub fn request_redraw(&mut self) {
|
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`].
|
/// Requests a new frame to be drawn at the given [`window::RedrawRequest`].
|
||||||
pub fn request_redraw_at(&mut self, at: Instant) {
|
pub fn request_redraw_at(
|
||||||
match self.redraw_request {
|
&mut self,
|
||||||
None => {
|
redraw_request: impl Into<window::RedrawRequest>,
|
||||||
self.redraw_request = Some(window::RedrawRequest::At(at));
|
) {
|
||||||
}
|
self.redraw_request = self.redraw_request.min(redraw_request.into());
|
||||||
Some(window::RedrawRequest::At(current)) if at < current => {
|
|
||||||
self.redraw_request = Some(window::RedrawRequest::At(at));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the request a redraw should happen, if any.
|
/// 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
|
self.redraw_request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,11 +84,32 @@ impl<'a, Message> Shell<'a, Message> {
|
||||||
/// method.
|
/// method.
|
||||||
pub fn replace_redraw_request(
|
pub fn replace_redraw_request(
|
||||||
shell: &mut Self,
|
shell: &mut Self,
|
||||||
redraw_request: Option<window::RedrawRequest>,
|
redraw_request: window::RedrawRequest,
|
||||||
) {
|
) {
|
||||||
shell.redraw_request = redraw_request;
|
shell.redraw_request = redraw_request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Requests the current [`InputMethod`] strategy.
|
||||||
|
///
|
||||||
|
/// __Important__: This request will only be honored by the
|
||||||
|
/// [`Shell`] only during a [`window::Event::RedrawRequested`].
|
||||||
|
pub fn request_input_method<T: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
ime: &InputMethod<T>,
|
||||||
|
) {
|
||||||
|
self.input_method.merge(ime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
/// Returns whether the current layout is invalid or not.
|
||||||
pub fn is_layout_invalid(&self) -> bool {
|
pub fn is_layout_invalid(&self) -> bool {
|
||||||
self.is_layout_invalid
|
self.is_layout_invalid
|
||||||
|
|
@ -134,20 +152,14 @@ impl<'a, Message> Shell<'a, Message> {
|
||||||
pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) {
|
pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) {
|
||||||
self.messages.extend(other.messages.drain(..).map(f));
|
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.is_layout_invalid =
|
self.is_layout_invalid =
|
||||||
self.is_layout_invalid || other.is_layout_invalid;
|
self.is_layout_invalid || other.is_layout_invalid;
|
||||||
|
|
||||||
self.are_widgets_invalid =
|
self.are_widgets_invalid =
|
||||||
self.are_widgets_invalid || other.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.event_status = self.event_status.merge(other.event_status);
|
||||||
|
self.input_method.merge(&other.input_method);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -284,15 +284,7 @@ impl<'a, Link, Font> Span<'a, Link, Font> {
|
||||||
pub fn new(fragment: impl IntoFragment<'a>) -> Self {
|
pub fn new(fragment: impl IntoFragment<'a>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text: fragment.into_fragment(),
|
text: fragment.into_fragment(),
|
||||||
size: None,
|
..Self::default()
|
||||||
line_height: None,
|
|
||||||
font: None,
|
|
||||||
color: None,
|
|
||||||
highlight: None,
|
|
||||||
link: None,
|
|
||||||
padding: Padding::ZERO,
|
|
||||||
underline: false,
|
|
||||||
strikethrough: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -440,6 +432,23 @@ impl<'a, Link, Font> Span<'a, Link, Font> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Link, Font> Default for Span<'_, Link, Font> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
text: Cow::default(),
|
||||||
|
size: None,
|
||||||
|
line_height: None,
|
||||||
|
font: None,
|
||||||
|
color: None,
|
||||||
|
link: None,
|
||||||
|
highlight: None,
|
||||||
|
padding: Padding::default(),
|
||||||
|
underline: false,
|
||||||
|
strikethrough: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
|
impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
|
||||||
fn from(value: &'a str) -> Self {
|
fn from(value: &'a str) -> Self {
|
||||||
Span::new(value)
|
Span::new(value)
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,12 @@ impl<P: Paragraph> Plain<P> {
|
||||||
self.raw.min_width()
|
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`].
|
/// Returns the cached [`Paragraph`].
|
||||||
pub fn raw(&self) -> &P {
|
pub fn raw(&self) -> &P {
|
||||||
&self.raw
|
&self.raw
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,15 @@ pub enum RedrawRequest {
|
||||||
|
|
||||||
/// Redraw at the given time.
|
/// Redraw at the given time.
|
||||||
At(Instant),
|
At(Instant),
|
||||||
|
|
||||||
|
/// No redraw is needed.
|
||||||
|
Wait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Instant> for RedrawRequest {
|
||||||
|
fn from(time: Instant) -> Self {
|
||||||
|
Self::At(time)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -34,5 +43,8 @@ mod tests {
|
||||||
assert!(RedrawRequest::At(now) <= RedrawRequest::At(now));
|
assert!(RedrawRequest::At(now) <= RedrawRequest::At(now));
|
||||||
assert!(RedrawRequest::At(now) <= RedrawRequest::At(later));
|
assert!(RedrawRequest::At(now) <= RedrawRequest::At(later));
|
||||||
assert!(RedrawRequest::At(later) >= RedrawRequest::At(now));
|
assert!(RedrawRequest::At(later) >= RedrawRequest::At(now));
|
||||||
|
|
||||||
|
assert!(RedrawRequest::Wait > RedrawRequest::NextFrame);
|
||||||
|
assert!(RedrawRequest::Wait > RedrawRequest::At(later));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget;
|
use crate::core::widget;
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{Clipboard, Element, Layout, Rectangle, Shell, Size, Vector};
|
use crate::core::{
|
||||||
|
Clipboard, Element, InputMethod, Layout, Rectangle, Shell, Size, Vector,
|
||||||
|
};
|
||||||
use crate::overlay;
|
use crate::overlay;
|
||||||
|
|
||||||
/// A set of interactive graphical elements with a specific [`Layout`].
|
/// A set of interactive graphical elements with a specific [`Layout`].
|
||||||
|
|
@ -186,7 +188,8 @@ where
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
|
|
||||||
let mut outdated = false;
|
let mut outdated = false;
|
||||||
let mut redraw_request = None;
|
let mut redraw_request = window::RedrawRequest::Wait;
|
||||||
|
let mut input_method = InputMethod::None;
|
||||||
|
|
||||||
let mut manual_overlay = ManuallyDrop::new(
|
let mut manual_overlay = ManuallyDrop::new(
|
||||||
self.root
|
self.root
|
||||||
|
|
@ -220,16 +223,8 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
event_statuses.push(shell.event_status());
|
event_statuses.push(shell.event_status());
|
||||||
|
redraw_request = redraw_request.min(shell.redraw_request());
|
||||||
match (redraw_request, shell.redraw_request()) {
|
input_method.merge(shell.input_method());
|
||||||
(None, Some(at)) => {
|
|
||||||
redraw_request = Some(at);
|
|
||||||
}
|
|
||||||
(Some(current), Some(new)) if new < current => {
|
|
||||||
redraw_request = Some(new);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if shell.is_layout_invalid() {
|
if shell.is_layout_invalid() {
|
||||||
let _ = ManuallyDrop::into_inner(manual_overlay);
|
let _ = ManuallyDrop::into_inner(manual_overlay);
|
||||||
|
|
@ -323,15 +318,8 @@ where
|
||||||
self.overlay = None;
|
self.overlay = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
match (redraw_request, shell.redraw_request()) {
|
redraw_request = redraw_request.min(shell.redraw_request());
|
||||||
(None, Some(at)) => {
|
input_method.merge(shell.input_method());
|
||||||
redraw_request = Some(at);
|
|
||||||
}
|
|
||||||
(Some(current), Some(new)) if new < current => {
|
|
||||||
redraw_request = Some(new);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
shell.revalidate_layout(|| {
|
shell.revalidate_layout(|| {
|
||||||
self.base = self.root.as_widget().layout(
|
self.base = self.root.as_widget().layout(
|
||||||
|
|
@ -355,7 +343,10 @@ where
|
||||||
if outdated {
|
if outdated {
|
||||||
State::Outdated
|
State::Outdated
|
||||||
} else {
|
} else {
|
||||||
State::Updated { redraw_request }
|
State::Updated {
|
||||||
|
redraw_request,
|
||||||
|
input_method,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
event_statuses,
|
event_statuses,
|
||||||
)
|
)
|
||||||
|
|
@ -636,7 +627,7 @@ impl Default for Cache {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The resulting state after updating a [`UserInterface`].
|
/// The resulting state after updating a [`UserInterface`].
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
/// The [`UserInterface`] is outdated and needs to be rebuilt.
|
/// The [`UserInterface`] is outdated and needs to be rebuilt.
|
||||||
Outdated,
|
Outdated,
|
||||||
|
|
@ -644,7 +635,9 @@ pub enum State {
|
||||||
/// The [`UserInterface`] is up-to-date and can be reused without
|
/// The [`UserInterface`] is up-to-date and can be reused without
|
||||||
/// rebuilding.
|
/// rebuilding.
|
||||||
Updated {
|
Updated {
|
||||||
/// The [`window::RedrawRequest`] when a redraw should be performed.
|
/// The [`window::RedrawRequest`] describing when a redraw should be performed.
|
||||||
redraw_request: Option<window::RedrawRequest>,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Action<Message> {
|
pub struct Action<Message> {
|
||||||
message_to_publish: Option<Message>,
|
message_to_publish: Option<Message>,
|
||||||
redraw_request: Option<window::RedrawRequest>,
|
redraw_request: window::RedrawRequest,
|
||||||
event_status: event::Status,
|
event_status: event::Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ impl<Message> Action<Message> {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
message_to_publish: None,
|
message_to_publish: None,
|
||||||
redraw_request: None,
|
redraw_request: window::RedrawRequest::Wait,
|
||||||
event_status: event::Status::Ignored,
|
event_status: event::Status::Ignored,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ impl<Message> Action<Message> {
|
||||||
/// soon as possible; without publishing any `Message`.
|
/// soon as possible; without publishing any `Message`.
|
||||||
pub fn request_redraw() -> Self {
|
pub fn request_redraw() -> Self {
|
||||||
Self {
|
Self {
|
||||||
redraw_request: Some(window::RedrawRequest::NextFrame),
|
redraw_request: window::RedrawRequest::NextFrame,
|
||||||
..Self::new()
|
..Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +58,7 @@ impl<Message> Action<Message> {
|
||||||
/// blinking caret on a text input.
|
/// blinking caret on a text input.
|
||||||
pub fn request_redraw_at(at: Instant) -> Self {
|
pub fn request_redraw_at(at: Instant) -> Self {
|
||||||
Self {
|
Self {
|
||||||
redraw_request: Some(window::RedrawRequest::At(at)),
|
redraw_request: window::RedrawRequest::At(at),
|
||||||
..Self::new()
|
..Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -75,11 +75,7 @@ impl<Message> Action<Message> {
|
||||||
/// widget implementations.
|
/// widget implementations.
|
||||||
pub fn into_inner(
|
pub fn into_inner(
|
||||||
self,
|
self,
|
||||||
) -> (
|
) -> (Option<Message>, window::RedrawRequest, event::Status) {
|
||||||
Option<Message>,
|
|
||||||
Option<window::RedrawRequest>,
|
|
||||||
event::Status,
|
|
||||||
) {
|
|
||||||
(
|
(
|
||||||
self.message_to_publish,
|
self.message_to_publish,
|
||||||
self.redraw_request,
|
self.redraw_request,
|
||||||
|
|
|
||||||
|
|
@ -238,27 +238,18 @@ where
|
||||||
{
|
{
|
||||||
let (message, redraw_request, event_status) = action.into_inner();
|
let (message, redraw_request, event_status) = action.into_inner();
|
||||||
|
|
||||||
|
shell.request_redraw_at(redraw_request);
|
||||||
|
|
||||||
if let Some(message) = message {
|
if let Some(message) = message {
|
||||||
shell.publish(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 {
|
if event_status == event::Status::Captured {
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) {
|
if shell.redraw_request() != window::RedrawRequest::NextFrame {
|
||||||
let mouse_interaction = self
|
let mouse_interaction = self
|
||||||
.mouse_interaction(tree, layout, cursor, viewport, renderer);
|
.mouse_interaction(tree, layout, cursor, viewport, renderer);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ use crate::core::renderer;
|
||||||
use crate::core::text;
|
use crate::core::text;
|
||||||
use crate::core::time::Instant;
|
use crate::core::time::Instant;
|
||||||
use crate::core::widget::{self, Widget};
|
use crate::core::widget::{self, Widget};
|
||||||
use crate::core::window;
|
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Clipboard, Element, Event, Length, Padding, Rectangle, Shell, Size, Theme,
|
Clipboard, Element, Event, Length, Padding, Rectangle, Shell, Size, Theme,
|
||||||
Vector,
|
Vector,
|
||||||
|
|
@ -554,16 +553,8 @@ where
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(redraw_request) = local_shell.redraw_request() {
|
shell.request_redraw_at(local_shell.redraw_request());
|
||||||
match redraw_request {
|
shell.request_input_method(local_shell.input_method());
|
||||||
window::RedrawRequest::NextFrame => {
|
|
||||||
shell.request_redraw();
|
|
||||||
}
|
|
||||||
window::RedrawRequest::At(at) => {
|
|
||||||
shell.request_redraw_at(at);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then finally react to them here
|
// Then finally react to them here
|
||||||
for message in local_messages {
|
for message in local_messages {
|
||||||
|
|
@ -742,6 +733,8 @@ where
|
||||||
published_message_to_shell = true;
|
published_message_to_shell = true;
|
||||||
|
|
||||||
// Unfocus the input
|
// Unfocus the input
|
||||||
|
let mut local_messages = Vec::new();
|
||||||
|
let mut local_shell = Shell::new(&mut local_messages);
|
||||||
self.text_input.update(
|
self.text_input.update(
|
||||||
&mut tree.children[0],
|
&mut tree.children[0],
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(
|
Event::Mouse(mouse::Event::ButtonPressed(
|
||||||
|
|
@ -751,9 +744,10 @@ where
|
||||||
mouse::Cursor::Unavailable,
|
mouse::Cursor::Unavailable,
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
&mut Shell::new(&mut vec![]),
|
&mut local_shell,
|
||||||
viewport,
|
viewport,
|
||||||
);
|
);
|
||||||
|
shell.request_input_method(local_shell.input_method());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use crate::core::overlay;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget;
|
use crate::core::widget;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::window;
|
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector,
|
self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector,
|
||||||
Widget,
|
Widget,
|
||||||
|
|
@ -344,17 +343,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
||||||
|
shell.request_redraw_at(local_shell.redraw_request());
|
||||||
if let Some(redraw_request) = local_shell.redraw_request() {
|
shell.request_input_method(local_shell.input_method());
|
||||||
match redraw_request {
|
|
||||||
window::RedrawRequest::NextFrame => {
|
|
||||||
shell.request_redraw();
|
|
||||||
}
|
|
||||||
window::RedrawRequest::At(at) => {
|
|
||||||
shell.request_redraw_at(at);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !local_messages.is_empty() {
|
if !local_messages.is_empty() {
|
||||||
let mut heads = self.state.take().unwrap().into_heads();
|
let mut heads = self.state.take().unwrap().into_heads();
|
||||||
|
|
@ -629,17 +619,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
||||||
|
shell.request_redraw_at(local_shell.redraw_request());
|
||||||
if let Some(redraw_request) = local_shell.redraw_request() {
|
shell.request_input_method(local_shell.input_method());
|
||||||
match redraw_request {
|
|
||||||
window::RedrawRequest::NextFrame => {
|
|
||||||
shell.request_redraw();
|
|
||||||
}
|
|
||||||
window::RedrawRequest::At(at) => {
|
|
||||||
shell.request_redraw_at(at);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !local_messages.is_empty() {
|
if !local_messages.is_empty() {
|
||||||
let mut inner =
|
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
|
let interaction = self
|
||||||
.grid_interaction(action, layout, cursor)
|
.grid_interaction(action, layout, cursor)
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,9 @@ use crate::core::widget::operation::{self, Operation};
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Background, Clipboard, Color, Element, Event, Layout, Length,
|
self, Background, Clipboard, Color, Element, Event, InputMethod, Layout,
|
||||||
Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
|
Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector,
|
||||||
|
Widget,
|
||||||
};
|
};
|
||||||
use crate::runtime::task::{self, Task};
|
use crate::runtime::task::{self, Task};
|
||||||
use crate::runtime::Action;
|
use crate::runtime::Action;
|
||||||
|
|
@ -728,6 +729,8 @@ where
|
||||||
_ => mouse::Cursor::Unavailable,
|
_ => mouse::Cursor::Unavailable,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let had_input_method = shell.input_method().is_open();
|
||||||
|
|
||||||
let translation =
|
let translation =
|
||||||
state.translation(self.direction, bounds, content_bounds);
|
state.translation(self.direction, bounds, content_bounds);
|
||||||
|
|
||||||
|
|
@ -745,6 +748,14 @@ where
|
||||||
..bounds
|
..bounds
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if !had_input_method {
|
||||||
|
if let InputMethod::Open { position, .. } =
|
||||||
|
shell.input_method_mut()
|
||||||
|
{
|
||||||
|
*position = *position + translation;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ use crate::core::mouse;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::widget::{self, Widget};
|
use crate::core::widget::{self, Widget};
|
||||||
use crate::core::window;
|
|
||||||
use crate::core::{Clipboard, Element, Event, Length, Rectangle, Shell, Size};
|
use crate::core::{Clipboard, Element, Event, Length, Rectangle, Shell, Size};
|
||||||
use crate::renderer::wgpu::primitive;
|
use crate::renderer::wgpu::primitive;
|
||||||
|
|
||||||
|
|
@ -105,21 +104,12 @@ where
|
||||||
{
|
{
|
||||||
let (message, redraw_request, event_status) = action.into_inner();
|
let (message, redraw_request, event_status) = action.into_inner();
|
||||||
|
|
||||||
|
shell.request_redraw_at(redraw_request);
|
||||||
|
|
||||||
if let Some(message) = message {
|
if let Some(message) = message {
|
||||||
shell.publish(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 {
|
if event_status == event::Status::Captured {
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
//! ```
|
//! ```
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::clipboard::{self, Clipboard};
|
use crate::core::clipboard::{self, Clipboard};
|
||||||
|
use crate::core::input_method;
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::keyboard::key;
|
use crate::core::keyboard::key;
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
|
|
@ -46,14 +47,15 @@ use crate::core::widget::operation;
|
||||||
use crate::core::widget::{self, Widget};
|
use crate::core::widget::{self, Widget};
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Background, Border, Color, Element, Event, Length, Padding, Pixels, Point,
|
Background, Border, Color, Element, Event, InputMethod, Length, Padding,
|
||||||
Rectangle, Shell, Size, SmolStr, Theme, Vector,
|
Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
use std::ops::Range;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use text::editor::{Action, Edit, Line, LineEnding, Motion};
|
pub use text::editor::{Action, Edit, Line, LineEnding, Motion};
|
||||||
|
|
@ -322,6 +324,51 @@ where
|
||||||
self.class = class.into();
|
self.class = class.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_method<'b>(
|
||||||
|
&self,
|
||||||
|
state: &'b State<Highlighter>,
|
||||||
|
renderer: &Renderer,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
) -> InputMethod<&'b str> {
|
||||||
|
let Some(Focus {
|
||||||
|
is_window_focused: true,
|
||||||
|
..
|
||||||
|
}) = &state.focus
|
||||||
|
else {
|
||||||
|
return InputMethod::Disabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(preedit) = &state.preedit else {
|
||||||
|
return InputMethod::Allowed;
|
||||||
|
};
|
||||||
|
|
||||||
|
let bounds = layout.bounds();
|
||||||
|
let internal = self.content.0.borrow_mut();
|
||||||
|
|
||||||
|
let text_bounds = bounds.shrink(self.padding);
|
||||||
|
let translation = text_bounds.position() - Point::ORIGIN;
|
||||||
|
|
||||||
|
let cursor = match internal.editor.cursor() {
|
||||||
|
Cursor::Caret(position) => position,
|
||||||
|
Cursor::Selection(ranges) => {
|
||||||
|
ranges.first().cloned().unwrap_or_default().position()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let line_height = self.line_height.to_absolute(
|
||||||
|
self.text_size.unwrap_or_else(|| renderer.default_size()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let position =
|
||||||
|
cursor + translation + Vector::new(0.0, f32::from(line_height));
|
||||||
|
|
||||||
|
InputMethod::Open {
|
||||||
|
position,
|
||||||
|
purpose: input_method::Purpose::Normal,
|
||||||
|
preedit: Some(preedit.as_ref()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The content of a [`TextEditor`].
|
/// The content of a [`TextEditor`].
|
||||||
|
|
@ -450,6 +497,7 @@ where
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct State<Highlighter: text::Highlighter> {
|
pub struct State<Highlighter: text::Highlighter> {
|
||||||
focus: Option<Focus>,
|
focus: Option<Focus>,
|
||||||
|
preedit: Option<input_method::Preedit>,
|
||||||
last_click: Option<mouse::Click>,
|
last_click: Option<mouse::Click>,
|
||||||
drag_click: Option<mouse::click::Kind>,
|
drag_click: Option<mouse::click::Kind>,
|
||||||
partial_scroll: f32,
|
partial_scroll: f32,
|
||||||
|
|
@ -458,7 +506,7 @@ pub struct State<Highlighter: text::Highlighter> {
|
||||||
highlighter_format_address: usize,
|
highlighter_format_address: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
struct Focus {
|
struct Focus {
|
||||||
updated_at: Instant,
|
updated_at: Instant,
|
||||||
now: Instant,
|
now: Instant,
|
||||||
|
|
@ -524,6 +572,7 @@ where
|
||||||
fn state(&self) -> widget::tree::State {
|
fn state(&self) -> widget::tree::State {
|
||||||
widget::tree::State::new(State {
|
widget::tree::State::new(State {
|
||||||
focus: None,
|
focus: None,
|
||||||
|
preedit: None,
|
||||||
last_click: None,
|
last_click: None,
|
||||||
drag_click: None,
|
drag_click: None,
|
||||||
partial_scroll: 0.0,
|
partial_scroll: 0.0,
|
||||||
|
|
@ -605,7 +654,7 @@ where
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
_renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
|
|
@ -701,6 +750,25 @@ where
|
||||||
}));
|
}));
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
|
Update::InputMethod(update) => match update {
|
||||||
|
Ime::Toggle(is_open) => {
|
||||||
|
state.preedit =
|
||||||
|
is_open.then(input_method::Preedit::new);
|
||||||
|
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
|
Ime::Preedit { content, selection } => {
|
||||||
|
state.preedit =
|
||||||
|
Some(input_method::Preedit { content, selection });
|
||||||
|
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
|
Ime::Commit(text) => {
|
||||||
|
shell.publish(on_edit(Action::Edit(Edit::Paste(
|
||||||
|
Arc::new(text),
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
},
|
||||||
Update::Binding(binding) => {
|
Update::Binding(binding) => {
|
||||||
fn apply_binding<
|
fn apply_binding<
|
||||||
H: text::Highlighter,
|
H: text::Highlighter,
|
||||||
|
|
@ -827,6 +895,10 @@ where
|
||||||
|
|
||||||
if is_redraw {
|
if is_redraw {
|
||||||
self.last_status = Some(status);
|
self.last_status = Some(status);
|
||||||
|
|
||||||
|
shell.request_input_method(
|
||||||
|
&self.input_method(state, renderer, layout),
|
||||||
|
);
|
||||||
} else if self
|
} else if self
|
||||||
.last_status
|
.last_status
|
||||||
.is_some_and(|last_status| status != last_status)
|
.is_some_and(|last_status| status != last_status)
|
||||||
|
|
@ -1129,9 +1201,19 @@ enum Update<Message> {
|
||||||
Drag(Point),
|
Drag(Point),
|
||||||
Release,
|
Release,
|
||||||
Scroll(f32),
|
Scroll(f32),
|
||||||
|
InputMethod(Ime),
|
||||||
Binding(Binding<Message>),
|
Binding(Binding<Message>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Ime {
|
||||||
|
Toggle(bool),
|
||||||
|
Preedit {
|
||||||
|
content: String,
|
||||||
|
selection: Option<Range<usize>>,
|
||||||
|
},
|
||||||
|
Commit(String),
|
||||||
|
}
|
||||||
|
|
||||||
impl<Message> Update<Message> {
|
impl<Message> Update<Message> {
|
||||||
fn from_event<H: Highlighter>(
|
fn from_event<H: Highlighter>(
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|
@ -1191,6 +1273,28 @@ impl<Message> Update<Message> {
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
|
Event::InputMethod(event) => match event {
|
||||||
|
input_method::Event::Opened | input_method::Event::Closed => {
|
||||||
|
Some(Update::InputMethod(Ime::Toggle(matches!(
|
||||||
|
event,
|
||||||
|
input_method::Event::Opened
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
input_method::Event::Preedit(content, selection)
|
||||||
|
if state.focus.is_some() =>
|
||||||
|
{
|
||||||
|
Some(Update::InputMethod(Ime::Preedit {
|
||||||
|
content,
|
||||||
|
selection,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
input_method::Event::Commit(content)
|
||||||
|
if state.focus.is_some() =>
|
||||||
|
{
|
||||||
|
Some(Update::InputMethod(Ime::Commit(content)))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
Event::Keyboard(keyboard::Event::KeyPressed {
|
Event::Keyboard(keyboard::Event::KeyPressed {
|
||||||
key,
|
key,
|
||||||
modifiers,
|
modifiers,
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ use editor::Editor;
|
||||||
|
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::clipboard::{self, Clipboard};
|
use crate::core::clipboard::{self, Clipboard};
|
||||||
|
use crate::core::input_method;
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::keyboard::key;
|
use crate::core::keyboard::key;
|
||||||
use crate::core::layout;
|
use crate::core::layout;
|
||||||
|
|
@ -56,8 +57,8 @@ use crate::core::widget::operation::{self, Operation};
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::window;
|
use crate::core::window;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
Background, Border, Color, Element, Event, Layout, Length, Padding, Pixels,
|
Background, Border, Color, Element, Event, InputMethod, Layout, Length,
|
||||||
Point, Rectangle, Shell, Size, Theme, Vector, Widget,
|
Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
|
||||||
};
|
};
|
||||||
use crate::runtime::task::{self, Task};
|
use crate::runtime::task::{self, Task};
|
||||||
use crate::runtime::Action;
|
use crate::runtime::Action;
|
||||||
|
|
@ -391,6 +392,58 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_method<'b>(
|
||||||
|
&self,
|
||||||
|
state: &'b State<Renderer::Paragraph>,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
value: &Value,
|
||||||
|
) -> InputMethod<&'b str> {
|
||||||
|
let Some(Focus {
|
||||||
|
is_window_focused: true,
|
||||||
|
..
|
||||||
|
}) = &state.is_focused
|
||||||
|
else {
|
||||||
|
return InputMethod::Disabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(preedit) = &state.is_ime_open else {
|
||||||
|
return InputMethod::Allowed;
|
||||||
|
};
|
||||||
|
|
||||||
|
let secure_value = self.is_secure.then(|| value.secure());
|
||||||
|
let value = secure_value.as_ref().unwrap_or(value);
|
||||||
|
|
||||||
|
let text_bounds = layout.children().next().unwrap().bounds();
|
||||||
|
|
||||||
|
let caret_index = match state.cursor.state(value) {
|
||||||
|
cursor::State::Index(position) => position,
|
||||||
|
cursor::State::Selection { start, end } => start.min(end),
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = state.value.raw();
|
||||||
|
let (cursor_x, scroll_offset) =
|
||||||
|
measure_cursor_and_scroll_offset(text, text_bounds, caret_index);
|
||||||
|
|
||||||
|
let alignment_offset = alignment_offset(
|
||||||
|
text_bounds.width,
|
||||||
|
text.min_width(),
|
||||||
|
self.alignment,
|
||||||
|
);
|
||||||
|
|
||||||
|
let x = (text_bounds.x + cursor_x).floor() - scroll_offset
|
||||||
|
+ alignment_offset;
|
||||||
|
|
||||||
|
InputMethod::Open {
|
||||||
|
position: Point::new(x, text_bounds.y + text_bounds.height),
|
||||||
|
purpose: if self.is_secure {
|
||||||
|
input_method::Purpose::Secure
|
||||||
|
} else {
|
||||||
|
input_method::Purpose::Normal
|
||||||
|
},
|
||||||
|
preedit: Some(preedit.as_ref()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
|
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
|
||||||
/// [`Value`] if provided.
|
/// [`Value`] if provided.
|
||||||
///
|
///
|
||||||
|
|
@ -1197,6 +1250,51 @@ where
|
||||||
|
|
||||||
state.keyboard_modifiers = *modifiers;
|
state.keyboard_modifiers = *modifiers;
|
||||||
}
|
}
|
||||||
|
Event::InputMethod(event) => match event {
|
||||||
|
input_method::Event::Opened | input_method::Event::Closed => {
|
||||||
|
let state = state::<Renderer>(tree);
|
||||||
|
|
||||||
|
state.is_ime_open =
|
||||||
|
matches!(event, input_method::Event::Opened)
|
||||||
|
.then(input_method::Preedit::new);
|
||||||
|
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
|
input_method::Event::Preedit(content, selection) => {
|
||||||
|
let state = state::<Renderer>(tree);
|
||||||
|
|
||||||
|
if state.is_focused.is_some() {
|
||||||
|
state.is_ime_open = Some(input_method::Preedit {
|
||||||
|
content: content.to_owned(),
|
||||||
|
selection: selection.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input_method::Event::Commit(text) => {
|
||||||
|
let state = state::<Renderer>(tree);
|
||||||
|
|
||||||
|
if let Some(focus) = &mut state.is_focused {
|
||||||
|
let Some(on_input) = &self.on_input else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut editor =
|
||||||
|
Editor::new(&mut self.value, &mut state.cursor);
|
||||||
|
editor.paste(Value::new(text));
|
||||||
|
|
||||||
|
focus.updated_at = Instant::now();
|
||||||
|
state.is_pasting = None;
|
||||||
|
|
||||||
|
let message = (on_input)(editor.contents());
|
||||||
|
shell.publish(message);
|
||||||
|
shell.capture_event();
|
||||||
|
|
||||||
|
update_cache(state, &self.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
Event::Window(window::Event::Unfocused) => {
|
Event::Window(window::Event::Unfocused) => {
|
||||||
let state = state::<Renderer>(tree);
|
let state = state::<Renderer>(tree);
|
||||||
|
|
||||||
|
|
@ -1258,6 +1356,12 @@ where
|
||||||
|
|
||||||
if let Event::Window(window::Event::RedrawRequested(_now)) = event {
|
if let Event::Window(window::Event::RedrawRequested(_now)) = event {
|
||||||
self.last_status = Some(status);
|
self.last_status = Some(status);
|
||||||
|
|
||||||
|
shell.request_input_method(&self.input_method(
|
||||||
|
state,
|
||||||
|
layout,
|
||||||
|
&self.value,
|
||||||
|
));
|
||||||
} else if self
|
} else if self
|
||||||
.last_status
|
.last_status
|
||||||
.is_some_and(|last_status| status != last_status)
|
.is_some_and(|last_status| status != last_status)
|
||||||
|
|
@ -1417,6 +1521,7 @@ pub struct State<P: text::Paragraph> {
|
||||||
placeholder: paragraph::Plain<P>,
|
placeholder: paragraph::Plain<P>,
|
||||||
icon: paragraph::Plain<P>,
|
icon: paragraph::Plain<P>,
|
||||||
is_focused: Option<Focus>,
|
is_focused: Option<Focus>,
|
||||||
|
is_ime_open: Option<input_method::Preedit>,
|
||||||
is_dragging: bool,
|
is_dragging: bool,
|
||||||
is_pasting: Option<Value>,
|
is_pasting: Option<Value>,
|
||||||
last_click: Option<mouse::Click>,
|
last_click: Option<mouse::Click>,
|
||||||
|
|
@ -1431,7 +1536,7 @@ fn state<Renderer: text::Renderer>(
|
||||||
tree.state.downcast_mut::<State<Renderer::Paragraph>>()
|
tree.state.downcast_mut::<State<Renderer::Paragraph>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
struct Focus {
|
struct Focus {
|
||||||
updated_at: Instant,
|
updated_at: Instant,
|
||||||
now: Instant,
|
now: Instant,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
//!
|
//!
|
||||||
//! [`winit`]: https://github.com/rust-windowing/winit
|
//! [`winit`]: https://github.com/rust-windowing/winit
|
||||||
//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.13/runtime
|
//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.13/runtime
|
||||||
|
use crate::core::input_method;
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::touch;
|
use crate::core::touch;
|
||||||
|
|
@ -140,6 +141,7 @@ pub fn window_event(
|
||||||
scale_factor: f64,
|
scale_factor: f64,
|
||||||
modifiers: winit::keyboard::ModifiersState,
|
modifiers: winit::keyboard::ModifiersState,
|
||||||
) -> Option<Event> {
|
) -> Option<Event> {
|
||||||
|
use winit::event::Ime;
|
||||||
use winit::event::WindowEvent;
|
use winit::event::WindowEvent;
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
|
@ -283,6 +285,15 @@ pub fn window_event(
|
||||||
self::modifiers(new_modifiers.state()),
|
self::modifiers(new_modifiers.state()),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
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 {
|
WindowEvent::Focused(focused) => Some(Event::Window(if focused {
|
||||||
window::Event::Focused
|
window::Event::Focused
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1160,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.
|
/// Returns `None` if there is an error during the conversion.
|
||||||
pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {
|
pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {
|
||||||
|
|
@ -1169,6 +1180,17 @@ pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {
|
||||||
winit::window::Icon::from_rgba(pixels, size.width, size.height).ok()
|
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
|
// See: https://en.wikipedia.org/wiki/Private_Use_Areas
|
||||||
fn is_private_use(c: char) -> bool {
|
fn is_private_use(c: char) -> bool {
|
||||||
('\u{E000}'..='\u{F8FF}').contains(&c)
|
('\u{E000}'..='\u{F8FF}').contains(&c)
|
||||||
|
|
|
||||||
|
|
@ -873,20 +873,16 @@ async fn run_instance<P, C>(
|
||||||
});
|
});
|
||||||
|
|
||||||
if let user_interface::State::Updated {
|
if let user_interface::State::Updated {
|
||||||
redraw_request: Some(redraw_request),
|
redraw_request,
|
||||||
|
input_method,
|
||||||
} = ui_state
|
} = ui_state
|
||||||
{
|
{
|
||||||
match redraw_request {
|
window.request_redraw(redraw_request);
|
||||||
window::RedrawRequest::NextFrame => {
|
window.request_input_method(input_method);
|
||||||
window.raw.request_redraw();
|
|
||||||
window.redraw_at = None;
|
|
||||||
}
|
|
||||||
window::RedrawRequest::At(at) => {
|
|
||||||
window.redraw_at = Some(at);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.draw_preedit();
|
||||||
|
|
||||||
debug.render_started();
|
debug.render_started();
|
||||||
match compositor.present(
|
match compositor.present(
|
||||||
&mut window.renderer,
|
&mut window.renderer,
|
||||||
|
|
@ -1029,27 +1025,23 @@ async fn run_instance<P, C>(
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "unconditional-rendering")]
|
#[cfg(feature = "unconditional-rendering")]
|
||||||
window.raw.request_redraw();
|
window.request_redraw(
|
||||||
|
window::RedrawRequest::NextFrame,
|
||||||
|
);
|
||||||
|
|
||||||
match ui_state {
|
match ui_state {
|
||||||
#[cfg(not(
|
|
||||||
feature = "unconditional-rendering"
|
|
||||||
))]
|
|
||||||
user_interface::State::Updated {
|
user_interface::State::Updated {
|
||||||
redraw_request: Some(redraw_request),
|
redraw_request: _redraw_request,
|
||||||
} => match redraw_request {
|
..
|
||||||
window::RedrawRequest::NextFrame => {
|
} => {
|
||||||
window.raw.request_redraw();
|
#[cfg(not(
|
||||||
window.redraw_at = None;
|
feature = "unconditional-rendering"
|
||||||
}
|
))]
|
||||||
window::RedrawRequest::At(at) => {
|
window.request_redraw(_redraw_request);
|
||||||
window.redraw_at = Some(at);
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
user_interface::State::Outdated => {
|
user_interface::State::Outdated => {
|
||||||
uis_stale = true;
|
uis_stale = true;
|
||||||
}
|
}
|
||||||
user_interface::State::Updated { .. } => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (event, status) in window_events
|
for (event, status) in window_events
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,23 @@
|
||||||
|
use crate::conversion;
|
||||||
|
use crate::core::alignment;
|
||||||
|
use crate::core::input_method;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
|
use crate::core::renderer;
|
||||||
|
use crate::core::text;
|
||||||
use crate::core::theme;
|
use crate::core::theme;
|
||||||
use crate::core::time::Instant;
|
use crate::core::time::Instant;
|
||||||
use crate::core::window::Id;
|
use crate::core::window::{Id, RedrawRequest};
|
||||||
use crate::core::{Point, Size};
|
use crate::core::{
|
||||||
|
Color, InputMethod, Padding, Point, Rectangle, Size, Text, Vector,
|
||||||
|
};
|
||||||
use crate::graphics::Compositor;
|
use crate::graphics::Compositor;
|
||||||
use crate::program::{Program, State};
|
use crate::program::{Program, State};
|
||||||
|
|
||||||
|
use winit::dpi::{LogicalPosition, LogicalSize};
|
||||||
|
use winit::monitor::MonitorHandle;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use winit::monitor::MonitorHandle;
|
|
||||||
|
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct WindowManager<P, C>
|
pub struct WindowManager<P, C>
|
||||||
|
|
@ -65,6 +74,7 @@ where
|
||||||
renderer,
|
renderer,
|
||||||
mouse_interaction: mouse::Interaction::None,
|
mouse_interaction: mouse::Interaction::None,
|
||||||
redraw_at: None,
|
redraw_at: None,
|
||||||
|
preedit: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -155,6 +165,7 @@ where
|
||||||
pub surface: C::Surface,
|
pub surface: C::Surface,
|
||||||
pub renderer: P::Renderer,
|
pub renderer: P::Renderer,
|
||||||
pub redraw_at: Option<Instant>,
|
pub redraw_at: Option<Instant>,
|
||||||
|
preedit: Option<Preedit<P::Renderer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P, C> Window<P, C>
|
impl<P, C> Window<P, C>
|
||||||
|
|
@ -179,4 +190,216 @@ where
|
||||||
|
|
||||||
Size::new(size.width, size.height)
|
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) {
|
||||||
|
match input_method {
|
||||||
|
InputMethod::None => {}
|
||||||
|
InputMethod::Disabled => {
|
||||||
|
self.raw.set_ime_allowed(false);
|
||||||
|
}
|
||||||
|
InputMethod::Allowed | InputMethod::Open { .. } => {
|
||||||
|
self.raw.set_ime_allowed(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), // TODO?
|
||||||
|
);
|
||||||
|
|
||||||
|
self.raw.set_ime_purpose(conversion::ime_purpose(purpose));
|
||||||
|
|
||||||
|
if let Some(preedit) = preedit {
|
||||||
|
if preedit.content.is_empty() {
|
||||||
|
self.preedit = None;
|
||||||
|
} else if let Some(overlay) = &mut self.preedit {
|
||||||
|
overlay.update(
|
||||||
|
position,
|
||||||
|
&preedit,
|
||||||
|
self.state.background_color(),
|
||||||
|
&self.renderer,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let mut overlay = Preedit::new();
|
||||||
|
overlay.update(
|
||||||
|
position,
|
||||||
|
&preedit,
|
||||||
|
self.state.background_color(),
|
||||||
|
&self.renderer,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.preedit = Some(overlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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(),
|
||||||
|
&Rectangle::new(
|
||||||
|
Point::ORIGIN,
|
||||||
|
self.state.viewport().logical_size(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Preedit<Renderer>
|
||||||
|
where
|
||||||
|
Renderer: text::Renderer,
|
||||||
|
{
|
||||||
|
position: Point,
|
||||||
|
content: Renderer::Paragraph,
|
||||||
|
spans: Vec<text::Span<'static, (), Renderer::Font>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Renderer> Preedit<Renderer>
|
||||||
|
where
|
||||||
|
Renderer: text::Renderer,
|
||||||
|
{
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
position: Point::ORIGIN,
|
||||||
|
spans: Vec::new(),
|
||||||
|
content: Renderer::Paragraph::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
position: Point,
|
||||||
|
preedit: &input_method::Preedit,
|
||||||
|
background: Color,
|
||||||
|
renderer: &Renderer,
|
||||||
|
) {
|
||||||
|
self.position = position;
|
||||||
|
|
||||||
|
let spans = match &preedit.selection {
|
||||||
|
Some(selection) => {
|
||||||
|
vec![
|
||||||
|
text::Span::new(&preedit.content[..selection.start]),
|
||||||
|
text::Span::new(if selection.start == selection.end {
|
||||||
|
"\u{200A}"
|
||||||
|
} else {
|
||||||
|
&preedit.content[selection.start..selection.end]
|
||||||
|
})
|
||||||
|
.color(background),
|
||||||
|
text::Span::new(&preedit.content[selection.end..]),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
_ => vec![text::Span::new(&preedit.content)],
|
||||||
|
};
|
||||||
|
|
||||||
|
if spans != self.spans.as_slice() {
|
||||||
|
use text::Paragraph as _;
|
||||||
|
|
||||||
|
self.content = Renderer::Paragraph::with_spans(Text {
|
||||||
|
content: &spans,
|
||||||
|
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,
|
||||||
|
shaping: text::Shaping::Advanced,
|
||||||
|
wrapping: text::Wrapping::None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self,
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
color: Color,
|
||||||
|
background: Color,
|
||||||
|
viewport: &Rectangle,
|
||||||
|
) {
|
||||||
|
use text::Paragraph as _;
|
||||||
|
|
||||||
|
if self.content.min_width() < 1.0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bounds = Rectangle::new(
|
||||||
|
self.position - Vector::new(0.0, self.content.min_height()),
|
||||||
|
self.content.min_bounds(),
|
||||||
|
);
|
||||||
|
|
||||||
|
bounds.x = bounds
|
||||||
|
.x
|
||||||
|
.max(viewport.x)
|
||||||
|
.min(viewport.x + viewport.width - bounds.width);
|
||||||
|
|
||||||
|
bounds.y = bounds
|
||||||
|
.y
|
||||||
|
.max(viewport.y)
|
||||||
|
.min(viewport.y + viewport.height - bounds.height);
|
||||||
|
|
||||||
|
renderer.with_layer(bounds, |renderer| {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
background,
|
||||||
|
);
|
||||||
|
|
||||||
|
renderer.fill_paragraph(
|
||||||
|
&self.content,
|
||||||
|
bounds.position(),
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
|
for span_bounds in self.content.span_bounds(1) {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: span_bounds
|
||||||
|
+ (bounds.position() - Point::ORIGIN),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue