Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
26 changes: 26 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ pub enum WindowEvent {
/// - **iOS / Android / Web / Orbital:** Unsupported.
Ime(Ime),

TextInputState(TextInputState),

/// The cursor has moved on the window.
///
/// ## Platform-specific
Expand Down Expand Up @@ -1095,3 +1097,27 @@ impl PartialEq for InnerSizeWriter {
self.new_inner_size.as_ptr() == other.new_inner_size.as_ptr()
}
}

/// 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(Serialize, 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(Serialize, 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>,
}
50 changes: 50 additions & 0 deletions src/platform_impl/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,32 @@ impl<T: 'static> EventLoop<T> {
}
}
}
InputEvent::TextEvent(ime_state) => {
let event = event::Event::WindowEvent {
window_id: window::WindowId(WindowId),
event: event::WindowEvent::TextInputState(
TextInputState {
text: ime_state.text.to_owned(),
selection: TextSpan {
start: ime_state.selection.start,
end: ime_state.selection.end,
},
compose_region: ime_state
.compose_region
.map(|region| TextSpan {
start: region.start,
end: region.end,
}),
}
)
};
sticky_exit_callback(
event,
self.window_target(),
control_flow,
callback
);
}
_ => {
warn!("Unknown android_activity input event {event:?}")
}
Expand Down Expand Up @@ -905,6 +931,28 @@ impl Window {

pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}

pub fn begin_ime_input(&self) {
self.app.show_soft_input(true);
}

pub fn end_ime_input(&self) {
self.app.hide_soft_input(true);
}

pub fn set_text_input_state(&self, state: TextInputState) {
self.app.set_text_input_state(android_activity::input::TextInputState {
text: state.text,
selection: android_activity::input::TextSpan {
start: state.selection.start,
end: state.selection.end,
},
compose_region: state.compose_region.map(|region| android_activity::input::TextSpan {
start: region.start,
end: region.end,
}),
});
}

pub fn focus_window(&self) {}

pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
Expand Down Expand Up @@ -987,6 +1035,8 @@ impl Window {
pub struct OsError;

use std::fmt::{self, Display, Formatter};
use crate::event::{TextInputState, TextSpan};

impl Display for OsError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(fmt, "Android OS Error")
Expand Down
7 changes: 7 additions & 0 deletions src/platform_impl/ios/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::{
WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel,
},
};
use crate::event::TextInputState;

pub struct Inner {
pub(crate) window: Id<WinitUIWindow>,
Expand Down Expand Up @@ -306,6 +307,12 @@ impl Inner {
warn!("`Window::set_ime_allowed` is ignored on iOS")
}

pub fn begin_ime_input(&self) {}

pub fn end_ime_input(&self) {}

pub fn set_text_input_state(&self, state: TextInputState) {}

pub fn focus_window(&self) {
warn!("`Window::set_focus` is ignored on iOS")
}
Expand Down
10 changes: 10 additions & 0 deletions src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ use crate::{
UserAttentionType, WindowAttributes, WindowButtons, WindowLevel,
},
};
use crate::event::TextInputState;

pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
pub(crate) use crate::platform_impl::Fullscreen;
Expand Down Expand Up @@ -545,6 +546,15 @@ impl Window {
x11_or_wayland!(match self; Window(w) => w.set_ime_purpose(purpose))
}

#[inline]
pub fn set_text_input_state(&self, state: TextInputState) {}

#[inline]
pub fn begin_ime_input(&self) {}

#[inline]
pub fn end_ime_input(&self) {}

