Skip to main content

ferroui_controls/
text_box_text_input_method_client.rs

1use crate::presenters::TextPresenter;
2use crate::TextBox;
3use ferroui_base::input::text_input::{
4    ContextMenuAction, TextInputMethodClient, TextInputMethodClientEvents, TextSelection,
5};
6use ferroui_base::input::{InputElement, TappedEventArgs};
7use ferroui_base::interactivity::RoutedEventHandlerToken;
8use ferroui_base::reactive::IDisposable;
9use ferroui_base::{FerroPropertyChangedEventArgs, Rect, Ref, Visual, WeakRef};
10use std::cell::{Cell, RefCell};
11use std::rc::{Rc, Weak};
12
13/// The text input method client of a [`TextBox`]: what the text box gives
14/// the input method of the platform to work with.
15///
16/// The text box owns its client, so the client only holds weak handles to
17/// the text box and to its presenter.
18pub struct TextBoxTextInputMethodClient {
19    this: Weak<TextBoxTextInputMethodClient>,
20    events: TextInputMethodClientEvents,
21    parent: RefCell<Option<WeakRef<TextBox>>>,
22    presenter: RefCell<Option<WeakRef<TextPresenter>>>,
23    selection_changed: Cell<bool>,
24    is_in_change: Cell<bool>,
25    parent_property_changed: RefCell<Option<Rc<dyn IDisposable>>>,
26    parent_tapped: RefCell<Option<RoutedEventHandlerToken>>,
27    caret_bounds_changed: RefCell<Option<Rc<dyn IDisposable>>>,
28}
29
30/// The scope of a change of a text box (see
31/// [`TextBoxTextInputMethodClient::begin_change`]): selection changes made
32/// while it lives are reported once, when it is dropped.
33pub(crate) struct TextInputMethodChange(Option<Rc<TextBoxTextInputMethodClient>>);
34
35impl Drop for TextInputMethodChange {
36    fn drop(&mut self) {
37        if let Some(client) = self.0.take() {
38            client.raise_events();
39        }
40    }
41}
42
43impl TextInputMethodClient for TextBoxTextInputMethodClient {
44    fn events(&self) -> &TextInputMethodClientEvents {
45        &self.events
46    }
47
48    fn text_view_visual(&self) -> Option<Ref<Visual>> {
49        self.presenter().map(Ref::upcast)
50    }
51
52    fn surrounding_text(&self) -> String {
53        let Some(parent) = self.parent() else {
54            return String::new();
55        };
56
57        let presenter = self.presenter();
58
59        if let Some(presenter) = &presenter {
60            let caret_index = parent.caret_index();
61
62            if caret_index != presenter.caret_index() {
63                presenter.set_current_value(TextPresenter::caret_index_property(), caret_index);
64            }
65        }
66
67        let text = parent.text();
68
69        if let Some(presenter) = &presenter {
70            if text != presenter.text() {
71                presenter.set_current_value(TextPresenter::text_property(), text.clone());
72            }
73        }
74
75        text.unwrap_or_default()
76    }
77
78    fn cursor_rectangle(&self) -> Rect {
79        let (Some(parent), Some(presenter)) = (self.parent(), self.presenter()) else {
80            return Rect::default();
81        };
82
83        let Some(transform) = presenter.transform_to_visual(&parent) else {
84            return Rect::default();
85        };
86
87        presenter.cursor_rectangle().transform_to_aabb(transform)
88    }
89
90    fn selection(&self) -> TextSelection {
91        let Some(parent) = self.parent() else {
92            return TextSelection::default();
93        };
94
95        TextSelection::new(parent.selection_start(), parent.selection_end())
96    }
97
98    fn set_selection(&self, value: TextSelection) {
99        let Some(parent) = self.parent() else {
100            return;
101        };
102
103        let _change = self.begin_change();
104        parent.set_selection_start(value.start);
105        parent.set_selection_end(value.end);
106    }
107
108    fn supports_preedit(&self) -> bool {
109        true
110    }
111
112    fn supports_surrounding_text(&self) -> bool {
113        true
114    }
115
116    fn set_preedit_text(&self, preedit_text: Option<&str>) {
117        self.set_preedit_text_with_cursor(preedit_text, None)
118    }
119
120    fn set_preedit_text_with_cursor(&self, preedit_text: Option<&str>, cursor_pos: Option<i32>) {
121        let (Some(_parent), Some(presenter)) = (self.parent(), self.presenter()) else {
122            return;
123        };
124
125        presenter.set_current_value(TextPresenter::preedit_text_property(), preedit_text.map(str::to_owned));
126        presenter.set_current_value(TextPresenter::preedit_text_cursor_position_property(), cursor_pos);
127    }
128
129    fn execute_context_menu_action(&self, action: ContextMenuAction) {
130        let Some(parent) = self.parent() else {
131            return;
132        };
133
134        match action {
135            ContextMenuAction::Copy => parent.copy(),
136            ContextMenuAction::Cut => parent.cut(),
137            ContextMenuAction::Paste => parent.paste(),
138            ContextMenuAction::SelectAll => parent.select_all(),
139        }
140    }
141}
142
143impl TextBoxTextInputMethodClient {
144    pub(crate) fn new() -> Rc<Self> {
145        Rc::new_cyclic(|this| Self {
146            this: this.clone(),
147            events: TextInputMethodClientEvents::new(),
148            parent: RefCell::new(None),
149            presenter: RefCell::new(None),
150            selection_changed: Cell::new(false),
151            is_in_change: Cell::new(false),
152            parent_property_changed: RefCell::new(None),
153            parent_tapped: RefCell::new(None),
154            caret_bounds_changed: RefCell::new(None),
155        })
156    }
157
158    fn parent(&self) -> Option<Ref<TextBox>> {
159        let parent = self.parent.borrow().clone();
160        parent.and_then(|parent| parent.upgrade())
161    }
162
163    fn presenter(&self) -> Option<Ref<TextPresenter>> {
164        let presenter = self.presenter.borrow().clone();
165        presenter.and_then(|presenter| presenter.upgrade())
166    }
167
168    pub(crate) fn set_presenter(&self, presenter: Option<&Ref<TextPresenter>>, parent: Option<&Ref<TextBox>>) {
169        let property_changed = self.parent_property_changed.borrow_mut().take();
170        let tapped = self.parent_tapped.borrow_mut().take();
171
172        if let Some(old_parent) = self.parent() {
173            if let Some(property_changed) = property_changed {
174                property_changed.dispose();
175            }
176
177            if let Some(tapped) = tapped {
178                old_parent.remove_handler(InputElement::tapped_event(), tapped);
179            }
180        }
181
182        *self.parent.borrow_mut() = parent.map(Ref::downgrade);
183
184        if let Some(parent) = parent {
185            let weak = self.this.clone();
186            let property_changed = parent.property_changed(move |e| {
187                if let Some(this) = weak.upgrade() {
188                    this.on_parent_property_changed(e);
189                }
190            });
191            *self.parent_property_changed.borrow_mut() = Some(property_changed);
192
193            let weak = self.this.clone();
194            let tapped = parent.add_handler(InputElement::tapped_event(), move |_, _: &TappedEventArgs| {
195                if let Some(this) = weak.upgrade() {
196                    this.on_parent_tapped();
197                }
198            });
199            *self.parent_tapped.borrow_mut() = Some(tapped);
200        }
201
202        let old_presenter = self.presenter();
203        let caret_bounds_changed = self.caret_bounds_changed.borrow_mut().take();
204
205        if let Some(old_presenter) = old_presenter {
206            old_presenter.set_current_im_client(None);
207            old_presenter.clear_value(TextPresenter::preedit_text_property());
208
209            if let Some(caret_bounds_changed) = caret_bounds_changed {
210                caret_bounds_changed.dispose();
211            }
212        }
213
214        *self.presenter.borrow_mut() = presenter.map(Ref::downgrade);
215
216        if let Some(presenter) = presenter {
217            presenter.set_current_im_client(self.this.upgrade());
218
219            let weak = self.this.clone();
220            let caret_bounds_changed = presenter.caret_bounds_changed(move || {
221                if let Some(this) = weak.upgrade() {
222                    this.on_presenter_caret_bounds_changed();
223                }
224            });
225            *self.caret_bounds_changed.borrow_mut() = Some(caret_bounds_changed);
226        }
227
228        self.raise_text_view_visual_changed();
229
230        self.raise_cursor_rectangle_changed();
231    }
232
233    fn on_presenter_caret_bounds_changed(&self) {
234        self.raise_cursor_rectangle_changed();
235    }
236
237    fn on_parent_tapped(&self) {
238        self.raise_input_pane_activation_requested();
239    }
240
241    fn on_parent_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
242        let property = e.property();
243
244        if property == TextBox::text_property().as_property() {
245            self.raise_surrounding_text_changed();
246        }
247
248        if property == TextBox::selection_start_property().as_property()
249            || property == TextBox::selection_end_property().as_property()
250        {
251            if self.is_in_change.get() {
252                self.selection_changed.set(true);
253            } else {
254                self.raise_selection_changed();
255            }
256        }
257    }
258
259    /// Starts a change of the text box: the selection changes made until
260    /// the returned scope is dropped are reported as one.
261    pub(crate) fn begin_change(&self) -> TextInputMethodChange {
262        if self.is_in_change.get() {
263            return TextInputMethodChange(None);
264        }
265
266        self.is_in_change.set(true);
267        TextInputMethodChange(self.this.upgrade())
268    }
269
270    fn raise_events(&self) {
271        self.is_in_change.set(false);
272
273        if self.selection_changed.get() {
274            self.raise_selection_changed();
275        }
276
277        self.selection_changed.set(false);
278    }
279}