Skip to content

Commit

Permalink
Fix IME output
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk authored and MarijnS95 committed Dec 12, 2023
1 parent 62fce54 commit 149dfdd
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 37 deletions.
12 changes: 6 additions & 6 deletions crates/eframe/src/web/app_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub struct AppRunner {
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
last_save_time: f64,
screen_reader: super::screen_reader::ScreenReader,
pub(crate) text_cursor_pos: Option<egui::Pos2>,
pub(crate) ime: Option<egui::output::IMEOutput>,
pub(crate) mutable_text_under_cursor: bool,
textures_delta: TexturesDelta,
}
Expand Down Expand Up @@ -108,7 +108,7 @@ impl AppRunner {
needs_repaint,
last_save_time: now_sec(),
screen_reader: Default::default(),
text_cursor_pos: None,
ime: None,
mutable_text_under_cursor: false,
textures_delta: Default::default(),
};
Expand Down Expand Up @@ -230,7 +230,7 @@ impl AppRunner {
copied_text,
events: _, // already handled
mutable_text_under_cursor,
text_cursor_pos,
ime,
#[cfg(feature = "accesskit")]
accesskit_update: _, // not currently implemented
} = platform_output;
Expand All @@ -250,9 +250,9 @@ impl AppRunner {

self.mutable_text_under_cursor = mutable_text_under_cursor;

if self.text_cursor_pos != text_cursor_pos {
super::text_agent::move_text_cursor(text_cursor_pos, self.canvas_id());
self.text_cursor_pos = text_cursor_pos;
if self.ime != ime {
super::text_agent::move_text_cursor(ime, self.canvas_id());
self.ime = ime;
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions crates/eframe/src/web/text_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,13 @@ fn is_mobile() -> Option<bool> {
// candidate window moves following text element (agent),
// so it appears that the IME candidate window moves with text cursor.
// On mobile devices, there is no need to do that.
pub fn move_text_cursor(cursor: Option<egui::Pos2>, canvas_id: &str) -> Option<()> {
pub fn move_text_cursor(ime: Option<egui::output::IMEOutput>, canvas_id: &str) -> Option<()> {
let style = text_agent().style();
// Note: movint agent on mobile devices will lead to unpredictable scroll.
// Note: moving agent on mobile devices will lead to unpredictable scroll.
if is_mobile() == Some(false) {
cursor.as_ref().and_then(|&egui::Pos2 { x, y }| {
ime.as_ref().and_then(|ime| {
let egui::Pos2 { x, y } = ime.cursor_rect.left_top();

let canvas = canvas_element(canvas_id)?;
let bounding_rect = text_agent().get_bounding_client_rect();
let y = (y + (canvas.scroll_top() + canvas.offset_top()) as f32)
Expand Down
20 changes: 12 additions & 8 deletions crates/egui-winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ impl State {
copied_text,
events: _, // handled above
mutable_text_under_cursor: _, // only used in eframe web
text_cursor_pos,
ime,
#[cfg(feature = "accesskit")]
accesskit_update,
} = platform_output;
Expand All @@ -678,19 +678,23 @@ impl State {
self.clipboard.set(copied_text);
}

let allow_ime = text_cursor_pos.is_some();
let allow_ime = ime.is_some();
if self.allow_ime != allow_ime {
self.allow_ime = allow_ime;
window.set_ime_allowed(allow_ime);
}

if let Some(egui::Pos2 { x, y }) = text_cursor_pos {
if let Some(ime) = ime {
let rect = ime.rect;
let pixels_per_point = self.pixels_per_point();
window.set_ime_cursor_area(
winit::dpi::LogicalPosition { x, y },
winit::dpi::LogicalSize {
// TODO: What size to use? New size arg in winit 0.29
width: 10,
height: 10,
winit::dpi::PhysicalPosition {
x: pixels_per_point * rect.min.x,
y: pixels_per_point * rect.min.y,
},
winit::dpi::PhysicalSize {
width: pixels_per_point * rect.width(),
height: pixels_per_point * rect.height(),
},
);
}
Expand Down
25 changes: 20 additions & 5 deletions crates/egui/src/data/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ impl FullOutput {
}
}

/// Information about text beeing edited.
///
/// Useful for IME.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct IMEOutput {
/// Where the [`crate::TextEdit`] is located on screen.
pub rect: crate::Rect,

/// Where the cursor is.
///
/// This is a very thin rectangle.
pub cursor_rect: crate::Rect,
}

/// The non-rendering part of what egui emits each frame.
///
/// You can access (and modify) this with [`crate::Context::output`].
Expand Down Expand Up @@ -83,10 +98,10 @@ pub struct PlatformOutput {
/// Use by `eframe` web to show/hide mobile keyboard and IME agent.
pub mutable_text_under_cursor: bool,

/// Screen-space position of text edit cursor (used for IME).
/// This is et if, and only if, the user is currently editing text.
///
/// Iff `Some`, the user is editing text.
pub text_cursor_pos: Option<crate::Pos2>,
/// Useful for IME.
pub ime: Option<IMEOutput>,

#[cfg(feature = "accesskit")]
pub accesskit_update: Option<accesskit::TreeUpdate>,
Expand Down Expand Up @@ -127,7 +142,7 @@ impl PlatformOutput {
copied_text,
mut events,
mutable_text_under_cursor,
text_cursor_pos,
ime,
#[cfg(feature = "accesskit")]
accesskit_update,
} = newer;
Expand All @@ -141,7 +156,7 @@ impl PlatformOutput {
}
self.events.append(&mut events);
self.mutable_text_under_cursor = mutable_text_under_cursor;
self.text_cursor_pos = text_cursor_pos.or(self.text_cursor_pos);
self.ime = ime.or(self.ime);

#[cfg(feature = "accesskit")]
{
Expand Down
21 changes: 6 additions & 15 deletions crates/egui/src/widgets/text_edit/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ impl<'t> TextEdit<'t> {
paint_cursor_selection(ui, &painter, text_draw_pos, &galley, &cursor_range);

if text.is_mutable() {
let cursor_pos = paint_cursor_end(
let cursor_rect = paint_cursor_end(
ui,
row_height,
&painter,
Expand All @@ -677,23 +677,14 @@ impl<'t> TextEdit<'t> {

let is_fully_visible = ui.clip_rect().contains_rect(rect); // TODO: remove this HACK workaround for https://github.com/emilk/egui/issues/1531
if (response.changed || selection_changed) && !is_fully_visible {
ui.scroll_to_rect(cursor_pos, None); // keep cursor in view
ui.scroll_to_rect(cursor_rect, None); // keep cursor in view
}

if interactive {
// eframe web uses `text_cursor_pos` when showing IME,
// so only set it when text is editable and visible!
// But `winit` and `egui_web` differs in how to set the
// position of IME.
if cfg!(target_arch = "wasm32") {
ui.ctx().output_mut(|o| {
o.text_cursor_pos = Some(cursor_pos.left_top());
});
} else {
ui.ctx().output_mut(|o| {
o.text_cursor_pos = Some(cursor_pos.left_bottom());
});
}
// For IME, so only set it when text is editable and visible!
ui.ctx().output_mut(|o| {
o.ime = Some(crate::output::IMEOutput { rect, cursor_rect });
});
}
}
}
Expand Down

0 comments on commit 149dfdd

Please sign in to comment.