ferroui_native/
ferro_native_text_input_method.rs1use crate::frn_string::to_c_string;
2use crate::helpers::{to_frn_rect, ComResultExt};
3use crate::interop::*;
4use ferroui_base::input::text_input::{ITextInputMethodImpl, TextInputMethodClient, TextInputOptions, TextSelection};
5use ferroui_base::reactive::IDisposable;
6use ferroui_base::Rect;
7use ferroui_microcom::ComPtr;
8use std::cell::RefCell;
9use std::ffi::CStr;
10use std::rc::{Rc, Weak};
11
12pub struct FerroNativeTextInputMethod {
14 weak_self: Weak<FerroNativeTextInputMethod>,
15 client: RefCell<Option<Rc<dyn TextInputMethodClient>>>,
16 subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
18 native_client: RefCell<Option<ComPtr<IFrnTextInputMethodClient>>>,
19 input_method: RefCell<Option<ComPtr<IFrnTextInputMethod>>>,
20}
21
22impl FerroNativeTextInputMethod {
23 pub(crate) fn new(top_level: &IFrnTopLevel) -> Rc<FerroNativeTextInputMethod> {
24 let input_method = top_level.get_input_method().check();
25 Rc::new_cyclic(|weak_self| FerroNativeTextInputMethod {
26 weak_self: weak_self.clone(),
27 client: RefCell::new(None),
28 subscriptions: RefCell::new(Vec::new()),
29 native_client: RefCell::new(None),
30 input_method: RefCell::new(input_method),
31 })
32 }
33
34 #[track_caller]
35 fn input_method(&self) -> ComPtr<IFrnTextInputMethod> {
36 match self.input_method.borrow().clone() {
37 Some(input_method) => input_method,
38 None => panic!("Cannot access a disposed object: the native text input method"),
39 }
40 }
41
42 fn client(&self) -> Option<Rc<dyn TextInputMethodClient>> {
43 self.client.borrow().clone()
44 }
45
46 pub fn dispose(&self) {
48 let input_method = self.input_method.borrow_mut().take();
49 drop(input_method);
50 let native_client = self.native_client.borrow_mut().take();
51 drop(native_client);
52 }
53
54 fn unsubscribe(&self) {
55 let subscriptions = std::mem::take(&mut *self.subscriptions.borrow_mut());
56 for subscription in subscriptions {
57 subscription.dispose();
58 }
59 }
60
61 fn on_cursor_rectangle_changed(&self) {
62 let Some(client) = self.client() else {
63 return;
64 };
65
66 let Some(text_view_visual) = client.text_view_visual() else {
67 return;
68 };
69
70 let Some(visual_root) = text_view_visual.visual_root() else {
71 return;
72 };
73
74 let Some(transform) = text_view_visual.transform_to_visual(&visual_root) else {
75 return;
76 };
77
78 let rect = client.cursor_rectangle().transform_to_aabb(transform);
79
80 self.input_method().set_cursor_rect(to_frn_rect(rect));
81 }
82
83 fn on_surrounding_text_changed(&self) {
84 let Some(client) = self.client() else {
85 return;
86 };
87
88 let surrounding_text = client.surrounding_text();
89 let selection = client.selection();
90
91 self.input_method().set_surrounding_text(Some(&to_c_string(&surrounding_text)), selection.start, selection.end);
92 }
93
94 fn on_selection_changed(&self) {
95 let Some(client) = self.client() else {
96 return;
97 };
98
99 let selection = client.selection();
100 self.input_method().set_selection_in_surrounding_text(selection.start, selection.end);
101 }
102
103 fn handler(&self, action: fn(&FerroNativeTextInputMethod)) -> Rc<dyn Fn()> {
104 let this = self.weak_self.clone();
105 Rc::new(move || {
106 if let Some(this) = this.upgrade() {
107 action(&this);
108 }
109 })
110 }
111}
112
113impl ITextInputMethodImpl for FerroNativeTextInputMethod {
114 fn reset(&self) {
115 self.input_method().reset();
116 }
117
118 fn set_client(&self, client: Option<Rc<dyn TextInputMethodClient>>) {
119 let old_client = self.client();
120 if old_client.is_some_and(|client| client.supports_surrounding_text()) {
121 self.unsubscribe();
122 }
123
124 let old_native_client = self.native_client.borrow_mut().take();
125 drop(old_native_client);
126 let old_client = self.client.replace(client.clone());
127 drop(old_client);
128
129 if let Some(client) = client {
130 *self.native_client.borrow_mut() =
131 Some(IFrnTextInputMethodClient::from_impl(NativeTextInputMethodClient { client: client.clone() }));
132
133 self.on_surrounding_text_changed();
134 self.on_cursor_rectangle_changed();
135 self.unsubscribe();
141 let subscriptions = vec![
142 client.surrounding_text_changed(self.handler(Self::on_surrounding_text_changed)),
143 client.cursor_rectangle_changed(self.handler(Self::on_cursor_rectangle_changed)),
144 client.selection_changed(self.handler(Self::on_selection_changed)),
145 ];
146 *self.subscriptions.borrow_mut() = subscriptions;
147 }
148
149 let native_client = self.native_client.borrow().clone();
150 self.input_method().set_client(native_client.as_deref()).check();
151 }
152
153 fn set_cursor_rect(&self, rect: Rect) {
154 self.input_method().set_cursor_rect(to_frn_rect(rect));
155 }
156
157 fn set_options(&self, _options: &TextInputOptions) {}
158}
159
160struct NativeTextInputMethodClient {
162 client: Rc<dyn TextInputMethodClient>,
163}
164
165impl IFrnTextInputMethodClientImpl for NativeTextInputMethodClient {
166 fn set_preedit_text(&self, preedit_text: Option<&CStr>) {
167 crate::callback_base::guard((), || {
168 if self.client.supports_preedit() {
169 let preedit_text = preedit_text.map(|text| text.to_string_lossy());
170 self.client.set_preedit_text(preedit_text.as_deref());
171 }
172 })
173 }
174
175 fn select_in_surrounding_text(&self, start: i32, end: i32) {
176 crate::callback_base::guard((), || {
177 if self.client.supports_surrounding_text() {
178 self.client.set_selection(TextSelection::new(start, end));
179 }
180 })
181 }
182}