feat(ime): minimal IME support for the text_box widget (#539)
* Support CJK input method * Fix the input method window occasionally jumps around Use the previous caret(text cursor) position to place the input method window when the position is not available * refactor: Omit calling request_input_method() if doesn't focus - iced UserInterface.update() starts with InputMethod::Disabled state, and merges any input_method_request() which is InputMethod::Enabled in widgets tree traversal. - We don't enable the input method feature if the widget doesn't have focus, we can safely omit request_input_method() call itself.
This commit is contained in:
parent
5f96f6191b
commit
4c53d607f7
1 changed files with 68 additions and 0 deletions
|
|
@ -7,6 +7,7 @@ use cosmic::{
|
||||||
Border, Radians, Shell, Transformation,
|
Border, Radians, Shell, Transformation,
|
||||||
clipboard::Clipboard,
|
clipboard::Clipboard,
|
||||||
image,
|
image,
|
||||||
|
input_method::{Event as InputMethodEvent, InputMethod, Preedit, Purpose},
|
||||||
keyboard::{Key, key::Named},
|
keyboard::{Key, key::Named},
|
||||||
layout::{self, Layout},
|
layout::{self, Layout},
|
||||||
renderer::{self, Quad, Renderer as _},
|
renderer::{self, Quad, Renderer as _},
|
||||||
|
|
@ -16,6 +17,7 @@ use cosmic::{
|
||||||
operation::{self, Operation},
|
operation::{self, Operation},
|
||||||
tree,
|
tree,
|
||||||
},
|
},
|
||||||
|
window::Event as WindowEvent,
|
||||||
},
|
},
|
||||||
iced::{
|
iced::{
|
||||||
Color, Element, Length, Padding, Point, Rectangle, Size, Vector,
|
Color, Element, Length, Padding, Point, Rectangle, Size, Vector,
|
||||||
|
|
@ -127,6 +129,35 @@ where
|
||||||
self.on_focus = Some(on_focus);
|
self.on_focus = Some(on_focus);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn input_method<'b>(
|
||||||
|
&self,
|
||||||
|
state: &'b State,
|
||||||
|
scale_factor: f32,
|
||||||
|
layout: Layout<'_>,
|
||||||
|
) -> InputMethod<&'b str> {
|
||||||
|
if !state.is_focused {
|
||||||
|
return InputMethod::Disabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
let editor_pos = layout.position() + [self.padding.left, self.padding.top].into();
|
||||||
|
let (caret_x, caret_y) = state.caret_position;
|
||||||
|
InputMethod::Enabled {
|
||||||
|
cursor: Rectangle::new(
|
||||||
|
Point::new(
|
||||||
|
editor_pos.x
|
||||||
|
+ (caret_x as f32 + state.editor_offset_x.get() as f32) / scale_factor,
|
||||||
|
editor_pos.y + (caret_y as f32) / scale_factor,
|
||||||
|
),
|
||||||
|
Size::new(
|
||||||
|
1.0,
|
||||||
|
(self.metrics.scale(scale_factor).line_height) / scale_factor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
purpose: Purpose::Normal,
|
||||||
|
preedit: state.preedit.as_ref().map(Preedit::as_ref),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_box<'a, Message>(
|
pub fn text_box<'a, Message>(
|
||||||
|
|
@ -1101,6 +1132,33 @@ where
|
||||||
}
|
}
|
||||||
state.modifiers = *modifiers;
|
state.modifiers = *modifiers;
|
||||||
}
|
}
|
||||||
|
Event::InputMethod(event) => match event {
|
||||||
|
InputMethodEvent::Opened | InputMethodEvent::Closed => {
|
||||||
|
let metrics = self.metrics.scale(scale_factor);
|
||||||
|
state.preedit = matches!(event, InputMethodEvent::Opened).then(|| {
|
||||||
|
let mut preedit = Preedit::new();
|
||||||
|
preedit.text_size = Some(metrics.font_size.into());
|
||||||
|
preedit
|
||||||
|
});
|
||||||
|
}
|
||||||
|
InputMethodEvent::Preedit(content, selection) => {
|
||||||
|
if state.is_focused {
|
||||||
|
state.preedit = Some(Preedit {
|
||||||
|
content: content.to_owned(),
|
||||||
|
selection: selection.clone(),
|
||||||
|
text_size: Some(self.metrics.font_size.into()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InputMethodEvent::Commit(text) => {
|
||||||
|
if state.is_focused {
|
||||||
|
editor.start_change();
|
||||||
|
editor.insert_string(&text, None);
|
||||||
|
editor.finish_change();
|
||||||
|
shell.capture_event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
Event::Mouse(MouseEvent::ButtonPressed(button)) => {
|
Event::Mouse(MouseEvent::ButtonPressed(button)) => {
|
||||||
if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
||||||
state.is_focused = true;
|
state.is_focused = true;
|
||||||
|
|
@ -1336,6 +1394,12 @@ where
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::Window(WindowEvent::RedrawRequested(_now)) => {
|
||||||
|
if state.is_focused {
|
||||||
|
state.caret_position = editor.cursor_position().unwrap_or(state.caret_position);
|
||||||
|
shell.request_input_method(&self.input_method(state, scale_factor, layout));
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1385,6 +1449,8 @@ pub struct State {
|
||||||
scrollbar_h_rect: Cell<Option<Rectangle<f32>>>,
|
scrollbar_h_rect: Cell<Option<Rectangle<f32>>>,
|
||||||
handle_opt: Mutex<Option<image::Handle>>,
|
handle_opt: Mutex<Option<image::Handle>>,
|
||||||
shift_anchor: Mutex<Option<Cursor>>,
|
shift_anchor: Mutex<Option<Cursor>>,
|
||||||
|
caret_position: (i32, i32),
|
||||||
|
preedit: Option<Preedit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
|
@ -1402,6 +1468,8 @@ impl State {
|
||||||
scrollbar_h_rect: Cell::new(None),
|
scrollbar_h_rect: Cell::new(None),
|
||||||
handle_opt: Mutex::new(None),
|
handle_opt: Mutex::new(None),
|
||||||
shift_anchor: Mutex::new(None),
|
shift_anchor: Mutex::new(None),
|
||||||
|
caret_position: (0, 0),
|
||||||
|
preedit: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue