Draft input_method support

This commit is contained in:
KENZ 2025-01-10 07:12:31 +09:00 committed by Héctor Ramón Jiménez
parent 599d8b560b
commit 7db5256b72
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
13 changed files with 420 additions and 27 deletions

View file

@ -1,4 +1,5 @@
//! Handle events of a user interface. //! Handle events of a user interface.
use crate::input_method;
use crate::keyboard; use crate::keyboard;
use crate::mouse; use crate::mouse;
use crate::touch; use crate::touch;
@ -23,6 +24,9 @@ pub enum Event {
/// A touch event /// A touch event
Touch(touch::Event), Touch(touch::Event),
/// A input method event
InputMethod(input_method::Event),
} }
/// The status of an [`Event`] after being processed. /// The status of an [`Event`] after being processed.

24
core/src/input_method.rs Normal file
View file

@ -0,0 +1,24 @@
//! Listen to input method events.
/// A input method 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 {
// These events correspond to underlying winit ime events.
// https://docs.rs/winit/latest/winit/event/enum.Ime.html
/// the IME was enabled.
Enabled,
/// new composing text should be set at the cursor position.
Preedit(String, Option<(usize, usize)>),
/// text should be inserted into the editor widget.
Commit(String),
/// the IME was disabled.
Disabled,
}

View file

@ -17,6 +17,7 @@ pub mod event;
pub mod font; pub mod font;
pub mod gradient; pub mod gradient;
pub mod image; pub mod image;
pub mod input_method;
pub mod keyboard; pub mod keyboard;
pub mod layout; pub mod layout;
pub mod mouse; pub mod mouse;
@ -72,6 +73,7 @@ pub use renderer::Renderer;
pub use rotation::Rotation; pub use rotation::Rotation;
pub use settings::Settings; pub use settings::Settings;
pub use shadow::Shadow; pub use shadow::Shadow;
pub use shell::CaretInfo;
pub use shell::Shell; pub use shell::Shell;
pub use size::Size; pub use size::Size;
pub use svg::Svg; pub use svg::Svg;

View file

@ -1,6 +1,15 @@
use crate::event;
use crate::time::Instant; use crate::time::Instant;
use crate::window; use crate::window;
use crate::{event, Point};
/// TODO
#[derive(Clone, Copy, Debug)]
pub struct CaretInfo {
/// TODO
pub position: Point,
/// TODO
pub input_method_allowed: bool,
}
/// A connection to the state of a shell. /// A connection to the state of a shell.
/// ///
@ -15,6 +24,7 @@ pub struct Shell<'a, Message> {
redraw_request: Option<window::RedrawRequest>, redraw_request: Option<window::RedrawRequest>,
is_layout_invalid: bool, is_layout_invalid: bool,
are_widgets_invalid: bool, are_widgets_invalid: bool,
caret_info: Option<CaretInfo>,
} }
impl<'a, Message> Shell<'a, Message> { impl<'a, Message> Shell<'a, Message> {
@ -26,6 +36,7 @@ impl<'a, Message> Shell<'a, Message> {
redraw_request: None, redraw_request: None,
is_layout_invalid: false, is_layout_invalid: false,
are_widgets_invalid: false, are_widgets_invalid: false,
caret_info: None,
} }
} }
@ -80,6 +91,16 @@ impl<'a, Message> Shell<'a, Message> {
self.redraw_request self.redraw_request
} }
/// TODO
pub fn update_caret_info(&mut self, caret_info: Option<CaretInfo>) {
self.caret_info = caret_info.or(self.caret_info);
}
/// TODO
pub fn caret_info(&self) -> Option<CaretInfo> {
self.caret_info
}
/// Returns whether the current layout is invalid or not. /// Returns whether the current layout is invalid or not.
pub fn is_layout_invalid(&self) -> bool { pub fn is_layout_invalid(&self) -> bool {
self.is_layout_invalid self.is_layout_invalid
@ -130,6 +151,8 @@ impl<'a, Message> Shell<'a, Message> {
); );
} }
self.update_caret_info(other.caret_info());
self.is_layout_invalid = self.is_layout_invalid =
self.is_layout_invalid || other.is_layout_invalid; self.is_layout_invalid || other.is_layout_invalid;

View file

