Skip to content

Commit

Permalink
Ime support
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasmerlin committed Jul 5, 2024
1 parent 4f6e6c4 commit f612e57
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 26 deletions.
8 changes: 3 additions & 5 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4676,9 +4676,7 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

[[package]]
name = "winit"
version = "0.29.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c824f11941eeae66ec71111cc2674373c772f482b58939bb4066b642aa2ffcf"
version = "0.29.15"
dependencies = [
"ahash",
"android-activity",
Expand Down Expand Up @@ -4785,9 +4783,9 @@ dependencies = [

[[package]]
name = "xkbcommon-dl"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6924668544c48c0133152e7eec86d644a056ca3d09275eb8d5cdb9855f9d8699"
checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5"
dependencies = [
"bitflags 2.5.0",
"dlib",
Expand Down
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ members = [
"crates/egui",
"crates/emath",
"crates/epaint",

"examples/*",
"tests/*",

"xtask",
]

Expand Down Expand Up @@ -264,3 +262,6 @@ manual_range_contains = "allow" # this one is just worse imho
self_named_module_files = "allow" # Disabled waiting on https://github.com/rust-lang/rust-clippy/issues/9602
significant_drop_tightening = "allow" # Too many false positives
wildcard_imports = "allow" # we do this a lot in egui

[patch.crates-io]
winit = { path = "../github/winit" }
3 changes: 2 additions & 1 deletion crates/eframe/src/web/app_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ impl AppRunner {
mutable_text_under_cursor: _, // TODO(#4569): https://github.com/emilk/egui/issues/4569
ime,
#[cfg(feature = "accesskit")]
accesskit_update: _, // not currently implemented
accesskit_update: _, // not currently implemented
text_input_state: _, // not currently implemented
} = platform_output;

super::set_cursor_icon(cursor_icon);
Expand Down
54 changes: 48 additions & 6 deletions crates/egui-winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod window_settings;
pub use window_settings::WindowSettings;

use ahash::HashSet;
use egui::{TextInputState, TextSpan};
use raw_window_handle::HasDisplayHandle;

#[allow(unused_imports)]
Expand Down Expand Up @@ -96,6 +97,7 @@ pub struct State {

/// track ime state
has_sent_ime_enabled: bool,
text_input_last_frame: bool,

#[cfg(feature = "accesskit")]
accesskit: Option<accesskit_winit::Adapter>,
Expand Down Expand Up @@ -137,7 +139,7 @@ impl State {
pointer_touch_id: None,

has_sent_ime_enabled: false,

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

Expand Down Expand Up @@ -368,6 +370,25 @@ impl State {
consumed: self.egui_ctx.wants_keyboard_input(),
}
}
WindowEvent::TextInputState(state) => {
self.egui_input
.events
.push(egui::Event::TextInputState(TextInputState {
text: state.text.clone(),
selection: TextSpan {
start: state.selection.start,
end: state.selection.end,
},
compose_region: state.compose_region.as_ref().map(|r| TextSpan {
start: r.start,
end: r.end,
}),
}));
EventResponse {
repaint: true,
consumed: self.egui_ctx.wants_keyboard_input(),
}
}
WindowEvent::KeyboardInput {
event,
is_synthetic,
Expand Down Expand Up @@ -816,6 +837,7 @@ impl State {
ime,
#[cfg(feature = "accesskit")]
accesskit_update,
text_input_state,
} = platform_output;

self.set_cursor_icon(window, cursor_icon);
Expand All @@ -828,12 +850,31 @@ impl State {
self.clipboard.set(copied_text);
}

let allow_ime = ime.is_some();
if self.allow_ime != allow_ime {
self.allow_ime = allow_ime;
crate::profile_scope!("set_ime_allowed");
window.set_ime_allowed(allow_ime);
if let Some(text_input_state) = text_input_state {
window.set_text_input_state(winit::event::TextInputState {
text: text_input_state.text,
selection: winit::event::TextSpan {
start: text_input_state.selection.start,
end: text_input_state.selection.end,
},
compose_region: text_input_state
.compose_region
.map(|r| winit::event::TextSpan {
start: r.start,
end: r.end,
}),
});
}

let text_input_this_frame = ime.is_some();
if self.text_input_last_frame != text_input_this_frame {
if text_input_this_frame {
window.begin_ime_input();
} else {
window.end_ime_input();
}
}
self.text_input_last_frame = text_input_this_frame;

if let Some(ime) = ime {
let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
Expand Down Expand Up @@ -1823,6 +1864,7 @@ pub fn short_window_event_description(event: &winit::event::WindowEvent) -> &'st
WindowEvent::KeyboardInput { .. } => "WindowEvent::KeyboardInput",
WindowEvent::ModifiersChanged { .. } => "WindowEvent::ModifiersChanged",
WindowEvent::Ime { .. } => "WindowEvent::Ime",
WindowEvent::TextInputState(..) => "WindowEvent::TextInputState",
WindowEvent::CursorMoved { .. } => "WindowEvent::CursorMoved",
WindowEvent::CursorEntered { .. } => "WindowEvent::CursorEntered",
WindowEvent::CursorLeft { .. } => "WindowEvent::CursorLeft",
Expand Down
26 changes: 26 additions & 0 deletions crates/egui/src/data/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ pub enum Event {
/// IME Event
Ime(ImeEvent),

TextInputState(TextInputState),

/// On touch screens, report this *in addition to*
/// [`Self::PointerMoved`], [`Self::PointerButton`], [`Self::PointerGone`]
Touch {
Expand Down Expand Up @@ -1141,6 +1143,30 @@ impl From<u32> for TouchId {
}
}

/// This struct holds a span within a region of text from `start` (inclusive) to
/// `end` (exclusive).
///
/// An empty span or cursor position is specified with `start == end`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TextSpan {
/// The start of the span (inclusive)
pub start: usize,

/// The end of the span (exclusive)
pub end: usize,
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TextInputState {
pub text: String,
/// A selection defined on the text.
pub selection: TextSpan,
/// A composing region defined on the text.
pub compose_region: Option<TextSpan>,
}

// ----------------------------------------------------------------------------

// TODO(emilk): generalize this to a proper event filter.
Expand Down
9 changes: 8 additions & 1 deletion crates/egui/src/data/output.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! All the data egui returns to the backend at the end of each frame.
use crate::{ViewportIdMap, ViewportOutput, WidgetType};
use crate::{TextInputState, ViewportIdMap, ViewportOutput, WidgetType};

/// What egui emits each frame from [`crate::Context::run`].
///
Expand Down Expand Up @@ -118,6 +118,8 @@ pub struct PlatformOutput {
/// Useful for IME.
pub ime: Option<IMEOutput>,

pub text_input_state: Option<TextInputState>,

/// The difference in the widget tree since last frame.
///
/// NOTE: this needs to be per-viewport.
Expand Down Expand Up @@ -155,6 +157,7 @@ impl PlatformOutput {
ime,
#[cfg(feature = "accesskit")]
accesskit_update,
text_input_state,
} = newer;

self.cursor_icon = cursor_icon;
Expand All @@ -168,6 +171,10 @@ impl PlatformOutput {
self.mutable_text_under_cursor = mutable_text_under_cursor;
self.ime = ime.or(self.ime);

if text_input_state.is_some() {
self.text_input_state = text_input_state;
}

#[cfg(feature = "accesskit")]
{
// egui produces a complete AccessKit tree for each frame,
Expand Down
11 changes: 11 additions & 0 deletions crates/egui/src/input_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub struct InputState {
// ----------------------------------------------
/// Position and size of the egui area.
pub screen_rect: Rect,
previous_screen_rect: Rect,

/// Also known as device pixel ratio, > 1 for high resolution screens.
pub pixels_per_point: f32,
Expand Down Expand Up @@ -177,6 +178,7 @@ impl Default for InputState {
zoom_factor_delta: 1.0,

screen_rect: Rect::from_min_size(Default::default(), vec2(10_000.0, 10_000.0)),
previous_screen_rect: Rect::from_min_size(Default::default(), vec2(10_000.0, 10_000.0)),
pixels_per_point: 1.0,
max_texture_side: 2048,
time: 0.0,
Expand Down Expand Up @@ -213,6 +215,7 @@ impl InputState {
new.predicted_dt
};

let previous_screen_rect = self.screen_rect;
let screen_rect = new.screen_rect.unwrap_or(self.screen_rect);
self.create_touch_states_for_new_devices(&new.events);
for touch_state in self.touch_states.values_mut() {
Expand Down Expand Up @@ -349,6 +352,7 @@ impl InputState {
zoom_factor_delta,

screen_rect,
previous_screen_rect,
pixels_per_point,
max_texture_side: new.max_texture_side.unwrap_or(self.max_texture_side),
time,
Expand All @@ -374,6 +378,11 @@ impl InputState {
self.screen_rect
}

#[inline(always)]
pub fn screen_rect_changed(&self) -> bool {
self.screen_rect != self.previous_screen_rect
}

/// Zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture).
/// * `zoom = 1`: no change
/// * `zoom < 1`: pinch together
Expand Down Expand Up @@ -1258,6 +1267,7 @@ impl InputState {

zoom_factor_delta,
screen_rect,
previous_screen_rect,
pixels_per_point,
max_texture_side,
time,
Expand Down Expand Up @@ -1309,6 +1319,7 @@ impl InputState {
ui.label(format!("zoom_factor_delta: {zoom_factor_delta:4.2}x"));

ui.label(format!("screen_rect: {screen_rect:?} points"));
ui.label(format!("previous_screen_rect: {:?} points", previous_screen_rect));
ui.label(format!(
"{pixels_per_point} physical pixels for each logical point"
));
Expand Down
Loading

0 comments on commit f612e57

Please sign in to comment.