diff --git a/core/src/element.rs b/core/src/element.rs index dea111afc7..37b2e91ae1 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -1,13 +1,11 @@ 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::{ - Clipboard, Color, Layout, Length, Rectangle, Shell, Vector, Widget, -}; +use crate::IME; +use crate::{layout, mouse}; +use crate::{overlay, Vector}; +use crate::{Clipboard, Color, Layout, Length, Rectangle, Shell, Widget}; use std::any::Any; use std::borrow::Borrow; @@ -385,6 +383,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, B>, viewport: &Rectangle, ) -> event::Status { @@ -398,6 +397,7 @@ where cursor, renderer, clipboard, + ime, &mut local_shell, viewport, ); @@ -519,11 +519,13 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { self.element.widget.on_event( - state, event, layout, cursor, renderer, clipboard, shell, viewport, + state, event, layout, cursor, renderer, clipboard, ime, shell, + viewport, ) } 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 new file mode 100644 index 0000000000..544c81ea1b --- /dev/null +++ b/core/src/ime.rs @@ -0,0 +1,65 @@ +//! Access the IME. + +mod event; + +pub use event::Event; + +/// IME Access interface. +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/ime/event.rs b/core/src/ime/event.rs new file mode 100644 index 0000000000..56be5aabf9 --- /dev/null +++ b/core/src/ime/event.rs @@ -0,0 +1,19 @@ +/// 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), + /// Notifies when the IME was disabled. + IMEDisabled, +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 54ea58394c..6d40b0b448 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -22,6 +22,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; @@ -62,6 +63,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 af10afee60..194162d505 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -6,12 +6,12 @@ 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::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; +use crate::{Clipboard,IME, Layout, Point, Rectangle, Shell, Size, Vector}; + /// An interactive component that can be displayed on top of other widgets. pub trait Overlay @@ -70,6 +70,7 @@ where _cursor: mouse::Cursor, _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 a279fe2859..691e2672b2 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; @@ -82,10 +82,11 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.overlay - .on_event(event, layout, cursor, renderer, clipboard, shell) + .on_event(event, layout, cursor, renderer, clipboard, ime, shell) } /// Returns the current [`mouse::Interaction`] of the [`Element`]. @@ -236,6 +237,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, B>, ) -> event::Status { let mut local_messages = Vec::new(); @@ -247,6 +249,7 @@ where cursor, renderer, clipboard, + ime, &mut local_shell, ); diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index e1e9727a40..de171abecd 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -4,10 +4,12 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::widget; + use crate::{ - Clipboard, Event, Layout, Overlay, Point, Rectangle, Shell, Size, Vector, + Clipboard, Event,IME, Layout, Overlay, Point, Rectangle, Shell, Size, Vector, }; + /// An [`Overlay`] container that displays multiple overlay [`overlay::Element`] /// children. #[allow(missing_debug_implementations)] @@ -85,6 +87,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.children @@ -97,6 +100,7 @@ where cursor, renderer, clipboard, + ime, shell, ) }) diff --git a/core/src/widget.rs b/core/src/widget.rs index 294d59847e..f8334ac4f3 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, Rectangle, Shell}; /// A component that displays information and allows interaction. @@ -115,6 +115,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/examples/integration/src/main.rs b/examples/integration/src/main.rs index 0f32fca005..15b0c217be 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -12,7 +12,7 @@ use iced_winit::core::{Color, Font, Pixels, 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::{ event::{Event, ModifiersState, WindowEvent}, @@ -62,7 +62,7 @@ pub fn main() -> Result<(), Box> { let mut cursor_position = None; 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; @@ -208,9 +208,10 @@ pub fn main() -> Result<(), Box> { text_color: Color::WHITE, }, &mut clipboard, + &ime, &mut debug, ); - + ime.apply_request(&window); // and request a redraw window.request_redraw(); } 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 { diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index acb14372f1..785972647d 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -225,7 +225,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; @@ -310,6 +310,7 @@ mod modal { cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -320,6 +321,7 @@ mod modal { cursor, renderer, clipboard, + ime, shell, viewport, ) @@ -440,6 +442,7 @@ mod modal { cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { let content_bounds = layout.children().next().unwrap().bounds(); @@ -463,6 +466,7 @@ mod modal { cursor, renderer, clipboard, + ime, shell, &layout.bounds(), ) diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 934049d5a5..3dd8c1f9af 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -185,7 +185,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; @@ -406,6 +406,7 @@ mod toast { cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -416,6 +417,7 @@ mod toast { cursor, renderer, clipboard, + ime, shell, viewport, ) @@ -537,6 +539,7 @@ mod toast { cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { if let Event::Window(window::Event::RedrawRequested(now)) = &event { @@ -588,6 +591,7 @@ mod toast { cursor, renderer, clipboard, + ime, &mut local_shell, &viewport, ); 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 29e94d65fd..8ece87f49e 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -19,6 +19,7 @@ pub mod clipboard; pub mod command; pub mod font; +pub mod ime; pub mod keyboard; pub mod overlay; pub mod program; diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index 4256efb705..805a015adf 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; @@ -176,6 +178,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { fn recurse( @@ -185,6 +188,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> (event::Status, bool) where @@ -204,6 +208,7 @@ where cursor, renderer, clipboard, + ime, shell, ) } else { @@ -234,6 +239,7 @@ where }, renderer, clipboard, + ime, shell, ), is_over, @@ -253,6 +259,7 @@ where cursor, renderer, clipboard, + ime, shell, ); diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs index 6f8f4063e2..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}; @@ -94,6 +94,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( @@ -112,6 +113,7 @@ where cursor, renderer, clipboard, + ime, &mut messages, ); diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 3594ac188c..0ad928592e 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; @@ -133,10 +135,9 @@ where /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() } /// # pub fn update(&mut self, _: ()) {} /// # } - /// use iced_runtime::core::clipboard; - /// use iced_runtime::core::mouse; - /// use iced_runtime::core::Size; + /// 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(); @@ -145,7 +146,7 @@ where /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor = mouse::Cursor::default(); /// let mut clipboard = clipboard::Null; - /// + /// let ime = ime::Null; /// // Initialize our event storage /// let mut events = Vec::new(); /// let mut messages = Vec::new(); @@ -166,6 +167,7 @@ where /// cursor, /// &mut renderer, /// &mut clipboard, + /// &ime, /// &mut messages /// ); /// @@ -183,6 +185,7 @@ where cursor: mouse::Cursor, renderer: &mut Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, messages: &mut Vec, ) -> (State, Vec) { use std::mem::ManuallyDrop; @@ -214,6 +217,7 @@ where cursor, renderer, clipboard, + ime, &mut shell, ); @@ -317,6 +321,7 @@ where base_cursor, renderer, clipboard, + ime, &mut shell, &viewport, ); @@ -388,6 +393,7 @@ where /// # pub fn update(&mut self, _: ()) {} /// # } /// use iced_runtime::core::clipboard; + /// use iced_runtime::core::ime; /// use iced_runtime::core::mouse; /// use iced_runtime::core::renderer; /// use iced_runtime::core::{Element, Size}; @@ -400,6 +406,7 @@ where /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor = mouse::Cursor::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(); @@ -420,6 +427,7 @@ where /// cursor, /// &mut renderer, /// &mut clipboard, + /// &ime, /// &mut messages /// ); /// 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/button.rs b/widget/src/button.rs index 384a315643..4bc19d2f3c 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}; @@ -197,6 +197,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -207,6 +208,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ) { diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 390f4d9268..4c8b3e03e3 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -15,7 +15,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, Rectangle, Size, Vector}; +use crate::core::{Length, Rectangle, Size, Vector, IME}; use crate::graphics::geometry; use std::marker::PhantomData; @@ -147,6 +147,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/checkbox.rs b/widget/src/checkbox.rs index d7fdf33961..c06c60b74e 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -10,6 +10,7 @@ use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Widget, + IME, }; pub use iced_style::checkbox::{Appearance, StyleSheet}; @@ -223,6 +224,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/column.rs b/widget/src/column.rs index 42e90ac105..ce69cd2fb4 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, Rectangle, - Shell, Widget, + Shell, Widget, IME, }; /// A container that distributes its contents vertically. @@ -171,6 +171,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -186,6 +187,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ) 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/container.rs b/widget/src/container.rs index ee7a496583..708e1ec36e 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -9,7 +9,7 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Operation}; use crate::core::{ Background, Clipboard, Color, Element, Layout, Length, Padding, Pixels, - Point, Rectangle, Shell, Size, Vector, Widget, + Point, Rectangle, Shell, Size, Vector, Widget, IME, }; use crate::runtime::Command; @@ -208,6 +208,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -218,6 +219,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ) diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 68015ba8db..e94780765c 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; @@ -150,6 +150,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/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/lazy.rs b/widget/src/lazy.rs index 167a055dcf..ca80bbf9ca 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -17,9 +17,7 @@ use crate::core::renderer; 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, Vector, -}; +use crate::core::{self, Clipboard, Hasher, IME,Length, Point, Rectangle, Shell, Size, Vector}; use crate::runtime::overlay::Nested; use ouroboros::self_referencing; @@ -188,6 +186,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -199,6 +198,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ) @@ -374,10 +374,13 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.with_overlay_mut_maybe(|overlay| { - overlay.on_event(event, layout, cursor, renderer, clipboard, shell) + overlay.on_event( + event, layout, cursor, renderer, clipboard, ime, shell, + ) }) .unwrap_or(event::Status::Ignored) } diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index ad0c382354..181e646d61 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -8,7 +8,7 @@ use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, - Widget, + Widget, IME, }; use crate::runtime::overlay::Nested; @@ -277,6 +277,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -292,6 +293,7 @@ where cursor, renderer, clipboard, + ime, &mut local_shell, viewport, ) @@ -618,6 +620,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { let mut local_messages = Vec::new(); @@ -631,6 +634,7 @@ where cursor, renderer, clipboard, + ime, &mut local_shell, ) }) diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index 86d37b6c1c..3641b2969b 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -5,10 +5,7 @@ use crate::core::overlay; use crate::core::renderer; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; -use crate::core::{ - self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, - Widget, -}; +use crate::core::{self, Clipboard, Element,IME, Length, Point, Rectangle, Shell, Size, Vector,Widget}; use crate::horizontal_space; use crate::runtime::overlay::Nested; @@ -183,6 +180,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -205,6 +203,7 @@ where cursor, renderer, clipboard, + ime, &mut local_shell, viewport, ) @@ -409,10 +408,13 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { self.with_overlay_mut_maybe(|overlay| { - overlay.on_event(event, layout, cursor, renderer, clipboard, shell) + overlay.on_event( + event, layout, cursor, renderer, clipboard, ime, shell, + ) }) .unwrap_or(event::Status::Ignored) } diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 3a5b01a3b1..7b0126970e 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, Rectangle, Shell, Widget, + Clipboard, Element, Layout, Length, Rectangle, Shell, Widget, IME, }; /// Emit messages on mouse events. @@ -152,6 +152,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -162,6 +163,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ) { diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 5098fa1714..f4d91317b9 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}; @@ -272,12 +273,13 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, ) -> event::Status { let bounds = layout.bounds(); self.container.on_event( - self.state, event, layout, cursor, renderer, clipboard, shell, + self.state, event, layout, cursor, renderer, clipboard, ime, shell, &bounds, ) } @@ -386,6 +388,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/pane_grid.rs b/widget/src/pane_grid.rs index 2d25a543ee..bbd91996e7 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 @@ -320,6 +320,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -361,6 +362,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, is_picked, diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index 826ea66349..263e89247e 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`]. @@ -230,6 +232,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, is_picked: bool, @@ -246,6 +249,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ); @@ -265,6 +269,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ) diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index f4dbb6b145..f36a07791b 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`]. @@ -307,6 +307,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -332,6 +333,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ) @@ -347,6 +349,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ) diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 00c1a7ff91..0dc09e23cd 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; @@ -201,6 +201,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/radio.rs b/widget/src/radio.rs index 57acc03384..684b4ff574 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -10,7 +10,7 @@ use crate::core::widget; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Clipboard, Color, Element, Layout, Length, Pixels, Rectangle, Shell, Size, - Widget, + Widget, IME, }; pub use iced_style::radio::{Appearance, StyleSheet}; @@ -250,6 +250,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/row.rs b/widget/src/row.rs index 7ca90fbbe9..69564a28aa 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, Rectangle, Shell, - Widget, + Widget, IME, }; /// A container that distributes its contents horizontally. @@ -160,6 +160,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -175,6 +176,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 49aed2f0f9..6b13030312 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; @@ -294,6 +294,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { @@ -314,6 +315,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ) 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 { diff --git a/widget/src/slider.rs b/widget/src/slider.rs index ac0982c82f..45d75696ba 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; @@ -187,6 +187,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_editor.rs b/widget/src/text_editor.rs index 1708a2e5ce..a820cacce6 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..afff0383e8 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; @@ -26,12 +28,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 @@ -332,6 +336,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { @@ -341,6 +346,7 @@ where cursor, renderer, clipboard, + ime, shell, &mut self.value, self.size, @@ -595,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, @@ -626,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(); @@ -712,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 (width, offset) = measure_cursor_and_scroll_offset( + &state.value, + bounds, + cursor_index, + ); + ime.set_ime_position( + (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); + 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; } } @@ -1034,6 +1079,151 @@ 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 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(); + 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, offset) = measure_cursor_and_scroll_offset( + ¶graph, + text_bounds, + before_preedit_cursor + preedit_text.chars().count(), + ); + let position = ( + (text_bounds.x + width - offset) 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; + } + } + } + Event::IME(ime::Event::IMEDisabled) => { + let state = state(); + state.ime_state = None; + return event::Status::Captured; + } _ => {} } @@ -1098,6 +1288,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(); @@ -1108,12 +1307,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() @@ -1145,7 +1362,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, @@ -1188,24 +1404,72 @@ 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 { 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; + 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((_, _, _, _, whole_paragraph)) = preedit_text { + whole_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) @@ -1250,6 +1514,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, @@ -1281,6 +1546,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..ae07b97acf --- /dev/null +++ b/widget/src/text_input/ime_state.rs @@ -0,0 +1,142 @@ +use iced_renderer::core::text::Paragraph; + +/// +/// +#[derive(Debug, Default, Clone)] +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 + BoldLine(usize, usize), + // indicate like IBus-MOZC + Cursor(usize), +} + +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, + 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 + } + } + + /// 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/widget/src/toggler.rs b/widget/src/toggler.rs index 476c8330c1..c50f56a5c7 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; @@ -223,6 +225,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/tooltip.rs b/widget/src/tooltip.rs index 9e102c5671..b3b7a29abc 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::event::{self, Event}; use crate::core::layout::{self, Layout}; @@ -152,6 +154,7 @@ where cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, + ime: &dyn IME, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { @@ -177,6 +180,7 @@ where cursor, renderer, clipboard, + ime, shell, viewport, ) diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 01d3359ca5..0ad6ce61a5 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; @@ -184,6 +186,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/winit/src/application.rs b/winit/src/application.rs index 315e34d93e..6b685ecff6 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; @@ -301,6 +302,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, @@ -323,6 +325,7 @@ async fn run_instance( init_command, &mut runtime, &mut clipboard, + &ime, &mut should_exit, &mut proxy, &mut debug, @@ -367,6 +370,7 @@ async fn run_instance( state.cursor(), &mut renderer, &mut clipboard, + &ime, &mut messages, ); @@ -396,6 +400,7 @@ async fn run_instance( &state, &mut renderer, &mut runtime, + &ime, &mut clipboard, &mut should_exit, &mut proxy, @@ -434,6 +439,7 @@ async fn run_instance( state.cursor(), &mut renderer, &mut clipboard, + &ime, &mut messages, ); @@ -455,7 +461,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); @@ -662,6 +668,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, @@ -695,6 +702,7 @@ pub fn update( command, runtime, clipboard, + ime, should_exit, proxy, debug, @@ -717,6 +725,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, @@ -748,6 +757,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 3ecd044c8a..e0f62b75e6 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; @@ -136,6 +137,22 @@ pub fn window_event( Some(Event::Window(window::Event::Moved { x, y })) } + WindowEvent::Ime(ime) => match ime { + winit::event::Ime::Enabled => { + Some(Event::IME(ime::Event::IMEEnabled)) + } + winit::event::Ime::Preedit(text, range) => { + // range parameter is used to mark converting position. + + Some(Event::IME(ime::Event::IMEPreedit(text.clone(), *range))) + } + winit::event::Ime::Commit(text) => { + Some(Event::IME(ime::Event::IMECommit(text.clone()))) + } + winit::event::Ime::Disabled => { + Some(Event::IME(ime::Event::IMEDisabled)) + } + }, _ => None, } } diff --git a/winit/src/ime.rs b/winit/src/ime.rs new file mode 100644 index 0000000000..c242b6c02f --- /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 95b55bb990..6a2ffc440a 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -37,6 +37,7 @@ pub use winit; pub mod application; pub mod clipboard; pub mod conversion; +pub mod ime; pub mod settings; #[cfg(feature = "system")] @@ -52,6 +53,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;