ferroui_base/input/text_input/text_input_method_client.rs
1use super::{
2 TextInputMethodConnection, TextInputMethodEditing, TextInputMethodError,
3 TextInputMethodRangeGeometry,
4};
5use crate::input::InputElement;
6use crate::reactive::{Disposable, IDisposable};
7use crate::utilities::HandlerList;
8use crate::{Rect, Ref, Visual};
9use std::cell::{Cell, RefCell};
10use std::rc::{Rc, Weak};
11
12/// The start and the end of a selection, in UTF-16 code units.
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
14pub struct TextSelection {
15 pub start: i32,
16 pub end: i32,
17}
18
19impl TextSelection {
20 pub const fn new(start: i32, end: i32) -> Self {
21 Self { start, end }
22 }
23}
24
25/// An action of the context menu of a text input.
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
27#[repr(i32)]
28pub enum ContextMenuAction {
29 Copy = 0,
30 Cut = 1,
31 Paste = 2,
32 SelectAll = 3,
33}
34
35type Event = Rc<HandlerList<dyn Fn()>>;
36
37fn subscribe(event: &Event, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
38 let token = event.add(handler);
39 let event = event.clone();
40 Disposable::create(move || {
41 event.remove(token);
42 })
43}
44
45fn raise(event: &Event) {
46 if event.is_empty() {
47 return;
48 }
49 for (_, handler) in event.snapshot().iter() {
50 handler();
51 }
52}
53
54/// The events of a [`TextInputMethodClient`]: the state every client
55/// embeds and returns from [`TextInputMethodClient::events`].
56#[derive(Default)]
57pub struct TextInputMethodClientEvents {
58 pub(super) connection: RefCell<Option<Weak<TextInputMethodConnection>>>,
59 state_version: Cell<u64>,
60 text_view_visual_changed: Event,
61 cursor_rectangle_changed: Event,
62 surrounding_text_changed: Event,
63 selection_changed: Event,
64 reset_requested: Event,
65 input_pane_activation_requested: Event,
66}
67
68impl TextInputMethodClientEvents {
69 pub(crate) fn state_version(&self) -> u64 {
70 self.state_version.get()
71 }
72
73 fn state_changed(&self) {
74 self.state_version
75 .set(self.state_version.get().wrapping_add(1));
76 }
77
78 /// Creates the events of a client, without any subscription.
79 pub fn new() -> Self {
80 Self::default()
81 }
82}
83
84/// The text editing side of a text input method: what a text editing
85/// control gives the input method of the platform to work with.
86///
87/// Implementations embed a [`TextInputMethodClientEvents`] and provide the
88/// members describing the text; the events and the methods raising them are
89/// provided. Clients are compared by reference.
90pub trait TextInputMethodClient {
91 /// The events of the client.
92 fn events(&self) -> &TextInputMethodClientEvents;
93
94 /// The semantic control that receives input and supplies options/geometry.
95 /// Existing clients default to the element queried by the input manager.
96 fn input_method_owner(&self) -> Option<Ref<InputElement>> {
97 None
98 }
99
100 /// Whether a focus destination belongs to this client's active editing UI.
101 /// The client must check actual object identity and current ownership.
102 fn retains_input_method_focus(&self, _focused: &InputElement) -> bool {
103 false
104 }
105
106 /// The current manager connection; adapters capture it during `set_client`.
107 /// The manager connects only a client that implements
108 /// [`input_method_editing`](Self::input_method_editing); for any other client
109 /// this is `None`, and the platform backends keep their upstream behaviour.
110 fn input_method_connection(&self) -> Option<Rc<TextInputMethodConnection>> {
111 self.events().connection()
112 }
113
114 /// Atomic composition, when implemented by this editing control. Returning
115 /// `Some` opts the client in to the exact-edit connection
116 /// ([`TextInputMethodConnection`]): its owner's options are published again when
117 /// they change, and a reset gives it a new connection.
118 fn input_method_editing(&self) -> Option<&dyn TextInputMethodEditing> {
119 None
120 }
121
122 /// Optional identity of the current editing state, including changes whose
123 /// public text/selection notifications are still deferred. A supporting
124 /// client advances this revision for changes to its document, selection or
125 /// composition state; reading it must describe the current state.
126 ///
127 /// This getter is observational: it may update a private cache used to
128 /// produce the revision, but must not initiate edits, input operations or
129 /// ownership transitions. The connection defensively rejects observable
130 /// reentrancy; it cannot identify an unreported edit made by the getter
131 /// itself when the getter returns that edit's new revision.
132 ///
133 /// Input sessions compare both this revision and the public event version.
134 /// Existing clients that return `None` continue to use their events alone.
135 fn input_method_state_revision(&self) -> Option<u64> {
136 None
137 }
138
139 /// The visual that's showing the text, or `None` when the client has
140 /// none (a text box without a template).
141 fn text_view_visual(&self) -> Option<Ref<Visual>>;
142
143 /// Indicates if the text input method client is capable of displaying
144 /// non-committed input on the cursor position.
145 fn supports_preedit(&self) -> bool;
146
147 /// Indicates if the text input method client supports sending
148 /// surrounding text.
149 fn supports_surrounding_text(&self) -> bool;
150
151 /// Returns the text around the cursor, usually the current paragraph.
152 fn surrounding_text(&self) -> String;
153
154 /// Gets the cursor rectangle relative to the semantic input owner. The
155 /// text-view visual is observed for layout changes; it may be a descendant.
156 fn cursor_rectangle(&self) -> Rect;
157
158 /// Queries an ordered range of the visible UTF-16 story, including any
159 /// composition preview. The result covers the requested range without
160 /// changing selection, scrolling, composition or history. Read-only range
161 /// endpoints can lie inside a shaped cluster or a surrogate pair: use the
162 /// formatter's cluster bounds and insertion-edge rules for those positions.
163 ///
164 /// `Unsupported` preserves the platform's legacy caret fallback. A client
165 /// that implements this capability returns `Unavailable` for hidden or
166 /// unformatted text, `InvalidRange` for invalid offsets, and `StateChanged`
167 /// for reentrant invalidation; these must never fall back to another caret.
168 fn text_range_geometry(
169 &self,
170 _range: TextSelection,
171 ) -> Result<TextInputMethodRangeGeometry, TextInputMethodError> {
172 Err(TextInputMethodError::Unsupported)
173 }
174
175 /// Gets the selection that is currently active.
176 fn selection(&self) -> TextSelection;
177
178 /// Sets the selection that is currently active.
179 fn set_selection(&self, value: TextSelection);
180
181 /// Sets the non-committed input string.
182 fn set_preedit_text(&self, _preedit_text: Option<&str>) {}
183
184 /// Executes a context menu action.
185 fn execute_context_menu_action(&self, _action: ContextMenuAction) {}
186
187 /// Sets the non-committed input string and the cursor offset in that
188 /// string.
189 fn set_preedit_text_with_cursor(&self, preedit_text: Option<&str>, _cursor_pos: Option<i32>) {
190 self.set_preedit_text(preedit_text)
191 }
192
193 /// Fires when the text view visual has changed. Disposing the returned
194 /// handle unsubscribes.
195 fn text_view_visual_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
196 subscribe(&self.events().text_view_visual_changed, handler)
197 }
198
199 /// Fires when the cursor rectangle has changed. Disposing the returned
200 /// handle unsubscribes.
201 fn cursor_rectangle_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
202 subscribe(&self.events().cursor_rectangle_changed, handler)
203 }
204
205 /// Fires when the surrounding text has changed. Disposing the returned
206 /// handle unsubscribes.
207 fn surrounding_text_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
208 subscribe(&self.events().surrounding_text_changed, handler)
209 }
210
211 /// Fires when the selection has changed. Disposing the returned handle
212 /// unsubscribes.
213 fn selection_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
214 subscribe(&self.events().selection_changed, handler)
215 }
216
217 /// Fires when client wants to reset the input state. Disposing the
218 /// returned handle unsubscribes.
219 fn reset_requested(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
220 subscribe(&self.events().reset_requested, handler)
221 }
222
223 /// Fires when the client requires the input pane (on-screen keyboard)
224 /// to be shown. Disposing the returned handle unsubscribes.
225 fn input_pane_activation_requested(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
226 subscribe(&self.events().input_pane_activation_requested, handler)
227 }
228
229 /// Raises the event telling that the text view visual has changed.
230 fn raise_text_view_visual_changed(&self) {
231 raise(&self.events().text_view_visual_changed)
232 }
233
234 /// Raises the event telling that the cursor rectangle has changed.
235 fn raise_cursor_rectangle_changed(&self) {
236 raise(&self.events().cursor_rectangle_changed)
237 }
238
239 /// Raises the event telling that the surrounding text has changed.
240 fn raise_surrounding_text_changed(&self) {
241 self.events().state_changed();
242 raise(&self.events().surrounding_text_changed)
243 }
244
245 /// Raises the event telling that the selection has changed.
246 fn raise_selection_changed(&self) {
247 self.events().state_changed();
248 raise(&self.events().selection_changed)
249 }
250
251 /// Raises the event asking for the input pane to be shown.
252 fn raise_input_pane_activation_requested(&self) {
253 raise(&self.events().input_pane_activation_requested)
254 }
255
256 /// Asks for the input state to be reset.
257 fn request_reset(&self) {
258 raise(&self.events().reset_requested)
259 }
260}