Skip to main content

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}