#[inline]
pub fn focus_window(&self) {
match self {
Expand Down
1 change: 1 addition & 0 deletions src/platform_impl/linux/wayland/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use crate::window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons,
};
use crate::event::TextInputState;

use super::event_loop::sink::EventSink;
use super::output::MonitorHandle;
Expand Down
10 changes: 10 additions & 0 deletions src/platform_impl/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use icrate::Foundation::{
use objc2::declare::{Ivar, IvarDrop};
use objc2::rc::{autoreleasepool, Id};
use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType};
use crate::event::TextInputState;

use super::appkit::{
NSApp, NSAppKitVersion, NSAppearance, NSApplicationPresentationOptions, NSBackingStoreType,
Expand Down Expand Up @@ -1199,6 +1200,15 @@ impl WinitWindow {
#[inline]
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}

#[inline]
pub fn begin_ime_input(&self) {}

#[inline]
pub fn end_ime_input(&self) {}

#[inline]
pub fn set_text_input_state(&self, state: TextInputState) {}

#[inline]
pub fn focus_window(&self) {
let is_minimized = self.isMiniaturized();
Expand Down
10 changes: 10 additions & 0 deletions src/platform_impl/orbital/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
window,
window::ImePurpose,
};
use crate::event::TextInputState;

use super::{
EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket,
Expand Down Expand Up @@ -333,6 +334,15 @@ impl Window {
#[inline]
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}

#[inline]
pub fn begin_ime_input(&self) {}

#[inline]
pub fn end_ime_input(&self) {}

#[inline]
pub fn set_text_input_state(&self, state: TextInputState) {}

#[inline]
pub fn focus_window(&self) {}

Expand Down
14 changes: 14 additions & 0 deletions src/platform_impl/web/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
WindowAttributes, WindowButtons, WindowId as RootWI, WindowLevel,
};
use crate::event::TextInputState;

use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle};
use web_sys::{Document, HtmlCanvasElement};
Expand Down Expand Up @@ -366,6 +367,19 @@ impl Window {
// Currently not implemented
}

#[inline]
pub fn begin_ime_input(&self) {
// Currently not implemented
}

#[inline]
pub fn end_ime_input(&self) {
// Currently not implemented
}

#[inline]
pub fn set_text_input_state(&self, state: TextInputState) {}

#[inline]
pub fn focus_window(&self) {
self.inner.dispatch(|inner| {
Expand Down
10 changes: 10 additions & 0 deletions src/platform_impl/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ use crate::{
WindowAttributes, WindowButtons, WindowLevel,
},
};
use crate::event::TextInputState;

/// The Win32 implementation of the main `Window` object.
pub(crate) struct Window {
Expand Down Expand Up @@ -761,6 +762,15 @@ impl Window {
#[inline]
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}

#[inline]
pub fn begin_ime_input(&self) {}

#[inline]
pub fn end_ime_input(&self) {}

#[inline]
pub fn set_text_input_state(&self, state: TextInputState) {}

#[inline]
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
let window = self.window.clone();
Expand Down
18 changes: 18 additions & 0 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
monitor::{MonitorHandle, VideoMode},
platform_impl,
};
use crate::event::TextInputState;

pub use crate::icon::{BadIcon, Icon};

Expand Down Expand Up @@ -1111,6 +1112,23 @@ impl Window {
self.window.set_ime_purpose(purpose);
}

/// Opens the IME input (soft keyboard) if the platform supports it.
/// Currently only supported on Android.
#[inline]
pub fn begin_ime_input(&self) {
self.window.begin_ime_input();
}

/// Hides the IME input (soft keyboard).
#[inline]
pub fn end_ime_input(&self) {
self.window.end_ime_input();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks redundant. We have set_ime_allowed where true means that the soft board will be opened, because we allow ime. false will hide the IME, this works the same on Wayland btw and its softboards, so this API is redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, egui just calls set_ime_allowed once on application start, I think that confused me. I've changed this in my egui branch and removed the begin_ime_input and end_ime_input functions


pub fn set_text_input_state(&self, state: TextInputState) {
self.window.set_text_input_state(state);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you get a bit more details on what this particular function tries to do? In general, that's true that we'd nee a way to set an IME state, like position of the ime, surrounding text, etc.

I think what we probably should do is to have a set_ime_state(state: Option<ImeState>) where you can set that information + position of the input method, content hint, and similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've replaced this with a set_ime_surrounding_text function similar to the one from wayland you sent.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does android have a notion of atomic state changes? Because wayland has for example, and using some big InputState would be preferred for it, but I can refactor it myself later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean, but I'd guess not. The android game text input library this relies on is quite simple. The idea is that this is called once when a text field is first focused and when the selection gets updated by the user.


/// Brings the window to the front and sets input focus. Has no effect if the window is
/// already in focus, minimized, or not visible.
///
Expand Down