@ -5,7 +5,9 @@ use crate::core::mouse;
use crate::core::renderer; use crate::core::renderer;
use crate::core::widget; use crate::core::widget;
use crate::core::window; use crate::core::window;
use crate::core::{Clipboard, Element, Layout, Rectangle, Shell, Size, Vector}; use crate::core::{
CaretInfo, Clipboard, Element, Layout, Rectangle, Shell, Size, Vector,
};
use crate::overlay; use crate::overlay;
/// A set of interactive graphical elements with a specific [`Layout`]. /// A set of interactive graphical elements with a specific [`Layout`].
@ -187,6 +189,7 @@ where
let mut outdated = false; let mut outdated = false;
let mut redraw_request = None; let mut redraw_request = None;
let mut caret_info = None;
let mut manual_overlay = ManuallyDrop::new( let mut manual_overlay = ManuallyDrop::new(
self.root self.root
@ -230,6 +233,7 @@ where
} }
_ => {} _ => {}
} }
caret_info = caret_info.or(shell.caret_info());
if shell.is_layout_invalid() { if shell.is_layout_invalid() {
let _ = ManuallyDrop::into_inner(manual_overlay); let _ = ManuallyDrop::into_inner(manual_overlay);
@ -332,6 +336,7 @@ where
} }
_ => {} _ => {}
} }
caret_info = caret_info.or(shell.caret_info());
shell.revalidate_layout(|| { shell.revalidate_layout(|| {
self.base = self.root.as_widget().layout( self.base = self.root.as_widget().layout(
@ -355,7 +360,10 @@ where
if outdated { if outdated {
State::Outdated State::Outdated
} else { } else {
State::Updated { redraw_request } State::Updated {
redraw_request,
caret_info,
}
}, },
event_statuses, event_statuses,
) )
@ -646,5 +654,7 @@ pub enum State {
Updated { Updated {
/// The [`window::RedrawRequest`] when a redraw should be performed. /// The [`window::RedrawRequest`] when a redraw should be performed.
redraw_request: Option<window::RedrawRequest>, redraw_request: Option<window::RedrawRequest>,
/// TODO
caret_info: Option<CaretInfo>,
}, },
} }

View file

@ -564,6 +564,7 @@ where
} }
} }
} }
shell.update_caret_info(local_shell.caret_info());
// Then finally react to them here // Then finally react to them here
for message in local_messages { for message in local_messages {
@ -742,6 +743,8 @@ where
published_message_to_shell = true; published_message_to_shell = true;
// Unfocus the input // Unfocus the input
let mut local_messages = Vec::new();
let mut local_shell = Shell::new(&mut local_messages);
self.text_input.update( self.text_input.update(
&mut tree.children[0], &mut tree.children[0],
Event::Mouse(mouse::Event::ButtonPressed( Event::Mouse(mouse::Event::ButtonPressed(
@ -751,9 +754,10 @@ where
mouse::Cursor::Unavailable, mouse::Cursor::Unavailable,
renderer, renderer,
clipboard, clipboard,
&mut Shell::new(&mut vec![]), &mut local_shell,
viewport, viewport,
); );
shell.update_caret_info(local_shell.caret_info());
} }
}); });

View file

