summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar KENZ <KENZ.gelsoft@gmail.com>2025-01-10 07:12:31 +0900
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2025-02-02 17:44:13 +0100
commit7db5256b720c3ecbe7c1cce7a1b47fd03151e03a (patch)
treeccf08e3f76e27d0871185b786ece83f970dddc77
parent599d8b560bec8036c5ddda62a7bf0a540bdec396 (diff)
downloadiced-7db5256b720c3ecbe7c1cce7a1b47fd03151e03a.tar.gz
iced-7db5256b720c3ecbe7c1cce7a1b47fd03151e03a.tar.bz2
iced-7db5256b720c3ecbe7c1cce7a1b47fd03151e03a.zip
Draft `input_method` support
-rw-r--r--core/src/event.rs4
-rw-r--r--core/src/input_method.rs24
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/shell.rs25
-rw-r--r--runtime/src/user_interface.rs14
-rw-r--r--widget/src/combo_box.rs6
-rw-r--r--widget/src/lazy/component.rs2
-rw-r--r--widget/src/scrollable.rs19
-rw-r--r--widget/src/text_editor.rs69
-rw-r--r--widget/src/text_input.rs95
-rw-r--r--winit/src/conversion.rs11
-rw-r--r--winit/src/program.rs162
-rw-r--r--winit/src/program/state.rs14
13 files changed, 420 insertions, 27 deletions
diff --git a/core/src/event.rs b/core/src/event.rs
index b6cf321e..323c5ffd 100644
--- a/core/src/event.rs
+++ b/core/src/event.rs
@@ -1,4 +1,5 @@
//! Handle events of a user interface.
+use crate::input_method;
use crate::keyboard;
use crate::mouse;
use crate::touch;
@@ -23,6 +24,9 @@ pub enum Event {
/// A touch event
Touch(touch::Event),
+
+ /// A input method event
+ InputMethod(input_method::Event),
}
/// The status of an [`Event`] after being processed.
diff --git a/core/src/input_method.rs b/core/src/input_method.rs
new file mode 100644
index 00000000..ceda39c3
--- /dev/null
+++ b/core/src/input_method.rs
@@ -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,
+}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 16b3aa0f..c7c38044 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -17,6 +17,7 @@ pub mod event;
pub mod font;
pub mod gradient;
pub mod image;
+pub mod input_method;
pub mod keyboard;
pub mod layout;
pub mod mouse;
@@ -72,6 +73,7 @@ pub use renderer::Renderer;
pub use rotation::Rotation;
pub use settings::Settings;
pub use shadow::Shadow;
+pub use shell::CaretInfo;
pub use shell::Shell;
pub use size::Size;
pub use svg::Svg;
diff --git a/core/src/shell.rs b/core/src/shell.rs
index 12ebbaa8..d2c1b9ec 100644
--- a/core/src/shell.rs
+++ b/core/src/shell.rs
@@ -1,6 +1,15 @@
-use crate::event;
use crate::time::Instant;
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.
///
@@ -15,6 +24,7 @@ pub struct Shell<'a, Message> {
redraw_request: Option<window::RedrawRequest>,
is_layout_invalid: bool,
are_widgets_invalid: bool,
+ caret_info: Option<CaretInfo>,
}
impl<'a, Message> Shell<'a, Message> {
@@ -26,6 +36,7 @@ impl<'a, Message> Shell<'a, Message> {
redraw_request: None,
is_layout_invalid: false,
are_widgets_invalid: false,
+ caret_info: None,
}
}
@@ -80,6 +91,16 @@ impl<'a, Message> Shell<'a, Message> {
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.
pub fn is_layout_invalid(&self) -> bool {
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 || other.is_layout_invalid;
diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs
index b2826f71..6d3360d0 100644
--- a/runtime/src/user_interface.rs
+++ b/runtime/src/user_interface.rs
@@ -5,7 +5,9 @@ use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget;
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;
/// A set of interactive graphical elements with a specific [`Layout`].
@@ -187,6 +189,7 @@ where
let mut outdated = false;
let mut redraw_request = None;
+ let mut caret_info = None;
let mut manual_overlay = ManuallyDrop::new(
self.root
@@ -230,6 +233,7 @@ where
}
_ => {}
}
+ caret_info = caret_info.or(shell.caret_info());
if shell.is_layout_invalid() {
let _ = ManuallyDrop::into_inner(manual_overlay);
@@ -332,6 +336,7 @@ where
}
_ => {}
}
+ caret_info = caret_info.or(shell.caret_info());
shell.revalidate_layout(|| {
self.base = self.root.as_widget().layout(
@@ -355,7 +360,10 @@ where
if outdated {
State::Outdated
} else {
- State::Updated { redraw_request }
+ State::Updated {
+ redraw_request,
+ caret_info,
+ }
},
event_statuses,
)
@@ -646,5 +654,7 @@ pub enum State {
Updated {
/// The [`window::RedrawRequest`] when a redraw should be performed.
redraw_request: Option<window::RedrawRequest>,
+ /// TODO
+ caret_info: Option<CaretInfo>,
},
}
diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs
index 500d2bec..d7c7c922 100644
--- a/widget/src/combo_box.rs
+++ b/widget/src/combo_box.rs
@@ -564,6 +564,7 @@ where
}
}
}
+ shell.update_caret_info(local_shell.caret_info());
// Then finally react to them here
for message in local_messages {
@@ -742,6 +743,8 @@ where
published_message_to_shell = true;
// Unfocus the input
+ let mut local_messages = Vec::new();
+ let mut local_shell = Shell::new(&mut local_messages);
self.text_input.update(
&mut tree.children[0],
Event::Mouse(mouse::Event::ButtonPressed(
@@ -751,9 +754,10 @@ where
mouse::Cursor::Unavailable,
renderer,
clipboard,
- &mut Shell::new(&mut vec![]),
+ &mut local_shell,
viewport,
);
+ shell.update_caret_info(local_shell.caret_info());
}
});
diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs
index 15b8b62e..b9fbde58 100644
--- a/widget/src/lazy/component.rs
+++ b/widget/src/lazy/component.rs
@@ -355,6 +355,7 @@ where
}
}
}
+ shell.update_caret_info(local_shell.caret_info());
if !local_messages.is_empty() {
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() {
let mut inner =
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index 312aee29..7df7a0e5 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -33,8 +33,9 @@ use crate::core::widget::operation::{self, Operation};
use crate::core::widget::tree::{self, Tree};
use crate::core::window;
use crate::core::{
- self, Background, Clipboard, Color, Element, Event, Layout, Length,
- Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
+ self, Background, CaretInfo, Clipboard, Color, Element, Event, Layout,
+ Length, Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector,
+ Widget,
};
use crate::runtime::task::{self, Task};
use crate::runtime::Action;
@@ -729,6 +730,7 @@ where
let translation =
state.translation(self.direction, bounds, content_bounds);
+ let children_may_have_caret = shell.caret_info().is_none();
self.content.as_widget_mut().update(
&mut tree.children[0],
event.clone(),
@@ -743,6 +745,19 @@ where
..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!(
diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs
index f1ec589b..2931e7f6 100644
--- a/widget/src/text_editor.rs
+++ b/widget/src/text_editor.rs
@@ -33,6 +33,7 @@
//! ```
use crate::core::alignment;
use crate::core::clipboard::{self, Clipboard};
+use crate::core::input_method;
use crate::core::keyboard;
use crate::core::keyboard::key;
use crate::core::layout::{self, Layout};
@@ -46,8 +47,8 @@ use crate::core::widget::operation;
use crate::core::widget::{self, Widget};
use crate::core::window;
use crate::core::{
- Background, Border, Color, Element, Event, Length, Padding, Pixels, Point,
- Rectangle, Shell, Size, SmolStr, Theme, Vector,
+ Background, Border, CaretInfo, Color, Element, Event, Length, Padding,
+ Pixels, Point, Rectangle, Shell, Size, SmolStr, Theme, Vector,
};
use std::borrow::Cow;
@@ -322,6 +323,46 @@ where
self.class = class.into();
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`].
@@ -605,7 +646,7 @@ where
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
- _renderer: &Renderer,
+ renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
@@ -701,6 +742,11 @@ where
}));
shell.capture_event();
}
+ Update::Commit(text) => {
+ shell.publish(on_edit(Action::Edit(Edit::Paste(
+ Arc::new(text),
+ ))));
+ }
Update::Binding(binding) => {
fn apply_binding<
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 {
self.last_status = Some(status);
} else if self
@@ -1129,6 +1188,7 @@ enum Update<Message> {
Drag(Point),
Release,
Scroll(f32),
+ Commit(String),
Binding(Binding<Message>),
}
@@ -1191,6 +1251,9 @@ impl<Message> Update<Message> {
}
_ => None,
},
+ Event::InputMethod(input_method::Event::Commit(text)) => {
+ Some(Update::Commit(text))
+ }
Event::Keyboard(keyboard::Event::KeyPressed {
key,
modifiers,
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index 57ebe46a..f2756b5b 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -36,6 +36,7 @@ mod value;
pub mod cursor;
pub use cursor::Cursor;
+use iced_runtime::core::input_method;
pub use value::Value;
use editor::Editor;
@@ -56,8 +57,8 @@ use crate::core::widget::operation::{self, Operation};
use crate::core::widget::tree::{self, Tree};
use crate::core::window;
use crate::core::{
- Background, Border, Color, Element, Event, Layout, Length, Padding, Pixels,
- Point, Rectangle, Shell, Size, Theme, Vector, Widget,
+ Background, Border, CaretInfo, Color, Element, Event, Layout, Length,
+ Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
};
use crate::runtime::task::{self, Task};
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
/// [`Value`] if provided.
///
@@ -1197,6 +1250,31 @@ where
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) => {
let state = state::<Renderer>(tree);
@@ -1256,6 +1334,19 @@ where
Status::Active
};
+ shell.update_caret_info(if state.is_focused() {
+ let rect = self
+ .caret_rect(tree, layout, Some(&self.value))
+ .unwrap_or(Rectangle::with_size(Size::<f32>::default()));
+ let bottom_left = Point::new(rect.x, rect.y + rect.height);
+ Some(CaretInfo {
+ position: bottom_left,
+ input_method_allowed: true,
+ })
+ } else {
+ None
+ });
+
if let Event::Window(window::Event::RedrawRequested(_now)) = event {
self.last_status = Some(status);
} else if self
diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs
index 462be65b..a289f060 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.13/runtime
+use crate::core::input_method;
use crate::core::keyboard;
use crate::core::mouse;
use crate::core::touch;
@@ -283,6 +284,16 @@ pub fn window_event(
self::modifiers(new_modifiers.state()),
)))
}
+ WindowEvent::Ime(ime) => {
+ use winit::event::Ime;
+ println!("ime event: {:?}", ime);
+ Some(Event::InputMethod(match ime {
+ Ime::Enabled => input_method::Event::Enabled,
+ Ime::Preedit(s, size) => input_method::Event::Preedit(s, size),
+ Ime::Commit(s) => input_method::Event::Commit(s),
+ Ime::Disabled => input_method::Event::Disabled,
+ }))
+ }
WindowEvent::Focused(focused) => Some(Event::Window(if focused {
window::Event::Focused
} else {
diff --git a/winit/src/program.rs b/winit/src/program.rs
index d8436212..688d6731 100644
--- a/winit/src/program.rs
+++ b/winit/src/program.rs
@@ -3,6 +3,8 @@ mod state;
mod window_manager;
pub use state::State;
+use winit::dpi::LogicalPosition;
+use winit::dpi::LogicalSize;
use crate::conversion;
use crate::core;
@@ -579,6 +581,8 @@ async fn run_instance<P, C>(
let mut clipboard = Clipboard::unconnected();
let mut compositor_receiver: Option<oneshot::Receiver<_>> = None;
+ let mut preedit = Preedit::<P>::new();
+
debug.startup_finished();
loop {
@@ -873,17 +877,27 @@ async fn run_instance<P, C>(
});
if let user_interface::State::Updated {
- redraw_request: Some(redraw_request),
+ redraw_request,
+ caret_info,
} = ui_state
{
match redraw_request {
- window::RedrawRequest::NextFrame => {
+ Some(window::RedrawRequest::NextFrame) => {
window.raw.request_redraw();
window.redraw_at = None;
}
- window::RedrawRequest::At(at) => {
+ Some(window::RedrawRequest::At(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();
match ui_state {
- #[cfg(not(
- feature = "unconditional-rendering"
- ))]
user_interface::State::Updated {
- redraw_request: Some(redraw_request),
- } => match redraw_request {
- window::RedrawRequest::NextFrame => {
- window.raw.request_redraw();
- window.redraw_at = None;
+ redraw_request: _redraw_request,
+ caret_info,
+ } => {
+ #[cfg(not(
+ feature = "unconditional-rendering"
+ ))]
+ match _redraw_request {
+ Some(
+ window::RedrawRequest::NextFrame,
+ ) => {
+ window.raw.request_redraw();
+ window.redraw_at = None;
+ }
+ Some(window::RedrawRequest::At(at)) => {
+ window.redraw_at = Some(at);
+ }
+ None => {}
}
- window::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 => {
uis_stale = true;
}
- user_interface::State::Updated { .. } => {}
}
for (event, status) in window_events
@@ -1138,6 +1165,111 @@ async fn run_instance<P, C>(
let _ = ManuallyDrop::into_inner(user_interfaces);
}
+fn update_input_method<P, C>(
+ window: &mut crate::program::window_manager::Window<P, C>,
+ preedit: &mut Preedit<P>,
+ caret_info: &crate::core::CaretInfo,
+) where
+ P: Program,
+ C: Compositor<Renderer = P::Renderer> + 'static,
+{
+ window.raw.set_ime_allowed(caret_info.input_method_allowed);
+ window.raw.set_ime_cursor_area(
+ LogicalPosition::new(caret_info.position.x, caret_info.position.y),
+ LogicalSize::new(10, 10),
+ );
+
+ let text = window.state.preedit();
+ if !text.is_empty() {
+ preedit.update(text.as_str(), &window.renderer);
+ preedit.fill(
+ &mut window.renderer,
+ window.state.text_color(),
+ window.state.background_color(),
+ caret_info.position,
+ );
+ }
+}
+
+struct Preedit<P: Program> {
+ content: Option<<P::Renderer as core::text::Renderer>::Paragraph>,
+}
+
+impl<P: Program> Preedit<P> {
+ fn new() -> Self {
+ Self { content: None }
+ }
+
+ fn update(&mut self, text: &str, renderer: &P::Renderer) {
+ use core::text::Paragraph as _;
+ use core::text::Renderer as _;
+
+ self.content = Some(
+ <P::Renderer as core::text::Renderer>::Paragraph::with_text(
+ core::Text::<&str, <P::Renderer as core::text::Renderer>::Font> {
+ content: text,
+ bounds: Size::INFINITY,
+ size: renderer.default_size(),
+ line_height: core::text::LineHeight::default(),
+ font: renderer.default_font(),
+ horizontal_alignment: core::alignment::Horizontal::Left,
+ vertical_alignment: core::alignment::Vertical::Top, //Bottom,
+ shaping: core::text::Shaping::Advanced,
+ wrapping: core::text::Wrapping::None,
+ },
+ ),
+ );
+ }
+
+ fn fill(
+ &self,
+ renderer: &mut P::Renderer,
+ fore_color: core::Color,
+ bg_color: core::Color,
+ caret_position: Point,
+ ) {
+ use core::text::Paragraph as _;
+ use core::text::Renderer as _;
+ use core::Renderer as _;
+
+ let Some(ref content) = self.content else {
+ return;
+ };
+ if content.min_width() < 1.0 {
+ return;
+ }
+
+ let top_left = Point::new(
+ caret_position.x,
+ caret_position.y - content.min_height(),
+ );
+ let bounds = core::Rectangle::new(top_left, content.min_bounds());
+ renderer.with_layer(bounds, |renderer| {
+ renderer.fill_quad(
+ core::renderer::Quad {
+ bounds,
+ ..Default::default()
+ },
+ core::Background::Color(bg_color),
+ );
+
+ let underline = 2.;
+ renderer.fill_quad(
+ core::renderer::Quad {
+ bounds: bounds.shrink(core::Padding {
+ top: bounds.height - underline,
+ ..Default::default()
+ }),
+ ..Default::default()
+ },
+ core::Background::Color(fore_color),
+ );
+
+ renderer.fill_paragraph(content, top_left, fore_color, bounds);
+ });
+ }
+}
+
/// Builds a window's [`UserInterface`] for the [`Program`].
fn build_user_interface<'a, P: Program>(
program: &'a P,
diff --git a/winit/src/program/state.rs b/winit/src/program/state.rs
index e883d04a..2b5710ac 100644
--- a/winit/src/program/state.rs
+++ b/winit/src/program/state.rs
@@ -4,7 +4,7 @@ use crate::core::{Color, Size};
use crate::graphics::Viewport;
use crate::program::Program;
-use winit::event::{Touch, WindowEvent};
+use winit::event::{Ime, Touch, WindowEvent};
use winit::window::Window;
use std::fmt::{Debug, Formatter};
@@ -22,6 +22,7 @@ where
modifiers: winit::keyboard::ModifiersState,
theme: P::Theme,
style: theme::Style,
+ preedit: String,
}
impl<P: Program> Debug for State<P>
@@ -73,6 +74,7 @@ where
modifiers: winit::keyboard::ModifiersState::default(),
theme,
style,
+ preedit: String::default(),
}
}
@@ -136,6 +138,11 @@ where
self.style.text_color
}
+ /// TODO
+ pub fn preedit(&self) -> String {
+ self.preedit.clone()
+ }
+
/// Processes the provided window event and updates the [`State`] accordingly.
pub fn update(
&mut self,
@@ -179,6 +186,11 @@ where
WindowEvent::ModifiersChanged(new_modifiers) => {
self.modifiers = new_modifiers.state();
}
+ WindowEvent::Ime(ime) => {
+ if let Ime::Preedit(text, _) = ime {
+ self.preedit = text.clone();
+ }
+ }
#[cfg(feature = "debug")]
WindowEvent::KeyboardInput {
event: