ferroui_controls/
text_box_text_input_method_client.rs1use 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
13pub 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
30pub(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 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}