@ -355,6 +355,7 @@ where
} }
} }
} }
shell.update_caret_info(local_shell.caret_info());
if !local_messages.is_empty() { if !local_messages.is_empty() {
let mut heads = self.state.take().unwrap().into_heads(); let mut heads = self.state.take().unwrap().into_heads();
@ -640,6 +641,7 @@ where
} }
} }
} }
shell.update_caret_info(local_shell.caret_info());
if !local_messages.is_empty() { if !local_messages.is_empty() {
let mut inner = let mut inner =

View file

@ -33,8 +33,9 @@ use crate::core::widget::operation::{self, Operation};
use crate::core::widget::tree::{self, Tree}; use crate::core::widget::tree::{self, Tree};
use crate::core::window; use crate::core::window;
use crate::core::{ use crate::core::{
self, Background, Clipboard, Color, Element, Event, Layout, Length, self, Background, CaretInfo, Clipboard, Color, Element, Event, Layout,
Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector,
Widget,
}; };
use crate::runtime::task::{self, Task}; use crate::runtime::task::{self, Task};
use crate::runtime::Action; use crate::runtime::Action;
@ -729,6 +730,7 @@ where
let translation = let translation =
state.translation(self.direction, bounds, content_bounds); state.translation(self.direction, bounds, content_bounds);
let children_may_have_caret = shell.caret_info().is_none();
self.content.as_widget_mut().update( self.content.as_widget_mut().update(
&mut tree.children[0], &mut tree.children[0],
event.clone(), event.clone(),
@ -743,6 +745,19 @@ where
..bounds ..bounds
}, },
); );
if children_may_have_caret {
if let Some(caret_info) = shell.caret_info() {
shell.update_caret_info(Some(CaretInfo {
position: Point::new(
caret_info.position.x - translation.x,
caret_info.position.y - translation.y,
),
input_method_allowed: caret_info
.input_method_allowed,
}));
}
}
}; };
if matches!( if matches!(

View file

@ -33,6 +33,7 @@
//! ``` //! ```
use crate::core::alignment; use crate::core::alignment;
use crate::core::clipboard::{self, Clipboard}; use crate::core::clipboard::{self, Clipboard};
use crate::core::input_method;
use crate::core::keyboard; use crate::core::keyboard;
use crate::core::keyboard::key; use crate::core::keyboard::key;
use crate::core::layout::{self, Layout}; use crate::core::layout::{self, Layout};
@ -46,8 +47,8 @@ use crate::core::widget::operation;
use crate::core::widget::{self, Widget}; use crate::core::widget::{self, Widget};
use crate::core::window; use crate::core::window;
use crate::core::{ use crate::core::{
Background, Border, Color, Element, Event, Length, Padding, Pixels, Point, Background, Border, CaretInfo, Color, Element, Event, Length, Padding,
Rectangle, Shell, Size, SmolStr, Theme, Vector, Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector,
}; };
use std::borrow::Cow; use std::borrow::Cow;
@ -322,6 +323,46 @@ where
self.class = class.into(); self.class = class.into();
self self
} }
fn caret_rect(
&self,
tree: &widget::Tree,
renderer: &Renderer,
layout: Layout<'_>,
) -> Option<Rectangle> {
let bounds = layout.bounds();
let internal = self.content.0.borrow_mut();
let state = tree.state.downcast_ref::<State<Highlighter>>();
let text_bounds = bounds.shrink(self.padding);
let translation = text_bounds.position() - Point::ORIGIN;
if let Some(_) = state.focus.as_ref() {
let position = match internal.editor.cursor() {
Cursor::Caret(position) => position,
Cursor::Selection(ranges) => ranges
.first()
.cloned()
.unwrap_or(Rectangle::default())
.position(),
};
Some(Rectangle::new(
position + translation,
Size::new(
1.0,
self.line_height
.to_absolute(
self.text_size
.unwrap_or_else(|| renderer.default_size()),
)
.into(),
),
))
} else {
None
}
}
} }
/// The content of a [`TextEditor`]. /// The content of a [`TextEditor`].
@ -605,7 +646,7 @@ where
event: Event, event: Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
_renderer: &Renderer, renderer: &Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
_viewport: &Rectangle, _viewport: &Rectangle,
@ -701,6 +742,11 @@ where
})); }));
shell.capture_event(); shell.capture_event();
} }
Update::Commit(text) => {
shell.publish(on_edit(Action::Edit(Edit::Paste(
Arc::new(text),
))));
}
Update::Binding(binding) => { Update::Binding(binding) => {
fn apply_binding< fn apply_binding<
H: text::Highlighter, H: text::Highlighter,
@ -825,6 +871,19 @@ where
} }
}; };
shell.update_caret_info(if state.is_focused() {
let rect = self
.caret_rect(tree, renderer, layout)
.unwrap_or(Rectangle::default());
let bottom_left = Point::new(rect.x, rect.y + rect.height);
Some(CaretInfo {
position: bottom_left,
input_method_allowed: true,
})
} else {
None
});
if is_redraw { if is_redraw {
self.last_status = Some(status); self.last_status = Some(status);
} else if self } else if self
@ -1129,6 +1188,7 @@ enum Update<Message> {
Drag(Point), Drag(Point),
Release, Release,
Scroll(f32), Scroll(f32),
Commit(String),
Binding(Binding<Message>), Binding(Binding<Message>),
} }
@ -1191,6 +1251,9 @@ impl<Message> Update<Message> {
} }
_ => None, _ => None,
}, },
Event::InputMethod(input_method::Event::Commit(text)) => {
Some(Update::Commit(text))
}
Event::Keyboard(keyboard::Event::KeyPressed { Event::Keyboard(keyboard::Event::KeyPressed {
key, key,
modifiers, modifiers,

View file

@ -36,6 +36,7 @@ mod value;
pub mod cursor; pub mod cursor;
pub use cursor::Cursor; pub use cursor::Cursor;
use iced_runtime::core::input_method;
pub use value::Value; pub use value::Value;
use editor::Editor; use editor::Editor;
@ -56,8 +57,8 @@ use crate::core::widget::operation::{self, Operation};
use crate::core::widget::tree::{self, Tree}; use crate::core::widget::tree::{self, Tree};
use crate::core::window; use crate::core::window;
use crate::core::{ use crate::core::{
Background, Border, Color, Element, Event, Layout, Length, Padding, Pixels, Background, Border, CaretInfo, Color, Element, Event, Layout, Length,
Point, Rectangle, Shell, Size, Theme, Vector, Widget, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
}; };
use crate::runtime::task::{self, Task}; use crate::runtime::task::{self, Task};
use crate::runtime::Action; use crate::runtime::Action;
@ -391,6 +392,58 @@ where
} }
} }
fn caret_rect(
&self,
tree: &Tree,
layout: Layout<'_>,
value: Option<&Value>,
) -> Option<Rectangle> {
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
let value = value.unwrap_or(&self.value);
let secure_value = self.is_secure.then(|| value.secure());
let value = secure_value.as_ref().unwrap_or(value);
let mut children_layout = layout.children();
let text_bounds = children_layout.next().unwrap().bounds();
if let Some(_) = state
.is_focused
.as_ref()
.filter(|focus| focus.is_window_focused)
{
let caret_index = match state.cursor.state(value) {
cursor::State::Index(position) => position,
cursor::State::Selection { start, end } => {
let left = start.min(end);
left
}
};
let text = state.value.raw();
let (caret_x, offset) = measure_cursor_and_scroll_offset(
text,
text_bounds,
caret_index,
);
let alignment_offset = alignment_offset(
text_bounds.width,
text.min_width(),
self.alignment,
);
let x = (text_bounds.x + caret_x).floor();
Some(Rectangle {
x: (alignment_offset - offset) + x,
y: text_bounds.y,
width: 1.0,
height: text_bounds.height,
})
} else {
None
}
}
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its /// Draws the [`TextInput`] with the given [`Renderer`], overriding its
/// [`Value`] if provided. /// [`Value`] if provided.
/// ///
@ -1197,6 +1250,31 @@ where
state.keyboard_modifiers = *modifiers; state.keyboard_modifiers = *modifiers;
} }
Event::InputMethod(input_method::Event::Commit(string)) => {
let state = state::<Renderer>(tree);
if let Some(focus) = &mut state.is_focused {
let Some(on_input) = &self.on_input else {
return;
};
state.is_pasting = None;
let mut editor =
Editor::new(&mut self.value, &mut state.cursor);
editor.paste(Value::new(&string));
let message = (on_input)(editor.contents());
shell.publish(message);
focus.updated_at = Instant::now();
update_cache(state, &self.value);
shell.capture_event();
}
}
Event::Window(window::Event::Unfocused) => { Event::Window(window::Event::Unfocused) => {
let state = state::<Renderer>(tree); let state = state::<Renderer>(tree);
@ -1256,6 +1334,19 @@ where
Status::Active Status::Active
}; };
shell.update_caret_info(if state.is_focused() {
let rect = self
.caret_rect(tree, layout, Some(&self.value))
.unwrap_or(Rectangle::with_size(Size::<f32>::default()));
let bottom_left = Point::new(rect.x, rect.y + rect.height);
Some(CaretInfo {
position: bottom_left,
input_method_allowed: true,
})
} else {
None
});
if let Event::Window(window::Event::RedrawRequested(_now)) = event { if let Event::Window(window::Event::RedrawRequested(_now)) = event {
self.last_status = Some(status); self.last_status = Some(status);
} else if self } else if self

View file

@ -2,6 +2,7 @@
//! //!
//! [`winit`]: https://github.com/rust-windowing/winit //! [`winit`]: https://github.com/rust-windowing/winit
//! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.13/runtime //! [`iced_runtime`]: https://github.com/iced-rs/iced/tree/0.13/runtime
use crate::core::input_method;
use crate::core::keyboard; use crate::core::keyboard;
use crate::core::mouse; use crate::core::mouse;
use crate::core::touch; use crate::core::touch;
@ -283,6 +284,16 @@ pub fn window_event(
self::modifiers(new_modifiers.state()), self::modifiers(new_modifiers.state()),
))) )))
} }
WindowEvent::Ime(ime) => {
use winit::event::Ime;
println!("ime event: {:?}", ime);
Some(Event::InputMethod(match ime {
Ime::Enabled => input_method::Event::Enabled,
Ime::Preedit(s, size) => input_method::Event::Preedit(s, size),
Ime::Commit(s) => input_method::Event::Commit(s),
Ime::Disabled => input_method::Event::Disabled,
}))
}
WindowEvent::Focused(focused) => Some(Event::Window(if focused { WindowEvent::Focused(focused) => Some(Event::Window(if focused {
window::Event::Focused window::Event::Focused
} else { } else {

View file

@ -3,6 +3,8 @@ mod state;
mod window_manager; mod window_manager;
pub use state::State; pub use state::State;
use winit::dpi::LogicalPosition;
use winit::dpi::LogicalSize;
use crate::conversion; use crate::conversion;
use crate::core; use crate::core;
@ -579,6 +581,8 @@ async fn run_instance<P, C>(
let mut clipboard = Clipboard::unconnected(); let mut clipboard = Clipboard::unconnected();
let mut compositor_receiver: Option<oneshot::Receiver<_>> = None; let mut compositor_receiver: Option<oneshot::Receiver<_>> = None;
let mut preedit = Preedit::<P>::new();
debug.startup_finished(); debug.startup_finished();
loop { loop {
@ -873,17 +877,27 @@ async fn run_instance<P, C>(
}); });
if let user_interface::State::Updated { if let user_interface::State::Updated {
redraw_request: Some(redraw_request), redraw_request,
caret_info,
} = ui_state } = ui_state
{ {
match redraw_request { match redraw_request {
window::RedrawRequest::NextFrame => { Some(window::RedrawRequest::NextFrame) => {
window.raw.request_redraw(); window.raw.request_redraw();
window.redraw_at = None; window.redraw_at = None;
} }
window::RedrawRequest::At(at) => { Some(window::RedrawRequest::At(at)) => {
window.redraw_at = Some(at); window.redraw_at = Some(at);
} }
None => {}
}
if let Some(caret_info) = caret_info {
update_input_method(
window,
&mut preedit,
&caret_info,
);
} }
} }
@ -1032,24 +1046,37 @@ async fn run_instance<P, C>(
window.raw.request_redraw(); window.raw.request_redraw();
match ui_state { match ui_state {
#[cfg(not(
feature = "unconditional-rendering"
))]
user_interface::State::Updated { user_interface::State::Updated {
redraw_request: Some(redraw_request), redraw_request: _redraw_request,
} => match redraw_request { caret_info,
window::RedrawRequest::NextFrame => { } => {
window.raw.request_redraw(); #[cfg(not(
window.redraw_at = None; feature = "unconditional-rendering"
))]
match _redraw_request {
Some(
window::RedrawRequest::NextFrame,
) => {
window.raw.request_redraw();
window.redraw_at = None;
}
Some(window::RedrawRequest::At(at)) => {
window.redraw_at = Some(at);
}
None => {}
} }
window::RedrawRequest::At(at) => {
window.redraw_at = Some(at); if let Some(caret_info) = caret_info {
update_input_method(
window,
&mut preedit,
&caret_info,
);
} }
}, }
user_interface::State::Outdated => { user_interface::State::Outdated => {
uis_stale = true; uis_stale = true;
} }
user_interface::State::Updated { .. } => {}
} }
for (event, status) in window_events for (event, status) in window_events
@ -1138,6 +1165,111 @@ async fn run_instance<P, C>(
let _ = ManuallyDrop::into_inner(user_interfaces); let _ = ManuallyDrop::into_inner(user_interfaces);
} }
fn update_input_method<P, C>(
window: &mut crate::program::window_manager::Window<P, C>,
preedit: &mut Preedit<P>,
caret_info: &crate::core::CaretInfo,
) where
P: Program,
C: Compositor<Renderer = P::Renderer> + 'static,
{
window.raw.set_ime_allowed(caret_info.input_method_allowed);
window.raw.set_ime_cursor_area(
LogicalPosition::new(caret_info.position.x, caret_info.position.y),
LogicalSize::new(10, 10),
);
let text = window.state.preedit();
if !text.is_empty() {
preedit.update(text.as_str(), &window.renderer);
preedit.fill(
&mut window.renderer,
window.state.text_color(),
window.state.background_color(),
caret_info.position,
);
}
}
struct Preedit<P: Program> {
content: Option<<P::Renderer as core::text::Renderer>::Paragraph>,
}
impl<P: Program> Preedit<P> {
fn new() -> Self {
Self { content: None }
}
fn update(&mut self, text: &str, renderer: &P::Renderer) {
use core::text::Paragraph as _;
use core::text::Renderer as _;
self.content = Some(
<P::Renderer as core::text::Renderer>::Paragraph::with_text(
core::Text::<&str, <P::Renderer as core::text::Renderer>::Font> {
content: text,
bounds: Size::INFINITY,
size: renderer.default_size(),
line_height: core::text::LineHeight::default(),
font: renderer.default_font(),
horizontal_alignment: core::alignment::Horizontal::Left,
vertical_alignment: core::alignment::Vertical::Top, //Bottom,
shaping: core::text::Shaping::Advanced,
wrapping: core::text::Wrapping::None,
},
),
);
}
fn fill(
&self,
renderer: &mut P::Renderer,
fore_color: core::Color,
bg_color: core::Color,
caret_position: Point,
) {
use core::text::Paragraph as _;
use core::text::Renderer as _;
use core::Renderer as _;
let Some(ref content) = self.content else {
return;
};
if content.min_width() < 1.0 {
return;
}
let top_left = Point::new(
caret_position.x,
caret_position.y - content.min_height(),
);
let bounds = core::Rectangle::new(top_left, content.min_bounds());
renderer.with_layer(bounds, |renderer| {
renderer.fill_quad(
core::renderer::Quad {
bounds,
..Default::default()
},
core::Background::Color(bg_color),
);
let underline = 2.;
renderer.fill_quad(
core::renderer::Quad {
bounds: bounds.shrink(core::Padding {
top: bounds.height - underline,
..Default::default()
}),
..Default::default()
},
core::Background::Color(fore_color),
);
renderer.fill_paragraph(content, top_left, fore_color, bounds);
});
}
}
/// Builds a window's [`UserInterface`] for the [`Program`]. /// Builds a window's [`UserInterface`] for the [`Program`].
fn build_user_interface<'a, P: Program>( fn build_user_interface<'a, P: Program>(
program: &'a P, program: &'a P,

View file

@ -4,7 +4,7 @@ use crate::core::{Color, Size};
use crate::graphics::Viewport; use crate::graphics::Viewport;
use crate::program::Program; use crate::program::Program;
use winit::event::{Touch, WindowEvent}; use winit::event::{Ime, Touch, WindowEvent};
use winit::window::Window; use winit::window::Window;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
@ -22,6 +22,7 @@ where
modifiers: winit::keyboard::ModifiersState, modifiers: winit::keyboard::ModifiersState,
theme: P::Theme, theme: P::Theme,
style: theme::Style, style: theme::Style,
preedit: String,
} }
impl<P: Program> Debug for State<P> impl<P: Program> Debug for State<P>
@ -73,6 +74,7 @@ where
modifiers: winit::keyboard::ModifiersState::default(), modifiers: winit::keyboard::ModifiersState::default(),
theme, theme,
style, style,
preedit: String::default(),
} }
} }
@ -136,6 +138,11 @@ where
self.style.text_color self.style.text_color
} }
/// TODO
pub fn preedit(&self) -> String {
self.preedit.clone()
}
/// Processes the provided window event and updates the [`State`] accordingly. /// Processes the provided window event and updates the [`State`] accordingly.
pub fn update( pub fn update(
&mut self, &mut self,
@ -179,6 +186,11 @@ where
WindowEvent::ModifiersChanged(new_modifiers) => { WindowEvent::ModifiersChanged(new_modifiers) => {
self.modifiers = new_modifiers.state(); self.modifiers = new_modifiers.state();
} }
WindowEvent::Ime(ime) => {
if let Ime::Preedit(text, _) = ime {
self.preedit = text.clone();
}
}
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
event: event: