Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions crates/eframe/src/web/text_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,7 @@ impl TextAgent {
};

let on_composition_start = {
let input = input.clone();
move |_: web_sys::CompositionEvent, runner: &mut AppRunner| {
input.set_value("");
let event = egui::Event::Ime(egui::ImeEvent::Enabled);
runner.input.raw.events.push(event);
// Repaint moves the text agent into place,
// see `move_to` in `AppRunner::handle_platform_output`.
runner.needs_repaint.repaint_asap();
Expand Down
64 changes: 5 additions & 59 deletions crates/egui-winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,6 @@ pub struct State {
/// Only one touch will be interpreted as pointer at any time.
pointer_touch_id: Option<u64>,

/// track ime state
has_sent_ime_enabled: bool,

#[cfg(feature = "accesskit")]
pub accesskit: Option<accesskit_winit::Adapter>,

Expand Down Expand Up @@ -150,8 +147,6 @@ impl State {
simulate_touch_screen: false,
pointer_touch_id: None,

has_sent_ime_enabled: false,

#[cfg(feature = "accesskit")]
accesskit: None,

Expand Down Expand Up @@ -689,17 +684,11 @@ impl State {
// }

match ime {
winit::event::Ime::Enabled => {
if cfg!(target_os = "linux") {
// This event means different things in X11 and Wayland, but we can just
// ignore it and enable IME on the preedit event.
// See <https://github.com/rust-windowing/winit/issues/2498>
} else {
self.ime_event_enable();
}
}
winit::event::Ime::Preedit(text, Some(_cursor)) => {
self.ime_event_enable();
// [`winit::event::Ime::Enabled`] means different things in X11 and
// Wayland, but it doesn't matter to us.
// See <https://github.com/rust-windowing/winit/issues/2498>
winit::event::Ime::Enabled | winit::event::Ime::Disabled => {}
winit::event::Ime::Preedit(text, _) => {
self.egui_input
.events
.push(egui::Event::Ime(egui::ImeEvent::Preedit(text.clone())));
Expand All @@ -708,53 +697,10 @@ impl State {
self.egui_input
.events
.push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone())));
self.ime_event_disable();
}
winit::event::Ime::Disabled => {
self.ime_event_disable();
}
winit::event::Ime::Preedit(_, None) => {
if cfg!(target_os = "macos") {
// On macOS, when the user presses backspace to delete the
// last character in an IME composition, `winit` only emits
// `winit::event::Ime::Preedit("", None)` without a
// preceding `winit::event::Ime::Preedit("", Some(0, 0))`.
//
// The current implementation of `egui::TextEdit` relies on
// receiving an `egui::ImeEvent::Preedit("")` to remove the
// last character in the composition in this case, so we
// emit it here.
//
// This is guarded to macOS-only, as applying it on other
// platforms is unnecessary and can cause undesired
// behavior.
// See: https://github.com/emilk/egui/pull/7973
self.egui_input
.events
.push(egui::Event::Ime(egui::ImeEvent::Preedit(String::new())));
}

self.ime_event_disable();
}
}
}

pub fn ime_event_enable(&mut self) {
if !self.has_sent_ime_enabled {
self.egui_input
.events
.push(egui::Event::Ime(egui::ImeEvent::Enabled));
self.has_sent_ime_enabled = true;
}
}

pub fn ime_event_disable(&mut self) {
self.egui_input
.events
.push(egui::Event::Ime(egui::ImeEvent::Disabled));
self.has_sent_ime_enabled = false;
}

/// Returns `true` if the event was sent to egui.
pub fn on_mouse_motion(&mut self, delta: (f64, f64)) -> bool {
if !self.is_pointer_in_window() && !self.any_pointer_button_down {
Expand Down
7 changes: 7 additions & 0 deletions crates/egui/src/data/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,15 +605,22 @@ pub enum Event {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ImeEvent {
/// Notifies when the IME was enabled.
#[deprecated = "No longer used by egui"]
Enabled,

/// A new IME candidate is being suggested.
///
/// An empty preedit string indicates that the IME has been dismissed, while
/// a non-empty preedit string indicates that the IME is active.
Preedit(String),

/// IME composition ended with this final result.
///
/// The IME is considered dismissed after this event.
Commit(String),

/// Notifies when the IME was disabled.
#[deprecated = "No longer used by egui"]
Disabled,
}

Expand Down
3 changes: 3 additions & 0 deletions crates/egui/src/data/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ pub struct PlatformOutput {
/// This is set if, and only if, the user is currently editing text.
///
/// Useful for IME.
///
/// This field should only be set by the widget that currently owns IME
/// events (see [`crate::Memory::owns_ime_events`]).
pub ime: Option<IMEOutput>,

/// The difference in the widget tree since last frame.
Expand Down
60 changes: 60 additions & 0 deletions crates/egui/src/memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ pub struct Memory {
/// (e.g. relative to some other widget).
#[cfg_attr(feature = "persistence", serde(skip))]
popups: ViewportIdMap<OpenPopup>,

/// When the last IME interruption was made.
#[cfg_attr(feature = "persistence", serde(skip))]
ime_interruption_time: ImeInterruptionTime,
}

#[derive(Clone, Copy, Debug, Default)]
enum ImeInterruptionTime {
#[default]
None,

/// The IME was interrupted in the current frame.
ThisFrame,

/// The IME was interrupted in the previous frame.
LastFrame,
}

impl Default for Memory {
Expand All @@ -133,6 +149,7 @@ impl Default for Memory {
popups: Default::default(),
everything_is_visible: Default::default(),
add_fonts: Default::default(),
ime_interruption_time: Default::default(),
};
slf.interactions.entry(slf.viewport_id).or_default();
slf.areas.entry(slf.viewport_id).or_default();
Expand Down Expand Up @@ -761,6 +778,16 @@ impl Memory {

self.areas.entry(self.viewport_id).or_default();

match self.ime_interruption_time {
ImeInterruptionTime::ThisFrame => {
self.ime_interruption_time = ImeInterruptionTime::LastFrame;
}
ImeInterruptionTime::LastFrame => {
self.ime_interruption_time = ImeInterruptionTime::None;
}
ImeInterruptionTime::None => {}
}

// self.interactions is handled elsewhere

self.options.begin_pass(new_raw_input);
Expand Down Expand Up @@ -875,9 +902,12 @@ impl Memory {

/// Give keyboard focus to a specific widget.
/// See also [`crate::Response::request_focus`].
///
/// Calling this will interrupt IME composition.
#[inline(always)]
pub fn request_focus(&mut self, id: Id) {
self.focus_mut().focused_widget = Some(FocusWidget::new(id));
self.interrupt_ime();
}

/// Surrender keyboard focus for a specific widget.
Expand Down Expand Up @@ -993,6 +1023,36 @@ impl Memory {
pub(crate) fn focus_mut(&mut self) -> &mut Focus {
self.focus.entry(self.viewport_id).or_default()
}

/// Check if the widget owns IME events.
///
/// A widget should only consume IME events if this returns `true`. At most
/// one widget can own IME events for each frame.
pub fn owns_ime_events(&self, id: Id) -> bool {
let Some(focus) = self.focus() else {
return false;
};
// We check across two frames because the widget that called
// `interrupt_ime` may run after other widgets that call this method
// within the same frame.
if matches!(
self.ime_interruption_time,
ImeInterruptionTime::ThisFrame | ImeInterruptionTime::LastFrame
) {
return false;
}
focus.focused() == Some(id)
}

/// Interrupt the current IME composition, if any.
///
/// This causes [`Self::owns_ime_events`] to return `false` for all widgets
/// for the remainder of this frame and the next frame, giving time
/// for the IME to be dismissed (by making `platform_output.ime` be `None`
/// for at least one frame).
pub fn interrupt_ime(&mut self) {
self.ime_interruption_time = ImeInterruptionTime::ThisFrame;
}
}

/// State of an open popup.
Expand Down
Loading
Loading