From 86a16f935f88cb570eb19dba41352a8b5f11509d Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Sun, 21 May 2023 00:31:30 +0900 Subject: [PATCH 01/17] ime rebase --- core/src/element.rs | 6 +- core/src/ime.rs | 61 ++++++ core/src/keyboard/event.rs | 11 +- core/src/lib.rs | 2 + core/src/overlay.rs | 3 +- core/src/overlay/element.rs | 6 +- core/src/overlay/group.rs | 3 + core/src/widget.rs | 3 +- runtime/src/command/action.rs | 8 + runtime/src/ime.rs | 28 +++ runtime/src/lib.rs | 1 + runtime/src/program/state.rs | 4 +- runtime/src/user_interface.rs | 5 + widget/src/button.rs | 4 +- widget/src/canvas.rs | 3 +- widget/src/canvas/event.rs | 2 +- widget/src/checkbox.rs | 3 +- widget/src/column.rs | 4 +- widget/src/container.rs | 4 +- widget/src/image/viewer.rs | 3 +- widget/src/lazy.rs | 6 +- widget/src/lazy/component.rs | 5 + widget/src/lazy/responsive.rs | 5 + widget/src/mouse_area.rs | 4 +- widget/src/overlay/menu.rs | 4 + widget/src/pane_grid.rs | 4 +- widget/src/pane_grid/content.rs | 7 +- widget/src/pane_grid/title_bar.rs | 5 +- widget/src/pick_list.rs | 3 +- widget/src/radio.rs | 3 +- widget/src/row.rs | 4 +- widget/src/scrollable.rs | 4 +- widget/src/slider.rs | 3 +- widget/src/text_input.rs | 311 ++++++++++++++++++++++++----- widget/src/text_input/ime_state.rs | 96 +++++++++ widget/src/toggler.rs | 3 + widget/src/tooltip.rs | 4 + widget/src/vertical_slider.rs | 3 + winit/src/application.rs | 24 ++- winit/src/conversion.rs | 17 ++ winit/src/ime.rs | 165 +++++++++++++++ winit/src/lib.rs | 2 + 42 files changed, 774 insertions(+), 72 deletions(-) create mode 100644 core/src/ime.rs create mode 100644 runtime/src/ime.rs create mode 100644 widget/src/text_input/ime_state.rs create mode 100644 winit/src/ime.rs diff --git a/core/src/element.rs b/core/src/element.rs index 98c5373786..7121f0d984 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -1,10 +1,10 @@ use crate::event::{self, Event}; -use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget; use crate::widget::tree::{self, Tree}; +use crate::{layout, IME}; use crate::{ Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget, }; @@ -381,6 +381,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, B>, ) -> event::Status { let mut local_messages = Vec::new(); @@ -393,6 +394,7 @@ where cursor_position, renderer, clipboard, + ime, &mut local_shell, ); @@ -524,6 +526,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.element.widget.on_event( @@ -533,6 +536,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) } diff --git a/core/src/ime.rs b/core/src/ime.rs new file mode 100644 index 0000000000..a82bf683bd --- /dev/null +++ b/core/src/ime.rs @@ -0,0 +1,61 @@ +//! Access the IME. + +/// +pub trait IME { + /// + fn set_ime_position(&self, x: i32, y: i32); + + /// need to call if clicked position is widget's region. + /// + /// IME willbe enabled. + fn inside(&self); + + /// need to call if clicked position is not widget's region. + /// + /// used to determine disable ime. + fn outside(&self); + + /// disable IME. + /// + fn password_mode(&self); + + /// force ime enabled or disabled. + /// + /// this will block request until unlock_set_ime_allowed. + fn force_set_ime_allowed(&self, allowed: bool); + /// remove request of force_set_ime_allowed + /// + fn unlock_set_ime_allowed(&self); + + #[cfg(target_os = "macos")] + /// macos's strange behavior of set_ime_position workaround. + /// + /// on macos we can't move cadidate window by set_ime_position. + /// + /// we set ime candidate window position by these steps when IME::Commit event processed. + /// + /// * disable IME + /// * set candidate position + /// * enable IME + fn set_ime_position_with_reenable(&self, x: i32, y: i32); +} + +/// A null implementation of the [`IME`] trait. +#[derive(Debug, Clone, Copy)] +pub struct Null; + +impl IME for Null { + fn set_ime_position(&self, _x: i32, _y: i32) {} + + fn outside(&self) {} + + fn password_mode(&self) {} + + fn inside(&self) {} + + fn force_set_ime_allowed(&self, _: bool) {} + + fn unlock_set_ime_allowed(&self) {} + #[cfg(target_os = "macos")] + fn set_ime_position_with_reenable(&self, x: i32, y: i32) {} +} diff --git a/core/src/keyboard/event.rs b/core/src/keyboard/event.rs index 016761af37..cbac881ecd 100644 --- a/core/src/keyboard/event.rs +++ b/core/src/keyboard/event.rs @@ -6,7 +6,7 @@ use super::{KeyCode, Modifiers}; /// additional events, feel free to [open an issue] and share your use case!_ /// /// [open an issue]: https://github.com/iced-rs/iced/issues -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Event { /// A keyboard key was pressed. KeyPressed { @@ -31,4 +31,13 @@ pub enum Event { /// The keyboard modifiers have changed. ModifiersChanged(Modifiers), + + /// IME enabled. + IMEEnabled, + + /// selecting input. + IMEPreedit(String, Option<(usize, usize)>), + + /// enter input. + IMECommit(String), } diff --git a/core/src/lib.rs b/core/src/lib.rs index 6de5ada436..d53e1e3061 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -30,6 +30,7 @@ pub mod event; pub mod font; pub mod gradient; pub mod image; +pub mod ime; pub mod keyboard; pub mod layout; pub mod mouse; @@ -68,6 +69,7 @@ pub use event::Event; pub use font::Font; pub use gradient::Gradient; pub use hasher::Hasher; +pub use ime::IME; pub use layout::Layout; pub use length::Length; pub use overlay::Overlay; diff --git a/core/src/overlay.rs b/core/src/overlay.rs index b9f3c735a8..c1e99696dd 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -6,11 +6,11 @@ pub use element::Element; pub use group::Group; use crate::event::{self, Event}; -use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; use crate::widget::Tree; +use crate::{layout, IME}; use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; /// An interactive component that can be displayed on top of other widgets. @@ -69,6 +69,7 @@ where _cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, _shell: &mut Shell<'_, Message>, ) -> event::Status { event::Status::Ignored diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 237d25d175..ccb52dcdb3 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -1,10 +1,10 @@ pub use crate::Overlay; use crate::event::{self, Event}; -use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; +use crate::{layout, IME}; use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; use std::any::Any; @@ -71,6 +71,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.overlay.on_event( @@ -79,6 +80,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) } @@ -218,6 +220,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, B>, ) -> event::Status { let mut local_messages = Vec::new(); @@ -229,6 +232,7 @@ where cursor_position, renderer, clipboard, + ime, &mut local_shell, ); diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index 0c48df34a7..7f3ac13b08 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -4,6 +4,7 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget; +use crate::IME; use crate::{Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size}; /// An [`Overlay`] container that displays multiple overlay [`overlay::Element`] @@ -84,6 +85,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.children @@ -96,6 +98,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) }) diff --git a/core/src/widget.rs b/core/src/widget.rs index 769f86591b..37ed915370 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -12,9 +12,9 @@ pub use tree::Tree; use crate::event::{self, Event}; use crate::layout::{self, Layout}; -use crate::mouse; use crate::overlay; use crate::renderer; +use crate::{mouse, IME}; use crate::{Clipboard, Length, Point, Rectangle, Shell}; /// A component that displays information and allows interaction. @@ -114,6 +114,7 @@ where _cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, _shell: &mut Shell<'_, Message>, ) -> event::Status { event::Status::Ignored diff --git a/runtime/src/command/action.rs b/runtime/src/command/action.rs index 6c74f0efb2..0863772199 100644 --- a/runtime/src/command/action.rs +++ b/runtime/src/command/action.rs @@ -1,6 +1,7 @@ use crate::clipboard; use crate::core::widget; use crate::font; +use crate::ime; use crate::system; use crate::window; @@ -21,6 +22,9 @@ pub enum Action { /// Run a clipboard action. Clipboard(clipboard::Action), + /// Run a IME releated action. + IME(ime::Action), + /// Run a window action. Window(window::Action), @@ -57,6 +61,7 @@ impl Action { match self { Self::Future(future) => Action::Future(Box::pin(future.map(f))), Self::Clipboard(action) => Action::Clipboard(action.map(f)), + Self::IME(action) => Action::IME(action), Self::Window(window) => Action::Window(window.map(f)), Self::System(system) => Action::System(system.map(f)), Self::Widget(operation) => { @@ -77,6 +82,9 @@ impl fmt::Debug for Action { Self::Clipboard(action) => { write!(f, "Action::Clipboard({action:?})") } + Self::IME(action) => { + write!(f, "Action::IME({action:?})") + } Self::Window(action) => write!(f, "Action::Window({action:?})"), Self::System(action) => write!(f, "Action::System({action:?})"), Self::Widget(_action) => write!(f, "Action::Widget"), diff --git a/runtime/src/ime.rs b/runtime/src/ime.rs new file mode 100644 index 0000000000..2df8909532 --- /dev/null +++ b/runtime/src/ime.rs @@ -0,0 +1,28 @@ +//! Access the IME + +use std::fmt; + +/// A IME action to be performed by some [`Command`]. +/// +/// [`Command`]: crate::Command +pub enum Action { + /// + Allow(bool), + + /// + Position(i32, i32), + /// + Unlock, +} + +impl fmt::Debug for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::Allow(_) => { + write!(f, "Action::Allow") + } + Action::Position(_, _) => write!(f, "Action::SetPosition"), + Action::Unlock => write!(f, "Action::Unlock"), + } + } +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 50abf7b27e..744962529a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -46,6 +46,7 @@ pub mod clipboard; pub mod command; pub mod font; +pub mod ime; pub mod keyboard; pub mod program; pub mod system; diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index 2fa9934d8f..cc83c1627c 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -1,7 +1,7 @@ use crate::core::event::{self, Event}; use crate::core::mouse; use crate::core::renderer; -use crate::core::{Clipboard, Point, Size}; +use crate::core::{Clipboard, Point, Size, IME}; use crate::user_interface::{self, UserInterface}; use crate::{Command, Debug, Program}; @@ -93,6 +93,7 @@ where theme: &::Theme, style: &renderer::Style, clipboard: &mut dyn Clipboard, + ime: &dyn IME, debug: &mut Debug, ) -> (Vec, Option>) { let mut user_interface = build_user_interface( @@ -111,6 +112,7 @@ where cursor_position, renderer, clipboard, + ime, &mut messages, ); diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index d9206134f1..8daed8a6d6 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -1,4 +1,6 @@ //! Implement your own event loop to drive a user interface. +use iced_core::IME; + use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -176,6 +178,7 @@ where cursor_position: Point, renderer: &mut Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, messages: &mut Vec, ) -> (State, Vec) { use std::mem::ManuallyDrop; @@ -206,6 +209,7 @@ where cursor_position, renderer, clipboard, + ime, &mut shell, ); @@ -290,6 +294,7 @@ where base_cursor, renderer, clipboard, + ime, &mut shell, ); diff --git a/widget/src/button.rs b/widget/src/button.rs index 7eee69cbae..ad8d4352a8 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -11,7 +11,7 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::widget::Operation; use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Padding, Point, - Rectangle, Shell, Vector, Widget, + Rectangle, Shell, Vector, Widget, IME, }; pub use iced_style::button::{Appearance, StyleSheet}; @@ -190,6 +190,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { if let event::Status::Captured = self.content.as_widget_mut().on_event( @@ -199,6 +200,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) { return event::Status::Captured; diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 171c4534d8..ca79c558c1 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -17,7 +17,7 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; use crate::core::{Clipboard, Element, Shell, Widget}; -use crate::core::{Length, Point, Rectangle, Size, Vector}; +use crate::core::{Length, Point, Rectangle, Size, Vector, IME}; use crate::graphics::geometry; use std::marker::PhantomData; @@ -147,6 +147,7 @@ where cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { let bounds = layout.bounds(); diff --git a/widget/src/canvas/event.rs b/widget/src/canvas/event.rs index 4508c18453..4f5c47d8b3 100644 --- a/widget/src/canvas/event.rs +++ b/widget/src/canvas/event.rs @@ -8,7 +8,7 @@ pub use crate::core::event::Status; /// A [`Canvas`] event. /// /// [`Canvas`]: crate::widget::Canvas -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum Event { /// A mouse event. Mouse(mouse::Event), diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 7d43bb4a1e..21b6f25ee7 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -9,7 +9,7 @@ use crate::core::touch; use crate::core::widget::Tree; use crate::core::{ Alignment, Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, - Shell, Widget, + Shell, Widget, IME, }; use crate::{Row, Text}; @@ -207,6 +207,7 @@ where cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { match event { diff --git a/widget/src/column.rs b/widget/src/column.rs index 8f363ec65f..0a0b55d9e3 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -7,7 +7,7 @@ use crate::core::renderer; use crate::core::widget::{Operation, Tree}; use crate::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Widget, + Rectangle, Shell, Widget, IME, }; /// A container that distributes its contents vertically. @@ -169,6 +169,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.children @@ -183,6 +184,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) }) diff --git a/widget/src/container.rs b/widget/src/container.rs index 9d932772d1..65a305608e 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -8,7 +8,7 @@ use crate::core::renderer; use crate::core::widget::{self, Operation, Tree}; use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, - Point, Rectangle, Shell, Widget, + Point, Rectangle, Shell, Widget, IME, }; pub use iced_style::container::{Appearance, StyleSheet}; @@ -199,6 +199,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.content.as_widget_mut().on_event( @@ -208,6 +209,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) } diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 0d60d81829..b7843fc8b3 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -7,7 +7,7 @@ use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Clipboard, Element, Layout, Length, Pixels, Point, Rectangle, Shell, Size, - Vector, Widget, + Vector, Widget, IME, }; use std::hash::Hash; @@ -147,6 +147,7 @@ where cursor_position: Point, renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, _shell: &mut Shell<'_, Message>, ) -> event::Status { let bounds = layout.bounds(); diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 0ad4686528..9638de7d1e 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -18,7 +18,7 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Widget}; use crate::core::Element; use crate::core::{ - self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size, + self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size, IME, }; use ouroboros::self_referencing; @@ -184,6 +184,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.with_element_mut(|element| { @@ -194,6 +195,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) }) @@ -374,6 +376,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.with_overlay_mut_maybe(|overlay| { @@ -383,6 +386,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) }) diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index 49ae68aff1..7950d9577d 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -8,6 +8,7 @@ use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, + IME, }; use ouroboros::self_referencing; @@ -268,6 +269,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { let mut local_messages = Vec::new(); @@ -282,6 +284,7 @@ where cursor_position, renderer, clipboard, + ime, &mut local_shell, ) }); @@ -601,6 +604,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { let mut local_messages = Vec::new(); @@ -614,6 +618,7 @@ where cursor_position, renderer, clipboard, + ime, &mut local_shell, ) }) diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index b41d978b03..9a2136ceba 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -7,6 +7,7 @@ use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, + IME, }; use crate::horizontal_space; @@ -179,6 +180,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { let state = tree.state.downcast_mut::(); @@ -200,6 +202,7 @@ where cursor_position, renderer, clipboard, + ime, &mut local_shell, ) }, @@ -415,6 +418,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.with_overlay_mut_maybe(|overlay| { @@ -424,6 +428,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) }) diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 0232c4947c..a697e740c9 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -8,7 +8,7 @@ use crate::core::renderer; use crate::core::touch; use crate::core::widget::{tree, Operation, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Widget, + Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Widget, IME, }; /// Emit messages on mouse events. @@ -149,6 +149,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { if let event::Status::Captured = self.content.as_widget_mut().on_event( @@ -158,6 +159,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) { return event::Status::Captured; diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 0acc6f7901..f3e9995026 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -11,6 +11,7 @@ use crate::core::touch; use crate::core::widget::Tree; use crate::core::{ Clipboard, Color, Length, Padding, Pixels, Point, Rectangle, Size, Vector, + IME, }; use crate::core::{Element, Shell, Widget}; use crate::scrollable::{self, Scrollable}; @@ -262,6 +263,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.container.on_event( @@ -271,6 +273,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) } @@ -390,6 +393,7 @@ where cursor_position: Point, renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, _shell: &mut Shell<'_, Message>, ) -> event::Status { match event { diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index e6ffb1d6da..55cffad9ce 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -43,7 +43,7 @@ use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, - Size, Vector, Widget, + Size, Vector, Widget, IME, }; /// A collection of panes distributed using either vertical or horizontal splits @@ -316,6 +316,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { let action = tree.state.downcast_mut::(); @@ -356,6 +357,7 @@ where cursor_position, renderer, clipboard, + ime, shell, is_picked, ) diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index 035ef05b4e..9cac7e41df 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -5,7 +5,9 @@ use crate::core::mouse; use crate::core::overlay; use crate::core::renderer; use crate::core::widget::{self, Tree}; -use crate::core::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; +use crate::core::{ + Clipboard, Element, Layout, Point, Rectangle, Shell, Size, IME, +}; use crate::pane_grid::{Draggable, TitleBar}; /// The content of a [`Pane`]. @@ -221,6 +223,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, is_picked: bool, ) -> event::Status { @@ -236,6 +239,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ); @@ -254,6 +258,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) }; diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 2129937be8..0823f91159 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -6,7 +6,7 @@ use crate::core::overlay; use crate::core::renderer; use crate::core::widget::{self, Tree}; use crate::core::{ - Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, + Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, IME, }; /// The title bar of a [`Pane`]. @@ -303,6 +303,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { let mut children = layout.children(); @@ -327,6 +328,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) } else { @@ -341,6 +343,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) } else { diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 8c445dda9b..cd81b0f79b 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -12,7 +12,7 @@ use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle, - Shell, Size, Widget, + Shell, Size, Widget, IME, }; use crate::overlay::menu::{self, Menu}; use crate::scrollable; @@ -199,6 +199,7 @@ where cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { update( diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 9dad1e22a2..716c8fd516 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -9,7 +9,7 @@ use crate::core::touch; use crate::core::widget::Tree; use crate::core::{ Alignment, Clipboard, Color, Element, Layout, Length, Pixels, Point, - Rectangle, Shell, Widget, + Rectangle, Shell, Widget, IME, }; use crate::{Row, Text}; @@ -232,6 +232,7 @@ where cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { match event { diff --git a/widget/src/row.rs b/widget/src/row.rs index 3ce363f8d6..670e5ed2c6 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -7,7 +7,7 @@ use crate::core::renderer; use crate::core::widget::{Operation, Tree}; use crate::core::{ Alignment, Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, - Shell, Widget, + Shell, Widget, IME, }; /// A container that distributes its contents horizontally. @@ -158,6 +158,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.children @@ -172,6 +173,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) }) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index fd51a6a8b5..0c263fde37 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -11,7 +11,7 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, + Rectangle, Shell, Size, Vector, Widget, IME, }; use crate::runtime::Command; @@ -227,6 +227,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { update( @@ -247,6 +248,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) }, diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 18a4966508..cbdad7bcc4 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -9,7 +9,7 @@ use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell, - Size, Widget, + Size, Widget, IME, }; use std::ops::RangeInclusive; @@ -186,6 +186,7 @@ where cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { update( diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index bbc07dac07..79326cece2 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -2,6 +2,7 @@ //! //! A [`TextInput`] has some local [`State`]. mod editor; +mod ime_state; mod value; pub mod cursor; @@ -26,12 +27,14 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, + Rectangle, Shell, Size, Vector, Widget, IME, }; use crate::runtime::Command; pub use iced_style::text_input::{Appearance, StyleSheet}; +use self::ime_state::IMEState; + /// A field that can be filled with text. /// /// # Example @@ -301,6 +304,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { update( @@ -309,6 +313,7 @@ where cursor_position, renderer, clipboard, + ime, shell, &mut self.value, self.size, @@ -531,6 +536,7 @@ pub fn update<'a, Message, Renderer>( cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, value: &mut Value, size: Option, @@ -546,12 +552,22 @@ where Message: Clone, Renderer: text::Renderer, { + let state = state(); + if state.is_focused.is_some() { + if is_secure { + ime.password_mode() + } else { + ime.inside() + } + } match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { - let state = state(); let is_clicked = layout.bounds().contains(cursor_position) && on_input.is_some(); + // if gain focus enable ime + let focus_gained = state.is_focused.is_none() && is_clicked; + let focus_lost = state.is_focused.is_some() && !is_clicked; state.is_focused = if is_clicked { state.is_focused.or_else(|| { @@ -638,19 +654,52 @@ where } state.last_click = Some(click); + if !is_secure && focus_gained { + let position = state.cursor.start(value); + + // calcurate where we need to place candidate window. + let size = size.unwrap_or_else(|| renderer.default_size()); + + let position = measure_cursor_and_scroll_offset( + renderer, + text_layout.bounds(), + value, + size, + position, + font.unwrap_or_else(|| { + Renderer::default_font(renderer) + }), + ); + let position = ( + (text_layout.bounds().x + position.0) as i32, + (text_layout.bounds().y) as i32 + size as i32, + ); + ime.set_ime_position(position.0, position.1); + } return event::Status::Captured; + } else if focus_lost { + let mut editor = Editor::new(value, &mut state.cursor); + ime.outside(); + if let Some((old_ime_state, on_input)) = + state.ime_state.take().zip(on_input) + { + old_ime_state + .preedit_text() + .chars() + .for_each(|ch| editor.insert(ch)); + let message = (on_input)(editor.contents()); + shell.publish(message); + } } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) | Event::Touch(touch::Event::FingerLost { .. }) => { - state().is_dragging = false; + state.is_dragging = false; } Event::Mouse(mouse::Event::CursorMoved { position }) | Event::Touch(touch::Event::FingerMoved { position, .. }) => { - let state = state(); - if state.is_dragging { let text_layout = layout.children().next().unwrap(); let target = position.x - text_layout.bounds().x; @@ -681,8 +730,6 @@ where } } Event::Keyboard(keyboard::Event::CharacterReceived(c)) => { - let state = state(); - if let Some(focus) = &mut state.is_focused { let Some(on_input) = on_input else { return event::Status::Ignored }; @@ -704,8 +751,6 @@ where } } Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => { - let state = state(); - if let Some(focus) = &mut state.is_focused { let Some(on_input) = on_input else { return event::Status::Ignored }; @@ -890,8 +935,6 @@ where } } Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. }) => { - let state = state(); - if state.is_focused.is_some() { match key_code { keyboard::KeyCode::V => { @@ -911,13 +954,9 @@ where } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - let state = state(); - state.keyboard_modifiers = modifiers; } Event::Window(window::Event::RedrawRequested(now)) => { - let state = state(); - if let Some(focus) = &mut state.is_focused { focus.now = now; @@ -930,6 +969,93 @@ where )); } } + Event::Keyboard(keyboard::Event::IMECommit(text)) => { + if let Some(on_input) = (state.is_pasting.is_none() + && !state.keyboard_modifiers.command() + && !is_secure + && state.is_focused.is_some() + && state.ime_state.is_some()) + .then(|| on_input) + .flatten() + { + let mut editor = Editor::new(value, &mut state.cursor); + + for ch in text.chars() { + editor.insert(ch); + } + + let message = (on_input)(editor.contents()); + shell.publish(message); + #[cfg(target_os = "macos")] + { + let text_bounds = + layout.children().next().unwrap().bounds(); + let size = size.unwrap_or_else(|| renderer.default_size()); + + let width = renderer.measure_width( + &editor.contents(), + size, + font.clone(), + ); + let (x, y) = ( + (text_bounds.x + width) as i32, + (text_bounds.y) as i32 + size as i32, + ); + ime.set_ime_position_with_reenable(x, y); + } + state.ime_state = None; + return event::Status::Captured; + } + } + + Event::Keyboard(keyboard::Event::IMEEnabled) => { + if state.is_focused.is_some() { + let _ = state.ime_state.replace(IMEState::default()); + return event::Status::Captured; + } + } + Event::Keyboard(keyboard::Event::IMEPreedit(text, range)) => { + if state.is_focused.is_none() || is_secure { + return event::Status::Ignored; + } + // calcurate where we need to place candidate window. + let text_bounds = layout.children().next().unwrap().bounds(); + let size = size.unwrap_or_else(|| renderer.default_size()); + + let chars_from_left = state.cursor.start(value); + + let before_preedit_text = value + .to_string() + .chars() + .take(chars_from_left) + .fold(String::new(), |mut buffer, ch| { + buffer.push(ch); + buffer + }); + + let position = renderer.measure_width( + &(before_preedit_text + &text), + size, + font.unwrap_or_else(|| Renderer::default_font(renderer)), + text::Shaping::Advanced, + ); + + if let Some(ime_state) = state.ime_state.as_mut() { + ime_state.set_event(text, range); + } else { + let mut new_state = IMEState::default(); + new_state.set_event(text, range); + let _ = state.ime_state.replace(new_state); + } + + let position = ( + (text_bounds.x + position) as i32, + (text_bounds.y) as i32 + size as i32, + ); + ime.set_ime_position(position.0, position.1); + + return event::Status::Captured; + } _ => {} } @@ -1012,16 +1138,74 @@ pub fn draw( let font = font.unwrap_or_else(|| renderer.default_font()); let size = size.unwrap_or_else(|| renderer.default_size()); + let color = if text.is_empty() + && state + .ime_state + .as_ref() + .map_or(true, |state| state.preedit_text().is_empty()) + { + theme.placeholder_color(style) + } else if is_disabled { + theme.disabled_color(style) + } else { + theme.value_color(style) + }; + + let (render_text, before_preedit_text) = if let Some(ime_state) = + state.ime_state.as_ref() + { + let mut text = text; + let chars_from_left = state.cursor.start(value); + let before_preedit_text = text.chars().take(chars_from_left).fold( + String::new(), + |mut buffer, ch| { + buffer.push(ch); + buffer + }, + ); + + text.insert_str(before_preedit_text.len(), ime_state.preedit_text()); + + (text, before_preedit_text) + } else if text.is_empty() { + (placeholder.to_owned(), String::new()) + } else { + (text.clone(), String::new()) + }; + // display preedit text and exist text concated. + // cursor position need to concated also. + + let (preedit_cursor_index, preedit_value) = + if let Some(ime_state) = state.ime_state.as_ref() { + ( + ime_state.before_cursor_text().chars().count(), + Value::new(ime_state.preedit_text()), + ) + } else { + (0, Value::new("")) + }; + + let mut value = value.clone(); + + value.insert_many(state.cursor.start(&value), preedit_value); + + let text_width = renderer.measure_width( + &render_text, + size, + font.clone(), + text::Shaping::Advanced, + ); + let (cursor, offset) = if let Some(focus) = &state.is_focused { - match state.cursor.state(value) { + match state.cursor.state(&value) { cursor::State::Index(position) => { let (text_value_width, offset) = measure_cursor_and_scroll_offset( renderer, text_bounds, - value, + &value, size, - position, + position + preedit_cursor_index, font, ); @@ -1060,9 +1244,9 @@ pub fn draw( measure_cursor_and_scroll_offset( renderer, text_bounds, - value, + &value, size, - left, + left + preedit_cursor_index, font, ); @@ -1070,9 +1254,9 @@ pub fn draw( measure_cursor_and_scroll_offset( renderer, text_bounds, - value, + &value, size, - right, + right + preedit_cursor_index, font, ); @@ -1105,41 +1289,72 @@ pub fn draw( (None, 0.0) }; - let text_width = renderer.measure_width( - if text.is_empty() { placeholder } else { &text }, - size, - font, - text::Shaping::Advanced, - ); - let render = |renderer: &mut Renderer| { - if let Some((cursor, color)) = cursor { - renderer.fill_quad(cursor, color); - } else { - renderer.with_translation(Vector::ZERO, |_| {}); - } - - renderer.fill_text(Text { - content: if text.is_empty() { placeholder } else { &text }, - color: if text.is_empty() { - theme.placeholder_color(style) - } else if is_disabled { - theme.disabled_color(style) - } else { - theme.value_color(style) - }, - font, + // ime mod should not paint selection + let fill_text = Text { + content: &render_text, + color, + font: font.clone(), bounds: Rectangle { y: text_bounds.center_y(), width: f32::INFINITY, ..text_bounds }, size, - line_height, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, + line_height, shaping: text::Shaping::Advanced, - }); + }; + if let Some(ime_state) = state.ime_state.as_ref() { + // draw under line. + + let before_preedit_text_width = renderer.measure_width( + &before_preedit_text, + size, + font.clone(), + text::Shaping::Advanced, + ); + + let splits = ime_state.split_to_pieces(); + + let _ = splits.iter().enumerate().fold( + text_bounds.x + before_preedit_text_width, + |offset, (idx, t)| { + if let Some(t) = t { + let width = renderer.measure_width( + t, + size, + font.clone(), + text::Shaping::Advanced, + ); + let quad = renderer::Quad { + bounds: Rectangle { + x: offset, + y: text_bounds.y + size, + width, + height: if idx == 1 { 3.0 } else { 1.0 }, + }, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::default(), + }; + renderer.fill_quad(quad, theme.value_color(style)); + width + offset + } else { + offset + } + }, + ); + } + + if let Some((cursor, color)) = cursor { + renderer.fill_quad(cursor, color); + } else { + renderer.with_translation(Vector::ZERO, |_| {}); + } + + renderer.fill_text(fill_text); }; if text_width > text_bounds.width { @@ -1174,6 +1389,7 @@ pub struct State { is_focused: Option, is_dragging: bool, is_pasting: Option, + ime_state: Option, last_click: Option, cursor: Cursor, keyboard_modifiers: keyboard::Modifiers, @@ -1201,6 +1417,7 @@ impl State { last_click: None, cursor: Cursor::default(), keyboard_modifiers: keyboard::Modifiers::default(), + ime_state: None, } } diff --git a/widget/src/text_input/ime_state.rs b/widget/src/text_input/ime_state.rs new file mode 100644 index 0000000000..f845bfa83a --- /dev/null +++ b/widget/src/text_input/ime_state.rs @@ -0,0 +1,96 @@ +/// +/// +#[derive(Debug, Default, Clone)] +pub struct IMEState { + preedit_text: String, + candidate_indicator: Option, +} +#[derive(Debug, Clone, Copy)] +enum CandidateIndicator { + // indicate like Windows + BoldLine(usize, usize), + // indicate like IBus-MOZC + Cursor(usize), +} + +impl IMEState { + pub fn preedit_text(&self) -> &str { + &self.preedit_text + } + pub fn set_event( + &mut self, + preedit_text: String, + range: Option<(usize, usize)>, + ) { + self.preedit_text = preedit_text; + // first we need to align to char boundary. + // + // ibus report incorrect charboundary for japanese input + + self.candidate_indicator = range.map(|(start, end)| { + // utf-8 is 1 to 4 byte variable length encoding so we try +3 byte. + let left = start.min(end); + let right = end.clamp(start, self.preedit_text.len()); + let start_byte = (0..left + 1) + .rfind(|index| self.preedit_text.is_char_boundary(*index)); + let end_byte = (right..right + 4) + .find(|index| self.preedit_text.is_char_boundary(*index)); + if let Some((start, end)) = start_byte.zip(end_byte) { + if start == end { + CandidateIndicator::Cursor(start) + } else { + CandidateIndicator::BoldLine(start, end) + } + } else { + CandidateIndicator::Cursor(self.preedit_text.len()) + } + }); + } + /// split text to three section of texts. + + /// * 1st section = light line text section. + /// * 2nd section = bold line text section. + /// * 3rd section = light line text section that after 2nd section. + pub fn split_to_pieces(&self) -> [Option<&str>; 3] { + let text = self.preedit_text.as_str(); + match self.candidate_indicator { + Some(CandidateIndicator::BoldLine(start, end)) => { + //split to three section. + + let (first, second_and_third) = if start < text.len() { + text.split_at(start) + } else { + (text, "") + }; + + let (second, third) = if end < text.len() { + second_and_third.split_at(end - start) + } else { + (second_and_third, "") + }; + [Some(first), Some(second), Some(third)] + } + Some(CandidateIndicator::Cursor(_)) => [Some(text), None, None], + None => [Some(text), None, None], + } + } + + pub fn before_cursor_text(&self) -> &str { + let text = &self.preedit_text; + if let Some(indicator) = self.candidate_indicator { + match indicator { + CandidateIndicator::BoldLine(_, _) => text, + CandidateIndicator::Cursor(position) => { + let (a, b) = text.split_at(position); + if a.is_empty() & cfg!(windows) { + b + } else { + a + } + } + } + } else { + text + } + } +} diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index b1ba65c95b..87a51b6fa7 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -1,4 +1,6 @@ //! Show toggle controls using togglers. +use iced_renderer::core::IME; + use crate::core::alignment; use crate::core::event; use crate::core::layout; @@ -205,6 +207,7 @@ where cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { match event { diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 084650d104..e8670ee0cd 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -1,4 +1,6 @@ //! Display a widget over another. +use iced_renderer::core::IME; + use crate::container; use crate::core; use crate::core::event::{self, Event}; @@ -139,6 +141,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.content.as_widget_mut().on_event( @@ -148,6 +151,7 @@ where cursor_position, renderer, clipboard, + ime, shell, ) } diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 2635611db6..bf823181bb 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -3,6 +3,8 @@ //! A [`VerticalSlider`] has some local [`State`]. use std::ops::RangeInclusive; +use iced_renderer::core::IME; + pub use crate::style::slider::{Appearance, Handle, HandleShape, StyleSheet}; use crate::core; @@ -183,6 +185,7 @@ where cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { update( diff --git a/winit/src/application.rs b/winit/src/application.rs index 3d7c6e5d84..de31b0580d 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -17,11 +17,12 @@ use crate::futures::futures; use crate::futures::{Executor, Runtime, Subscription}; use crate::graphics::compositor::{self, Compositor}; use crate::runtime::clipboard; +use crate::runtime::ime; use crate::runtime::program::Program; use crate::runtime::user_interface::{self, UserInterface}; use crate::runtime::{Command, Debug}; use crate::style::application::{Appearance, StyleSheet}; -use crate::{Clipboard, Error, Proxy, Settings}; +use crate::{Clipboard, Error, Proxy, Settings, IME}; use futures::channel::mpsc; @@ -294,6 +295,7 @@ async fn run_instance( let physical_size = state.physical_size(); let mut clipboard = Clipboard::connect(&window); + let ime = IME::new(); let mut cache = user_interface::Cache::default(); let mut surface = compositor.create_surface( &window, @@ -314,6 +316,7 @@ async fn run_instance( init_command, &mut runtime, &mut clipboard, + &ime, &mut should_exit, &mut proxy, &mut debug, @@ -359,6 +362,7 @@ async fn run_instance( state.cursor_position(), &mut renderer, &mut clipboard, + &ime, &mut messages, ); @@ -386,6 +390,7 @@ async fn run_instance( &state, &mut renderer, &mut runtime, + &ime, &mut clipboard, &mut should_exit, &mut proxy, @@ -425,6 +430,7 @@ async fn run_instance( state.cursor_position(), &mut renderer, &mut clipboard, + &ime, &mut messages, ); @@ -446,7 +452,7 @@ async fn run_instance( mouse_interaction = new_mouse_interaction; } - + ime.apply_request(&window); window.request_redraw(); runtime.broadcast(redraw_event, core::event::Status::Ignored); @@ -651,6 +657,7 @@ pub fn update( state: &State, renderer: &mut A::Renderer, runtime: &mut Runtime, A::Message>, + ime: &IME, clipboard: &mut Clipboard, should_exit: &mut bool, proxy: &mut winit::event_loop::EventLoopProxy, @@ -682,6 +689,7 @@ pub fn update( command, runtime, clipboard, + ime, should_exit, proxy, debug, @@ -703,6 +711,7 @@ pub fn run_command( command: Command, runtime: &mut Runtime, A::Message>, clipboard: &mut Clipboard, + ime: &IME, should_exit: &mut bool, proxy: &mut winit::event_loop::EventLoopProxy, debug: &mut Debug, @@ -734,6 +743,17 @@ pub fn run_command( clipboard.write(contents); } }, + command::Action::IME(action) => match action { + ime::Action::Allow(allow) => { + ime.set_ime_allowed(allow); + } + ime::Action::Position(x, y) => { + ime.set_ime_position(x, y); + } + ime::Action::Unlock => { + ime.unlock_set_ime_allowed(); + } + }, command::Action::Window(action) => match action { window::Action::Close => { *should_exit = true; diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index a926218470..58045dc146 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -136,6 +136,23 @@ pub fn window_event( Some(Event::Window(window::Event::Moved { x, y })) } + WindowEvent::Ime(ime) => match ime { + winit::event::Ime::Enabled => { + Some(Event::Keyboard(keyboard::Event::IMEEnabled)) + } + winit::event::Ime::Preedit(text, range) => { + // range parameter is used to mark converting position. + + Some(Event::Keyboard(keyboard::Event::IMEPreedit( + text.clone(), + *range, + ))) + } + winit::event::Ime::Commit(text) => { + Some(Event::Keyboard(keyboard::Event::IMECommit(text.clone()))) + } + winit::event::Ime::Disabled => None, + }, _ => None, } } diff --git a/winit/src/ime.rs b/winit/src/ime.rs new file mode 100644 index 0000000000..f141a48c31 --- /dev/null +++ b/winit/src/ime.rs @@ -0,0 +1,165 @@ +//! Access to winit ime related things. +use std::sync::RwLock; + +use iced_runtime::{command, ime::Action, Command}; +use winit::window::Window; + +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +enum RequestKind { + Outside, + Inside, + Password, + #[cfg(target_os = "macos")] + SetPositionWithReenable(i32, i32), +} + +/// IME related setting interface. +/// +/// need to recreate every frame. +/// +/// when `application::update` and `UserInterface::update` finished ,call `change_ime_enabled_or_disable`. + +#[derive(Debug, Default)] +pub struct IME { + requests: RwLock>, + force: RwLock>, + position: RwLock>, +} + +impl IME { + #[must_use] + /// create manager. + pub fn new() -> Self { + Self::default() + } + + /// Send IME enable or disable position update message to winit. + /// + /// + pub fn apply_request(&self, window: &Window) { + if let Ok(force) = self.force.read() { + if let Some(allowed) = *force { + window.set_ime_allowed(allowed); + } else { + if let Ok(requests) = self.requests.read() { + #[cfg(target_os = "macos")] + if let Some(RequestKind::SetPositionWithReenable(x, y)) = + requests.iter().find(|kind| { + matches!( + kind, + RequestKind::SetPositionWithReenable(_, _) + ) + }) + { + window.set_ime_allowed(false); + window.set_ime_position(winit::dpi::LogicalPosition { + x: *x, + y: *y, + }); + window.set_ime_allowed(true); + } + // this is needed to eliminate exisiting buffer of IME. + // if this block is absent preedit of other text input is copied. + if requests + .iter() + .any(|kind| matches!(kind, RequestKind::Outside)) + { + window.set_ime_allowed(false); + } + } + if let Ok(mut requests) = self.requests.write() { + if !requests.is_empty() { + let allowed = + requests.drain(..).fold(false, |sum, x| { + sum | matches!(x, RequestKind::Inside) + }); + window.set_ime_allowed(allowed); + } + } + } + } + + if let Ok(mut position) = self.position.write() { + if let Some((x, y)) = position.take() { + window.set_ime_position(winit::dpi::LogicalPosition { x, y }); + } + } + } +} +impl IME { + /// allow input by ime or not. + pub fn set_ime_allowed(&self, allowed: bool) { + if let Ok(mut guard) = self.force.write() { + *guard = Some(allowed); + } + } + + /// set the logical position of IME candidate window. + pub fn set_ime_position(&self, x: i32, y: i32) { + if let Ok(mut guard) = self.position.write() { + *guard = Some((x, y)); + } + } + + /// remove the rquest of `set_ime_allowed` + pub fn unlock_set_ime_allowed(&self) { + if let Ok(mut guard) = self.force.write() { + *guard = None; + } + } +} + +impl crate::core::ime::IME for IME { + fn set_ime_position(&self, x: i32, y: i32) { + self.set_ime_position(x, y); + } + + fn inside(&self) { + if let Ok(mut guard) = self.requests.write() { + guard.push(RequestKind::Inside); + }; + } + + fn outside(&self) { + if let Ok(mut guard) = self.requests.write() { + guard.push(RequestKind::Outside); + }; + } + + /// disable IME. + /// + fn password_mode(&self) { + if let Ok(mut guard) = self.requests.write() { + guard.push(RequestKind::Password); + }; + } + + fn force_set_ime_allowed(&self, allowed: bool) { + self.set_ime_allowed(allowed); + } + + fn unlock_set_ime_allowed(&self) { + self.unlock_set_ime_allowed(); + } + #[cfg(target_os = "macos")] + fn set_ime_position_with_reenable(&self, x: i32, y: i32) { + if let Ok(mut guard) = self.requests.write() { + guard.push(RequestKind::SetPositionWithReenable(x, y)) + } + } +} + +/// allow input by ime or not. +pub fn set_ime_allowed(allowed: bool) -> Command { + Command::single(command::Action::IME(Action::Allow(allowed))) +} + +/// allow input by ime or not. +pub fn unlock_set_ime_allowed() -> Command { + Command::single(command::Action::IME(Action::Unlock)) +} + +/// set the logical position of IME candidate window. +pub fn set_position(x: i32, y: i32) -> Command { + Command::single(command::Action::IME(Action::Position(x, y))) +} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 62d66d5ea0..51d9e8ebdd 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -41,6 +41,7 @@ pub use winit; pub mod application; pub mod clipboard; pub mod conversion; +pub mod ime; pub mod settings; #[cfg(feature = "system")] @@ -56,6 +57,7 @@ pub use application::Application; pub use application::Profiler; pub use clipboard::Clipboard; pub use error::Error; +pub use ime::IME; pub use position::Position; pub use proxy::Proxy; pub use settings::Settings; From e2de5de0431bef0878e3db0d0c6187ef2e5075ed Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Fri, 2 Jun 2023 13:50:34 +0900 Subject: [PATCH 02/17] fix needless clone --- examples/integration/src/main.rs | 7 ++-- examples/modal/src/main.rs | 6 +++- examples/toast/src/main.rs | 6 +++- src/advanced.rs | 2 +- widget/src/text_input.rs | 57 +++++++++++++++++--------------- 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index c935aca745..ed0fe3c3aa 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -11,7 +11,7 @@ use iced_winit::core::{Color, Size}; use iced_winit::runtime::program; use iced_winit::runtime::Debug; use iced_winit::style::Theme; -use iced_winit::{conversion, futures, winit, Clipboard}; +use iced_winit::{conversion, futures, winit, Clipboard, IME}; use winit::{ dpi::PhysicalPosition, @@ -61,7 +61,7 @@ pub fn main() -> Result<(), Box> { let mut cursor_position = PhysicalPosition::new(-1.0, -1.0); let mut modifiers = ModifiersState::default(); let mut clipboard = Clipboard::connect(&window); - + let ime = IME::new(); // Initialize wgpu #[cfg(target_arch = "wasm32")] let default_backend = wgpu::Backends::GL; @@ -202,9 +202,10 @@ pub fn main() -> Result<(), Box> { &Theme::Dark, &renderer::Style { text_color: Color::WHITE }, &mut clipboard, + &ime, &mut debug, ); - + ime.apply_request(&window); // and request a redraw window.request_redraw(); } diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 9e1e4c2fca..bf0764efff 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -180,7 +180,7 @@ mod modal { use iced::advanced::overlay; use iced::advanced::renderer; use iced::advanced::widget::{self, Widget}; - use iced::advanced::{self, Clipboard, Shell}; + use iced::advanced::{self, Clipboard, Shell, IME}; use iced::alignment::Alignment; use iced::event; use iced::mouse; @@ -257,6 +257,7 @@ mod modal { cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.base.as_widget_mut().on_event( @@ -266,6 +267,7 @@ mod modal { cursor_position, renderer, clipboard, + ime, shell, ) } @@ -380,6 +382,7 @@ mod modal { cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { let content_bounds = layout.children().next().unwrap().bounds(); @@ -403,6 +406,7 @@ mod modal { cursor_position, renderer, clipboard, + ime, shell, ) } diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 515218e7e7..b0713a8bd1 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -183,7 +183,7 @@ mod toast { use iced::advanced::overlay; use iced::advanced::renderer; use iced::advanced::widget::{self, Operation, Tree}; - use iced::advanced::{Clipboard, Shell, Widget}; + use iced::advanced::{Clipboard, Shell, Widget, IME}; use iced::event::{self, Event}; use iced::mouse; use iced::theme; @@ -399,6 +399,7 @@ mod toast { cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.content.as_widget_mut().on_event( @@ -408,6 +409,7 @@ mod toast { cursor_position, renderer, clipboard, + ime, shell, ) } @@ -526,6 +528,7 @@ mod toast { cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { if let Event::Window(window::Event::RedrawRequested(now)) = &event { @@ -575,6 +578,7 @@ mod toast { cursor_position, renderer, clipboard, + ime, &mut local_shell, ); diff --git a/src/advanced.rs b/src/advanced.rs index 2071aed04b..c7dba1bb22 100644 --- a/src/advanced.rs +++ b/src/advanced.rs @@ -7,7 +7,7 @@ pub use crate::core::renderer::{self, Renderer}; pub use crate::core::svg; pub use crate::core::text::{self, Text}; pub use crate::core::widget::{self, Widget}; -pub use crate::core::{Clipboard, Hasher, Shell}; +pub use crate::core::{Clipboard, Hasher, Shell, IME}; pub use crate::renderer::graphics; pub mod subscription { diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index b099847113..22646e1fd5 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -975,7 +975,7 @@ where && !is_secure && state.is_focused.is_some() && state.ime_state.is_some()) - .then(|| on_input) + .then_some(on_input) .flatten() { let mut editor = Editor::new(value, &mut state.cursor); @@ -1151,29 +1151,32 @@ pub fn draw( theme.value_color(style) }; - let (render_text, before_preedit_text) = if let Some(ime_state) = - state.ime_state.as_ref() - { - let mut text = text; - let chars_from_left = state.cursor.start(value); - let before_preedit_text = text.chars().take(chars_from_left).fold( - String::new(), - |mut buffer, ch| { - buffer.push(ch); - buffer - }, - ); + let (render_text, before_preedit_text_range) = + if let Some(ime_state) = state.ime_state.as_ref() { + let mut text = text; + let chars_from_left = + if state.cursor.start(value) < state.cursor.end(value) { + state.cursor.start(value) + 1 + } else { + state.cursor.start(value) + }; + let last_index = text + .char_indices() + .take(chars_from_left) + .last() + .map(|(idx, _)| idx) + .unwrap_or_default(); - text.insert_str(before_preedit_text.len(), ime_state.preedit_text()); + text.insert_str(last_index, ime_state.preedit_text()); - (text, before_preedit_text) - } else if text.is_empty() { - (placeholder.to_owned(), String::new()) - } else { - (text.clone(), String::new()) - }; - // display preedit text and exist text concated. - // cursor position need to concated also. + (text, (0, last_index)) + } else if text.is_empty() { + (placeholder.to_owned(), (0, 0)) + } else { + (text, (0, 0)) + }; + let before_preedit_text = + &render_text[before_preedit_text_range.0..before_preedit_text_range.1]; let (preedit_cursor_index, preedit_value) = if let Some(ime_state) = state.ime_state.as_ref() { @@ -1192,7 +1195,7 @@ pub fn draw( let text_width = renderer.measure_width( &render_text, size, - font.clone(), + font, text::Shaping::Advanced, ); @@ -1294,7 +1297,7 @@ pub fn draw( let fill_text = Text { content: &render_text, color, - font: font.clone(), + font, bounds: Rectangle { y: text_bounds.center_y(), width: f32::INFINITY, @@ -1310,9 +1313,9 @@ pub fn draw( // draw under line. let before_preedit_text_width = renderer.measure_width( - &before_preedit_text, + before_preedit_text, size, - font.clone(), + font, text::Shaping::Advanced, ); @@ -1325,7 +1328,7 @@ pub fn draw( let width = renderer.measure_width( t, size, - font.clone(), + font, text::Shaping::Advanced, ); let quad = renderer::Quad { From 91fe845e876b3f1fddc718079084cd543ddef23a Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Fri, 2 Jun 2023 13:54:05 +0900 Subject: [PATCH 03/17] fix macos build issue --- widget/src/text_input.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 22646e1fd5..b2ab3dafb0 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -995,7 +995,10 @@ where let width = renderer.measure_width( &editor.contents(), size, - font.clone(), + font.unwrap_or_else(|| { + Renderer::default_font(renderer) + }), + text::Shaping::Advanced, ); let (x, y) = ( (text_bounds.x + width) as i32, From 5b17793a837980b4512e2ecdcfd6bf84c8d25ff6 Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Fri, 2 Jun 2023 14:10:36 +0900 Subject: [PATCH 04/17] doc test fix --- runtime/src/user_interface.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 8daed8a6d6..341483120e 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -130,7 +130,7 @@ where /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } /// # pub fn update(&mut self, _: ()) {} /// # } - /// use iced_runtime::core::{clipboard, Size, Point}; + /// use iced_runtime::core::{clipboard,ime, Size, Point}; /// use iced_runtime::user_interface::{self, UserInterface}; /// use iced_wgpu::Renderer; /// @@ -140,7 +140,7 @@ where /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor_position = Point::default(); /// let mut clipboard = clipboard::Null; - /// + /// let ime = ime::Null; /// // Initialize our event storage /// let mut events = Vec::new(); /// let mut messages = Vec::new(); @@ -161,6 +161,7 @@ where /// cursor_position, /// &mut renderer, /// &mut clipboard, + /// &ime, /// &mut messages /// ); /// @@ -364,6 +365,7 @@ where /// # pub fn update(&mut self, _: ()) {} /// # } /// use iced_runtime::core::clipboard; + /// use iced_runtime::core::ime; /// use iced_runtime::core::renderer; /// use iced_runtime::core::{Element, Size, Point}; /// use iced_runtime::user_interface::{self, UserInterface}; @@ -375,6 +377,7 @@ where /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor_position = Point::default(); /// let mut clipboard = clipboard::Null; + /// let ime = ime::Null; /// let mut events = Vec::new(); /// let mut messages = Vec::new(); /// let mut theme = Theme::default(); @@ -395,6 +398,7 @@ where /// cursor_position, /// &mut renderer, /// &mut clipboard, + /// &ime, /// &mut messages /// ); /// From ed175529037378aba86000cc0303b232ff640e28 Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Wed, 21 Jun 2023 09:32:10 +0900 Subject: [PATCH 05/17] merge related fix --- core/src/element.rs | 9 +-------- core/src/overlay/element.rs | 11 ++--------- runtime/src/overlay/nested.rs | 7 +++++++ widget/src/text_input.rs | 4 ++-- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/core/src/element.rs b/core/src/element.rs index 953b1e7bd7..bd41ca471b 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -516,14 +516,7 @@ where shell: &mut Shell<'_, Message>, ) -> event::Status { self.element.widget.on_event( - state, - event, - layout, - cursor_position, - renderer, - clipboard, - ime, - shell, + state, event, layout, cursor, renderer, clipboard, ime, shell, ) } diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 5401946c91..354b87be31 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -74,15 +74,8 @@ where ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { - self.overlay.on_event( - event, - layout, - cursor_position, - renderer, - clipboard, - ime, - shell, - ) + self.overlay + .on_event(event, layout, cursor, renderer, clipboard, ime, shell) } /// Returns the current [`mouse::Interaction`] of the [`Element`]. diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index cd258ab687..9f97b9616f 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -1,3 +1,5 @@ +use iced_core::IME; + use crate::core::event; use crate::core::layout; use crate::core::mouse; @@ -173,6 +175,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { fn recurse( @@ -182,6 +185,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> (event::Status, bool) where @@ -201,6 +205,7 @@ where cursor, renderer, clipboard, + ime, shell, ) } else { @@ -231,6 +236,7 @@ where }, renderer, clipboard, + ime, shell, ), is_over, @@ -250,6 +256,7 @@ where cursor, renderer, clipboard, + ime, shell, ); diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index c8ad265306..ae17456c32 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -563,8 +563,8 @@ where match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { - let is_clicked = - layout.bounds().contains(cursor_position) && on_input.is_some(); + let is_clicked = cursor.position_over(layout.bounds()).is_some() + && on_input.is_some(); // if gain focus enable ime let focus_gained = state.is_focused.is_none() && is_clicked; let focus_lost = state.is_focused.is_some() && !is_clicked; From 2522fd188df37fa1086c66ed9fb51009eca7781c Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Wed, 21 Jun 2023 09:55:36 +0900 Subject: [PATCH 06/17] doctest fix --- runtime/src/user_interface.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index ed2ed5bc6b..66e93dd27a 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -132,6 +132,7 @@ where /// # } /// use iced_runtime::core::{clipboard,ime, Size, Point}; /// use iced_runtime::user_interface::{self, UserInterface}; + /// use iced_core::mouse; /// use iced_wgpu::Renderer; /// /// let mut counter = Counter::new(); From 1add649cd6c65bc747f3b99208aa048eb7df0abd Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Sat, 11 Nov 2023 16:55:14 +0900 Subject: [PATCH 07/17] add ime command interface for widgets. --- core/src/element.rs | 5 +++-- runtime/src/program/state.rs | 2 +- widget/src/combo_box.rs | 5 ++++- widget/src/keyed/column.rs | 4 +++- widget/src/text_editor.rs | 3 ++- widget/src/text_input.rs | 4 +++- 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/core/src/element.rs b/core/src/element.rs index 6992a6884f..37b2e91ae1 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -1,10 +1,10 @@ use crate::event::{self, Event}; -use crate::mouse; -use crate::overlay; use crate::renderer; use crate::widget; use crate::widget::tree::{self, Tree}; use crate::IME; +use crate::{layout, mouse}; +use crate::{overlay, Vector}; use crate::{Clipboard, Color, Layout, Length, Rectangle, Shell, Widget}; use std::any::Any; @@ -525,6 +525,7 @@ where ) -> event::Status { self.element.widget.on_event( state, event, layout, cursor, renderer, clipboard, ime, shell, + viewport, ) } diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index 0ccdfc0fee..03064229da 100644 --- a/runtime/src/program/state.rs +++ b/runtime/src/program/state.rs @@ -2,7 +2,7 @@ use crate::core::event::{self, Event}; use crate::core::mouse; use crate::core::renderer; use crate::core::widget::operation::{self, Operation}; -use crate::core::{Clipboard, Size}; +use crate::core::{Clipboard, Size, IME}; use crate::user_interface::{self, UserInterface}; use crate::{Command, Debug, Program}; diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 768c240287..5f22b79d1a 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -8,7 +8,7 @@ use crate::core::renderer; use crate::core::text; use crate::core::time::Instant; use crate::core::widget::{self, Widget}; -use crate::core::{Clipboard, Element, Length, Padding, Rectangle, Shell}; +use crate::core::{Clipboard, Element, Length, Padding, Rectangle, Shell, IME}; use crate::overlay::menu; use crate::text::LineHeight; use crate::{container, scrollable, text_input, TextInput}; @@ -352,6 +352,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -380,6 +381,7 @@ where cursor, renderer, clipboard, + ime, &mut local_shell, viewport, ); @@ -569,6 +571,7 @@ where mouse::Cursor::Unavailable, renderer, clipboard, + ime, &mut Shell::new(&mut vec![]), viewport, ); diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index 0ef82407cf..d2a9cc774c 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -8,7 +8,7 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::widget::Operation; use crate::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, - Shell, Widget, + Shell, Widget, IME, }; /// A container that distributes its contents vertically. @@ -232,6 +232,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -247,6 +248,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 1708a2e5ce..5b02aa99e3 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -10,7 +10,7 @@ use crate::core::text::{self, LineHeight}; use crate::core::widget::{self, Widget}; use crate::core::{ Clipboard, Color, Element, Length, Padding, Pixels, Rectangle, Shell, - Vector, + Vector, IME, }; use std::cell::RefCell; @@ -359,6 +359,7 @@ where cursor: mouse::Cursor, _renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 27efe755c9..f0df3a5452 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -26,7 +26,7 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, - Rectangle, Shell, Size, Vector, Widget, + Rectangle, Shell, Size, Vector, Widget, IME, }; use crate::runtime::Command; @@ -332,9 +332,11 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { + let _ = ime; update( event, layout, From f2f850cc98ae2931e49df1182ac7c7982854afe6 Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Sun, 12 Nov 2023 20:42:13 +0900 Subject: [PATCH 08/17] fixed to match latest iced --- core/src/event.rs | 5 +- core/src/ime.rs | 6 +- core/src/ime/event.rs | 17 ++ core/src/keyboard/event.rs | 11 +- widget/src/text_input.rs | 272 +++++++++++++++++++++++++++-- widget/src/text_input/ime_state.rs | 56 +++++- winit/src/conversion.rs | 10 +- 7 files changed, 344 insertions(+), 33 deletions(-) create mode 100644 core/src/ime/event.rs diff --git a/core/src/event.rs b/core/src/event.rs index 953cd73f94..04a97e8b87 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -1,9 +1,9 @@ //! Handle events of a user interface. +use crate::ime; use crate::keyboard; use crate::mouse; use crate::touch; use crate::window; - /// A user interface event. /// /// _**Note:** This type is largely incomplete! If you need to track @@ -26,6 +26,9 @@ pub enum Event { /// A platform specific event PlatformSpecific(PlatformSpecific), + + /// A ime event + IME(ime::Event), } /// A platform specific event diff --git a/core/src/ime.rs b/core/src/ime.rs index a82bf683bd..52175fbf68 100644 --- a/core/src/ime.rs +++ b/core/src/ime.rs @@ -1,6 +1,10 @@ //! Access the IME. -/// +mod event; + +pub use event::Event; + +/// IME Access interface. pub trait IME { /// fn set_ime_position(&self, x: i32, y: i32); diff --git a/core/src/ime/event.rs b/core/src/ime/event.rs new file mode 100644 index 0000000000..189653a0eb --- /dev/null +++ b/core/src/ime/event.rs @@ -0,0 +1,17 @@ +/// A IME event. +/// +/// _**Note:** This type is largely incomplete! If you need to track +/// additional events, feel free to [open an issue] and share your use case!_ +/// +/// [open an issue]: https://github.com/iced-rs/iced/issues +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Event { + /// IME enabled. + IMEEnabled, + + /// selecting input. + IMEPreedit(String, Option<(usize, usize)>), + + /// enter input. + IMECommit(String), +} diff --git a/core/src/keyboard/event.rs b/core/src/keyboard/event.rs index cbac881ecd..016761af37 100644 --- a/core/src/keyboard/event.rs +++ b/core/src/keyboard/event.rs @@ -6,7 +6,7 @@ use super::{KeyCode, Modifiers}; /// additional events, feel free to [open an issue] and share your use case!_ /// /// [open an issue]: https://github.com/iced-rs/iced/issues -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Event { /// A keyboard key was pressed. KeyPressed { @@ -31,13 +31,4 @@ pub enum Event { /// The keyboard modifiers have changed. ModifiersChanged(Modifiers), - - /// IME enabled. - IMEEnabled, - - /// selecting input. - IMEPreedit(String, Option<(usize, usize)>), - - /// enter input. - IMECommit(String), } diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index f0df3a5452..ddd69abc56 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -1,23 +1,25 @@ //! Display fields that can be filled with text. //! //! A [`TextInput`] has some local [`State`]. +pub mod cursor; mod editor; +mod ime_state; mod value; -pub mod cursor; - pub use cursor::Cursor; +use iced_renderer::core::text::Paragraph; pub use value::Value; use editor::Editor; use crate::core::alignment; use crate::core::event::{self, Event}; +use crate::core::ime; use crate::core::keyboard; use crate::core::layout; use crate::core::mouse::{self, click}; use crate::core::renderer; -use crate::core::text::{self, Paragraph as _, Text}; +use crate::core::text::{self, Text}; use crate::core::time::{Duration, Instant}; use crate::core::touch; use crate::core::widget; @@ -32,6 +34,8 @@ use crate::runtime::Command; pub use iced_style::text_input::{Appearance, StyleSheet}; +use self::ime_state::IMEState; + /// A field that can be filled with text. /// /// # Example @@ -336,13 +340,13 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { - let _ = ime; update( event, layout, cursor, renderer, clipboard, + ime, shell, &mut self.value, self.size, @@ -597,6 +601,7 @@ pub fn update<'a, Message, Renderer>( cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, value: &mut Value, size: Option, @@ -628,13 +633,22 @@ where Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { let state = state(); - + if state.is_focused.is_some() { + if is_secure { + ime.password_mode(); + } else { + ime.inside(); + } + } let click_position = if on_input.is_some() { cursor.position_over(layout.bounds()) } else { None }; - + let focus_gained = + state.is_focused.is_none() && click_position.is_some(); + let focus_lost = + state.is_focused.is_some() && click_position.is_none(); state.is_focused = if click_position.is_some() { state.is_focused.or_else(|| { let now = Instant::now(); @@ -714,6 +728,35 @@ where state.last_click = Some(click); + if !is_secure && focus_gained { + let bounds = text_layout.bounds(); + let cursor_index = + find_cursor_position(bounds, value, state, target) + .unwrap_or(0); + let (offset, _) = measure_cursor_and_scroll_offset( + &state.value, + bounds, + cursor_index, + ); + ime.set_ime_position( + (bounds.x + offset) as i32, + bounds.y as i32, + ); + } else if focus_lost { + let mut editor = Editor::new(value, &mut state.cursor); + ime.outside(); + if let Some((old_ime_state, on_input)) = + state.ime_state.take().zip(on_input) + { + old_ime_state + .before_preedit_text() + .chars() + .for_each(|ch| editor.insert(ch)); + let message = (on_input)(editor.contents()); + shell.publish(message); + } + } + return event::Status::Captured; } } @@ -1036,6 +1079,140 @@ where } } } + Event::IME(ime::Event::IMEEnabled) => { + let state = state(); + if state.is_focused.is_some() { + let _ = state.ime_state.replace(IMEState::default()); + return event::Status::Captured; + } + } + Event::IME(ime::Event::IMEPreedit(preedit_text, range)) => { + let state = state(); + if state.is_focused.is_none() || is_secure { + return event::Status::Ignored; + } + if let Some(focus) = &mut state.is_focused { + // calcurate where we need to place candidate window. + let text_bounds = layout.children().next().unwrap().bounds(); + let before_preedit_cursor = state.cursor.start(value); + // "A|BC" + // if cursor is between A and B then we have preedit text あいうえお, we have to display + // "A|あいうえおBC" + // so we split value to 2 pieces . + // 1 before preedit cursor. + // 2 after preedit text. + let inputted_text = value.to_string(); + let before_preedit_text: String = + inputted_text.chars().take(before_preedit_cursor).collect(); + let before_preedit_paragraph = Text { + content: &before_preedit_text.clone(), + bounds: Size { + width: f32::INFINITY, + height: text_bounds.height, + }, + size: size.unwrap_or_else(|| renderer.default_size()), + line_height, + font: font.unwrap_or_else(|| renderer.default_font()), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: text::Shaping::Advanced, + }; + + let after_preedit_text: String = + inputted_text.chars().skip(before_preedit_cursor).collect(); + + let whole_text = before_preedit_text.clone() + + &preedit_text + + &after_preedit_text; + + let whole_text = Text { + content: &whole_text, + ..before_preedit_paragraph + }; + let mut paragraph = Renderer::Paragraph::default(); + paragraph.update(whole_text); + + { + let (width, _) = measure_cursor_and_scroll_offset( + ¶graph, + text_bounds, + before_preedit_cursor + preedit_text.chars().count(), + ); + let position = ( + (text_bounds.x + width) as i32, + (text_bounds.y + text_bounds.height) as i32, + ); + ime.set_ime_position(position.0, position.1); + } + // set current state to ime_state. + if let Some(ime_state) = state.ime_state.as_mut() { + ime_state + .before_preedit_paragraph_mut() + .update(before_preedit_paragraph); + ime_state.whole_paragraph_mut().update(whole_text); + ime_state.set_event(preedit_text, range); + ime_state.set_before_preedit_text(before_preedit_text); + } else { + let mut new_state = + IMEState::::default(); + new_state + .before_preedit_paragraph_mut() + .update(before_preedit_paragraph); + new_state.whole_paragraph_mut().update(whole_text); + new_state.set_event(preedit_text, range); + new_state.set_before_preedit_text(before_preedit_text); + let _ = state.ime_state.replace(new_state); + } + // measure underline width for drawing. + if let Some(ime_state) = &mut state.ime_state { + let measure_width = move |chunk: &str| { + let text = Text { + content: chunk, + ..whole_text + }; + let cursor_index = chunk.chars().count(); + let mut paragraph = Renderer::Paragraph::default(); + paragraph.update(text); + measure_cursor_and_scroll_offset( + ¶graph, + text_bounds, + cursor_index, + ) + .0 + }; + ime_state.measure_underlines(measure_width); + } + focus.updated_at = Instant::now(); + } + + return event::Status::Captured; + } + // Insert text characters to value. + // and delete current IME state. + Event::IME(ime::Event::IMECommit(text)) => { + let state = state(); + if let Some(focus) = &mut state.is_focused { + let Some(on_input) = on_input else { + return event::Status::Ignored; + }; + if state.is_pasting.is_none() + && !state.keyboard_modifiers.command() + { + let mut editor = Editor::new(value, &mut state.cursor); + + text.chars().for_each(|ch| editor.insert(ch)); + + let message = (on_input)(editor.contents()); + shell.publish(message); + + focus.updated_at = Instant::now(); + + update_cache(state, value); + state.ime_state = None; + return event::Status::Captured; + } + } + } _ => {} } @@ -1100,6 +1277,15 @@ pub fn draw( appearance.icon_color, ); } + let preedit_text = state.ime_state.as_ref().map(|ime_state| { + ( + ime_state.before_preedit_text(), + ime_state.underlines(), + ime_state.before_preedit_paragraph(), + ime_state.before_cursor_text().chars().count(), + ime_state.whole_paragraph(), + ) + }); let text = value.to_string(); @@ -1110,12 +1296,30 @@ pub fn draw( { match state.cursor.state(value) { cursor::State::Index(position) => { - let (text_value_width, offset) = + // in ime mode A|BC + // あいうえお inserted between A and B will be A|あいうえおBC + // so we need A 's width to display underline and cursors. + let (text_value_width, offset) = if let Some(( + before_preedit_text, + _underlines, + _before_preedit_pragraph, + before_cursor_text_char_count, + whole_paragraph, + )) = preedit_text + { + measure_cursor_and_scroll_offset( + whole_paragraph, + text_bounds, + before_preedit_text.chars().count() + + before_cursor_text_char_count, + ) + } else { measure_cursor_and_scroll_offset( &state.value, text_bounds, position, - ); + ) + }; let is_cursor_visible = ((focus.now - focus.updated_at) .as_millis() @@ -1147,7 +1351,6 @@ pub fn draw( cursor::State::Selection { start, end } => { let left = start.min(end); let right = end.max(start); - let (left_position, left_offset) = measure_cursor_and_scroll_offset( &state.value, @@ -1196,18 +1399,63 @@ pub fn draw( let render = |renderer: &mut Renderer| { if let Some((cursor, color)) = cursor { renderer.fill_quad(cursor, color); + // render underlines for ime mode. + if let Some(( + before_preedit_text, + Some(underlines), + before_preedit_paragraph, + _, + _, + )) = preedit_text + { + let (left, _) = measure_cursor_and_scroll_offset( + before_preedit_paragraph, + text_bounds, + 0, + ); + let (right, _) = measure_cursor_and_scroll_offset( + before_preedit_paragraph, + text_bounds, + before_preedit_text.chars().count(), + ); + let before_preedit_width = right - left; + println!("before preedit width = {}", before_preedit_width); + underlines + .iter() + .enumerate() + .for_each(|(index, underline)| { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: underline.0 + + text_bounds.x + + before_preedit_width, + y: text_bounds.y + text_bounds.height, + width: underline.1, + height: if index == 1 { 2.0 } else { 1.0 }, + }, + border_radius: cursor.border_radius, + border_width: 0.0, + border_color: cursor.border_color, + }, + theme.value_color(style), + ); + }); + } } else { renderer.with_translation(Vector::ZERO, |_| {}); } renderer.fill_paragraph( - if text.is_empty() { + if let Some((_, _, _, _, paragraph)) = preedit_text { + paragraph + } else if text.is_empty() && preedit_text.is_none() { &state.placeholder } else { &state.value }, Point::new(text_bounds.x, text_bounds.center_y()), - if text.is_empty() { + if text.is_empty() && preedit_text.is_none() { theme.placeholder_color(style) } else if is_disabled { theme.disabled_color(style) @@ -1252,6 +1500,7 @@ pub struct State { is_focused: Option, is_dragging: bool, is_pasting: Option, + ime_state: Option>, last_click: Option, cursor: Cursor, keyboard_modifiers: keyboard::Modifiers, @@ -1283,6 +1532,7 @@ impl State

{ last_click: None, cursor: Cursor::default(), keyboard_modifiers: keyboard::Modifiers::default(), + ime_state: None, } } diff --git a/widget/src/text_input/ime_state.rs b/widget/src/text_input/ime_state.rs index f845bfa83a..b7c6d6ae02 100644 --- a/widget/src/text_input/ime_state.rs +++ b/widget/src/text_input/ime_state.rs @@ -1,10 +1,19 @@ +use std::{fmt::Debug, sync::Arc}; + +use iced_renderer::core::text::Paragraph; + /// /// #[derive(Debug, Default, Clone)] -pub struct IMEState { +pub struct IMEState { + before_preedit_text: String, + before_preedit_paragraph: P, preedit_text: String, + whole_paragraph: P, + underlines: Option<[(f32, f32); 3]>, candidate_indicator: Option, } + #[derive(Debug, Clone, Copy)] enum CandidateIndicator { // indicate like Windows @@ -13,9 +22,24 @@ enum CandidateIndicator { Cursor(usize), } -impl IMEState { - pub fn preedit_text(&self) -> &str { - &self.preedit_text +impl IMEState

{ + pub fn before_preedit_text(&self) -> &str { + &self.before_preedit_text + } + pub fn set_before_preedit_text(&mut self, text: String) { + self.before_preedit_text = text; + } + pub fn before_preedit_paragraph_mut(&mut self) -> &mut P { + &mut self.before_preedit_paragraph + } + pub fn before_preedit_paragraph(&self) -> &P { + &self.before_preedit_paragraph + } + pub fn whole_paragraph_mut(&mut self) -> &mut P { + &mut self.whole_paragraph + } + pub fn whole_paragraph(&self) -> &P { + &self.whole_paragraph } pub fn set_event( &mut self, @@ -93,4 +117,28 @@ impl IMEState { text } } + + /// measure underline offset and width for each splitted pieces. + /// + /// we can retrieve result by underlines method. + pub fn measure_underlines f32>(&mut self, measure_fn: F) { + let pieces = self.split_to_pieces(); + let mut width_iter = pieces.iter().map(|chunk| match chunk { + Some(chunk) => (measure_fn)(&chunk), + None => 0.0, + }); + + let mut widths = [0.0; 3]; + widths.fill_with(|| width_iter.next().unwrap()); + let _ = self.underlines.replace([ + (0.0, widths[0]), + (widths[0], widths[1]), + (widths[0] + widths[1], widths[2]), + ]); + } + /// retrieve underline infos + /// + pub fn underlines(&self) -> Option<[(f32, f32); 3]> { + self.underlines + } } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index c4ad0a1b53..f165e035c9 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -2,6 +2,7 @@ //! //! [`winit`]: https://github.com/rust-windowing/winit //! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.10/runtime +use crate::core::ime; use crate::core::keyboard; use crate::core::mouse; use crate::core::touch; @@ -138,18 +139,15 @@ pub fn window_event( } WindowEvent::Ime(ime) => match ime { winit::event::Ime::Enabled => { - Some(Event::Keyboard(keyboard::Event::IMEEnabled)) + Some(Event::IME(ime::Event::IMEEnabled)) } winit::event::Ime::Preedit(text, range) => { // range parameter is used to mark converting position. - Some(Event::Keyboard(keyboard::Event::IMEPreedit( - text.clone(), - *range, - ))) + Some(Event::IME(ime::Event::IMEPreedit(text.clone(), *range))) } winit::event::Ime::Commit(text) => { - Some(Event::Keyboard(keyboard::Event::IMECommit(text.clone()))) + Some(Event::IME(ime::Event::IMECommit(text.clone()))) } winit::event::Ime::Disabled => None, }, From f6ee02e4daf41f14332dbe7697b213df7af27d8e Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Sun, 12 Nov 2023 20:53:37 +0900 Subject: [PATCH 09/17] merge upstream --- widget/src/text_input.rs | 42 ++++++++++++++++++++++++++++++ widget/src/text_input/ime_state.rs | 2 -- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ddd69abc56..c98ad8ab66 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -1407,6 +1407,48 @@ pub fn draw( _, _, )) = preedit_text + { + let (left, _) = measure_cursor_and_scroll_offset( + before_preedit_paragraph, + text_bounds, + 0, + ); + let (right, _) = measure_cursor_and_scroll_offset( + before_preedit_paragraph, + text_bounds, + before_preedit_text.chars().count(), + ); + let before_preedit_width = right - left; + underlines + .iter() + .enumerate() + .for_each(|(index, underline)| { + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: underline.0 + + text_bounds.x + + before_preedit_width, + y: text_bounds.y + text_bounds.height, + width: underline.1, + height: if index == 1 { 2.0 } else { 1.0 }, + }, + border_radius: cursor.border_radius, + border_width: 0.0, + border_color: cursor.border_color, + }, + theme.value_color(style), + ); + }); + } + // render underlines for ime mode. + if let Some(( + before_preedit_text, + Some(underlines), + before_preedit_paragraph, + _, + _, + )) = preedit_text { let (left, _) = measure_cursor_and_scroll_offset( before_preedit_paragraph, diff --git a/widget/src/text_input/ime_state.rs b/widget/src/text_input/ime_state.rs index b7c6d6ae02..8c3cd063f6 100644 --- a/widget/src/text_input/ime_state.rs +++ b/widget/src/text_input/ime_state.rs @@ -1,5 +1,3 @@ -use std::{fmt::Debug, sync::Arc}; - use iced_renderer::core::text::Paragraph; /// From 6a358cfd15e93d77206dab63afa754e92178ec5b Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Sun, 12 Nov 2023 20:53:47 +0900 Subject: [PATCH 10/17] fit to ime --- examples/loading_spinners/src/circular.rs | 2 ++ examples/loading_spinners/src/linear.rs | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index bf01c3b44f..9fce4e8977 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -2,6 +2,7 @@ use iced::advanced::layout; use iced::advanced::renderer; use iced::advanced::widget::tree::{self, Tree}; +use iced::advanced::IME; use iced::advanced::{Clipboard, Layout, Renderer, Shell, Widget}; use iced::event; use iced::mouse; @@ -272,6 +273,7 @@ where _cursor: mouse::Cursor, _renderer: &iced::Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index c5bb4791ce..51c78b693b 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -1,7 +1,7 @@ //! Show a linear progress indicator. -use iced::advanced::layout; use iced::advanced::renderer::{self, Quad}; use iced::advanced::widget::tree::{self, Tree}; +use iced::advanced::{layout, IME}; use iced::advanced::{Clipboard, Layout, Shell, Widget}; use iced::event; use iced::mouse; @@ -193,6 +193,7 @@ where _cursor: mouse::Cursor, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { From 50fb8464242f4e9504cea93eb1bda62fff98c7cb Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:05:48 +0900 Subject: [PATCH 11/17] lint fix --- widget/src/text_editor.rs | 2 +- widget/src/text_input/ime_state.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 5b02aa99e3..a820cacce6 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -359,7 +359,7 @@ where cursor: mouse::Cursor, _renderer: &Renderer, clipboard: &mut dyn Clipboard, - ime: &dyn IME, + _ime: &dyn IME, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { diff --git a/widget/src/text_input/ime_state.rs b/widget/src/text_input/ime_state.rs index 8c3cd063f6..ae07b97acf 100644 --- a/widget/src/text_input/ime_state.rs +++ b/widget/src/text_input/ime_state.rs @@ -122,7 +122,7 @@ impl IMEState

{ pub fn measure_underlines f32>(&mut self, measure_fn: F) { let pieces = self.split_to_pieces(); let mut width_iter = pieces.iter().map(|chunk| match chunk { - Some(chunk) => (measure_fn)(&chunk), + Some(chunk) => (measure_fn)(chunk), None => 0.0, }); From 9189c6578f93b7d9ac186c63f84b017abf779a80 Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:10:14 +0900 Subject: [PATCH 12/17] fix lint macos --- core/src/ime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/ime.rs b/core/src/ime.rs index 52175fbf68..544c81ea1b 100644 --- a/core/src/ime.rs +++ b/core/src/ime.rs @@ -26,7 +26,7 @@ pub trait IME { /// force ime enabled or disabled. /// /// this will block request until unlock_set_ime_allowed. - fn force_set_ime_allowed(&self, allowed: bool); + fn force_set_ime_allowed(&self, _allowed: bool); /// remove request of force_set_ime_allowed /// fn unlock_set_ime_allowed(&self); @@ -61,5 +61,5 @@ impl IME for Null { fn unlock_set_ime_allowed(&self) {} #[cfg(target_os = "macos")] - fn set_ime_position_with_reenable(&self, x: i32, y: i32) {} + fn set_ime_position_with_reenable(&self, _x: i32, _y: i32) {} } From 6ac01769f9045d3616c319fdd148ef7dcbc31e09 Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:14:37 +0900 Subject: [PATCH 13/17] fix mac lint --- winit/src/ime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winit/src/ime.rs b/winit/src/ime.rs index f141a48c31..c242b6c02f 100644 --- a/winit/src/ime.rs +++ b/winit/src/ime.rs @@ -144,7 +144,7 @@ impl crate::core::ime::IME for IME { #[cfg(target_os = "macos")] fn set_ime_position_with_reenable(&self, x: i32, y: i32) { if let Ok(mut guard) = self.requests.write() { - guard.push(RequestKind::SetPositionWithReenable(x, y)) + guard.push(RequestKind::SetPositionWithReenable(x, y)); } } } From 96514f19df6b50d4aeb22efc5c4fecdf079dac86 Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Sun, 12 Nov 2023 22:17:25 +0900 Subject: [PATCH 14/17] fix offset calculation --- widget/src/text_input.rs | 55 ++++++---------------------------------- 1 file changed, 8 insertions(+), 47 deletions(-) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index c98ad8ab66..14571fe4a7 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -1393,8 +1393,12 @@ pub fn draw( } else { (None, 0.0) }; - - let text_width = state.value.min_width(); + // in ime mode we need to use whole_paragraph for determine offsetting text. + let text_width = if let Some(preedit_text) = preedit_text { + preedit_text.4.min_width() + } else { + state.value.min_width() + }; let render = |renderer: &mut Renderer| { if let Some((cursor, color)) = cursor { @@ -1441,56 +1445,13 @@ pub fn draw( ); }); } - // render underlines for ime mode. - if let Some(( - before_preedit_text, - Some(underlines), - before_preedit_paragraph, - _, - _, - )) = preedit_text - { - let (left, _) = measure_cursor_and_scroll_offset( - before_preedit_paragraph, - text_bounds, - 0, - ); - let (right, _) = measure_cursor_and_scroll_offset( - before_preedit_paragraph, - text_bounds, - before_preedit_text.chars().count(), - ); - let before_preedit_width = right - left; - println!("before preedit width = {}", before_preedit_width); - underlines - .iter() - .enumerate() - .for_each(|(index, underline)| { - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: underline.0 - + text_bounds.x - + before_preedit_width, - y: text_bounds.y + text_bounds.height, - width: underline.1, - height: if index == 1 { 2.0 } else { 1.0 }, - }, - border_radius: cursor.border_radius, - border_width: 0.0, - border_color: cursor.border_color, - }, - theme.value_color(style), - ); - }); - } } else { renderer.with_translation(Vector::ZERO, |_| {}); } renderer.fill_paragraph( - if let Some((_, _, _, _, paragraph)) = preedit_text { - paragraph + if let Some((_, _, _, _, whole_paragraph)) = preedit_text { + whole_paragraph } else if text.is_empty() && preedit_text.is_none() { &state.placeholder } else { From b92716320677fe598c77a7c3c510e4b8a8c663ea Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Wed, 15 Nov 2023 11:10:04 +0900 Subject: [PATCH 15/17] add ime parameter for Shader widget --- widget/src/shader.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/widget/src/shader.rs b/widget/src/shader.rs index ca140627d7..5259800337 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -12,7 +12,7 @@ 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, Length, Rectangle, Shell, Size}; +use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size, IME}; use crate::renderer::wgpu::primitive::pipeline; use std::marker::PhantomData; @@ -99,6 +99,7 @@ where cursor: mouse::Cursor, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, + _ime: &dyn IME, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { From f9187a3bc7e20d300503f0dc277a0c18622d40d7 Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Thu, 16 Nov 2023 11:31:30 +0900 Subject: [PATCH 16/17] restrict overflowing IME candidate window --- widget/src/text_input.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 14571fe4a7..7af07c6c5f 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -733,14 +733,14 @@ where let cursor_index = find_cursor_position(bounds, value, state, target) .unwrap_or(0); - let (offset, _) = measure_cursor_and_scroll_offset( + let (width, offset) = measure_cursor_and_scroll_offset( &state.value, bounds, cursor_index, ); ime.set_ime_position( - (bounds.x + offset) as i32, - bounds.y as i32, + (bounds.x + width - offset) as i32, + (bounds.y + bounds.height) as i32, ); } else if focus_lost { let mut editor = Editor::new(value, &mut state.cursor); @@ -1133,13 +1133,13 @@ where paragraph.update(whole_text); { - let (width, _) = measure_cursor_and_scroll_offset( + let (width, offset) = measure_cursor_and_scroll_offset( ¶graph, text_bounds, before_preedit_cursor + preedit_text.chars().count(), ); let position = ( - (text_bounds.x + width) as i32, + (text_bounds.x + width - offset) as i32, (text_bounds.y + text_bounds.height) as i32, ); ime.set_ime_position(position.0, position.1); From 3fb06a76ae2c2047a259700a472775fbb60a0127 Mon Sep 17 00:00:00 2001 From: triangle drawer <48007646+t18b219k@users.noreply.github.com> Date: Thu, 16 Nov 2023 12:05:02 +0900 Subject: [PATCH 17/17] more precise event handling --- core/src/ime/event.rs | 2 ++ widget/src/text_input.rs | 11 +++++++++++ winit/src/conversion.rs | 4 +++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/core/src/ime/event.rs b/core/src/ime/event.rs index 189653a0eb..56be5aabf9 100644 --- a/core/src/ime/event.rs +++ b/core/src/ime/event.rs @@ -14,4 +14,6 @@ pub enum Event { /// enter input. IMECommit(String), + /// Notifies when the IME was disabled. + IMEDisabled, } diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 7af07c6c5f..afff0383e8 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -1088,9 +1088,15 @@ where } Event::IME(ime::Event::IMEPreedit(preedit_text, range)) => { let state = state(); + if let ("", None) = (preedit_text.as_str(), range) { + state.ime_state = None; + return event::Status::Captured; + } + if state.is_focused.is_none() || is_secure { return event::Status::Ignored; } + if let Some(focus) = &mut state.is_focused { // calcurate where we need to place candidate window. let text_bounds = layout.children().next().unwrap().bounds(); @@ -1213,6 +1219,11 @@ where } } } + Event::IME(ime::Event::IMEDisabled) => { + let state = state(); + state.ime_state = None; + return event::Status::Captured; + } _ => {} } diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index f165e035c9..e0f62b75e6 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -149,7 +149,9 @@ pub fn window_event( winit::event::Ime::Commit(text) => { Some(Event::IME(ime::Event::IMECommit(text.clone()))) } - winit::event::Ime::Disabled => None, + winit::event::Ime::Disabled => { + Some(Event::IME(ime::Event::IMEDisabled)) + } }, _ => None, }