Draft input_method support
This commit is contained in:
parent
599d8b560b
commit
7db5256b72
13 changed files with 420 additions and 27 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
//! Handle events of a user interface.
|
//! Handle events of a user interface.
|
||||||
|
use crate::input_method;
|
||||||
use crate::keyboard;
|
use crate::keyboard;
|
||||||
use crate::mouse;
|
use crate::mouse;
|
||||||
use crate::touch;
|
use crate::touch;
|
||||||
|
|
@ -23,6 +24,9 @@ pub enum Event {
|
||||||
|
|
||||||
/// A touch event
|
/// A touch event
|
||||||
Touch(touch::Event),
|
Touch(touch::Event),
|
||||||
|
|
||||||
|
/// 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
24
core/src/input_method.rs
Normal 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,
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
|
|
|
||||||
|
|
@ -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!(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue