feat(text_input): minimal IME support for COSMIC specific text widgets

This commit is contained in:
KENZ 2026-04-02 07:35:57 +09:00 committed by GitHub
parent 0ba668eb52
commit f6eb314606
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -22,10 +22,11 @@ use iced::Limits;
use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent};
use iced::clipboard::mime::AsMimeTypes;
use iced_core::event::{self, Event};
use iced_core::input_method::{self, InputMethod, Preedit};
use iced_core::mouse::{self, click};
use iced_core::overlay::Group;
use iced_core::renderer::{self, Renderer as CoreRenderer};
use iced_core::text::{self, Paragraph, Renderer, Text};
use iced_core::text::{self, Affinity, Paragraph, Renderer, Text};
use iced_core::time::{Duration, Instant};
use iced_core::touch;
use iced_core::widget::Id;
@ -2083,6 +2084,66 @@ pub fn update<'a, Message: Clone + 'static>(
state.keyboard_modifiers = *modifiers;
}
Event::InputMethod(event) => {
let state = state();
match event {
input_method::Event::Opened | input_method::Event::Closed => {
state.preedit = matches!(event, input_method::Event::Opened)
.then(input_method::Preedit::new);
shell.capture_event();
return;
}
input_method::Event::Preedit(content, selection) => {
if state.is_focused.is_some() {
state.preedit = Some(input_method::Preedit {
content: content.to_owned(),
selection: selection.clone(),
text_size: Some(size.into()),
});
shell.capture_event();
return;
}
}
input_method::Event::Commit(text) => {
let Some(focus) = &mut state.is_focused else {
return;
};
let Some(on_input) = on_input else {
return;
};
if state.is_read_only {
return;
}
focus.updated_at = Instant::now();
LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
let mut editor = Editor::new(unsecured_value, &mut state.cursor);
editor.paste(Value::new(&text));
let contents = editor.contents();
let unsecured_value = Value::new(&contents);
let message = if let Some(paste) = &on_paste {
(paste)(contents)
} else {
(on_input)(contents)
};
shell.publish(message);
state.is_pasting = None;
let value = if is_secure {
unsecured_value.secure()
} else {
unsecured_value
};
update_cache(state, &value);
shell.capture_event();
return;
}
}
}
Event::Window(window::Event::RedrawRequested(now)) => {
let state = state();
@ -2095,6 +2156,8 @@ pub fn update<'a, Message: Clone + 'static>(
now.checked_add(Duration::from_millis(millis_until_redraw as u64))
.unwrap_or(*now),
));
shell.request_input_method(&input_method(state, text_layout, unsecured_value));
} else if always_active {
shell.request_redraw();
}
@ -2269,6 +2332,43 @@ pub fn update<'a, Message: Clone + 'static>(
}
}
fn input_method<'b>(
state: &'b State,
text_layout: Layout<'_>,
value: &Value,
) -> InputMethod<&'b str> {
if state.is_focused() {
} else {
return InputMethod::Disabled;
};
let text_bounds = text_layout.bounds();
let cursor_index = match state.cursor.state(value) {
cursor::State::Index(position) => position,
cursor::State::Selection { start, end } => start.min(end),
};
let (cursor, offset) = measure_cursor_and_scroll_offset(
state.value.raw(),
text_bounds,
cursor_index,
value,
state.cursor.affinity(),
state.scroll_offset,
);
InputMethod::Enabled {
cursor: Rectangle::new(
Point::new(text_bounds.x + cursor - offset, text_bounds.y),
Size::new(1.0, text_bounds.height),
),
purpose: if state.is_secure {
input_method::Purpose::Secure
} else {
input_method::Purpose::Normal
},
preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref),
}
}
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
/// [`Value`] if provided.
///
@ -2789,6 +2889,7 @@ pub struct State {
is_pasting: Option<Value>,
last_click: Option<mouse::Click>,
cursor: Cursor,
preedit: Option<Preedit>,
keyboard_modifiers: keyboard::Modifiers,
scroll_offset: f32,
}
@ -2868,6 +2969,7 @@ impl State {
is_pasting: None,
last_click: None,
cursor: Cursor::default(),
preedit: None,
keyboard_modifiers: keyboard::Modifiers::default(),
scroll_offset: 0.0,
dirty: false,