1use crate::{
2 App, Bounds, ClipboardItem, Context, Entity, InputHandler, Pixels, TextInputConfiguration,
3 UTF16Selection, Window,
4};
5use std::ops::Range;
6
7pub trait EntityInputHandler: 'static + Sized {
14 fn text_for_range(
16 &mut self,
17 range: Range<usize>,
18 adjusted_range: &mut Option<Range<usize>>,
19 window: &mut Window,
20 cx: &mut Context<Self>,
21 ) -> Option<String>;
22
23 fn selected_text_range(
25 &mut self,
26 ignore_disabled_input: bool,
27 window: &mut Window,
28 cx: &mut Context<Self>,
29 ) -> Option<UTF16Selection>;
30
31 fn marked_text_range(
33 &self,
34 window: &mut Window,
35 cx: &mut Context<Self>,
36 ) -> Option<Range<usize>>;
37
38 fn unmark_text(&mut self, window: &mut Window, cx: &mut Context<Self>);
40
41 fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut Context<Self>) {
43 if let Some(text) = item.text() {
44 self.replace_text_in_range(None, &text, window, cx);
45 }
46 }
47
48 fn replace_text_in_range(
50 &mut self,
51 range: Option<Range<usize>>,
52 text: &str,
53 window: &mut Window,
54 cx: &mut Context<Self>,
55 );
56
57 fn replace_and_mark_text_in_range(
59 &mut self,
60 range: Option<Range<usize>>,
61 new_text: &str,
62 new_selected_range: Option<Range<usize>>,
63 window: &mut Window,
64 cx: &mut Context<Self>,
65 );
66
67 fn bounds_for_range(
69 &mut self,
70 range_utf16: Range<usize>,
71 element_bounds: Bounds<Pixels>,
72 window: &mut Window,
73 cx: &mut Context<Self>,
74 ) -> Option<Bounds<Pixels>>;
75
76 fn character_index_for_point(
78 &mut self,
79 point: crate::Point<Pixels>,
80 window: &mut Window,
81 cx: &mut Context<Self>,
82 ) -> Option<usize>;
83
84 fn set_selected_text_range(
86 &mut self,
87 _range_utf16: Range<usize>,
88 _window: &mut Window,
89 _cx: &mut Context<Self>,
90 ) {
91 }
92
93 fn text_length_utf16(
95 &mut self,
96 _window: &mut Window,
97 _cx: &mut Context<Self>,
98 ) -> Option<usize> {
99 None
100 }
101
102 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
104 true
105 }
106
107 fn text_input_configuration(
109 &mut self,
110 _window: &mut Window,
111 _cx: &mut Context<Self>,
112 ) -> TextInputConfiguration {
113 TextInputConfiguration::default()
114 }
115
116 fn text_input_editable_range(
118 &mut self,
119 _window: &mut Window,
120 _cx: &mut Context<Self>,
121 ) -> Option<Range<usize>> {
122 None
123 }
124}
125
126pub struct ElementInputHandler<V> {
129 view: Entity<V>,
130 element_bounds: Bounds<Pixels>,
131}
132
133impl<V: 'static> ElementInputHandler<V> {
134 pub fn new(element_bounds: Bounds<Pixels>, view: Entity<V>) -> Self {
138 ElementInputHandler {
139 view,
140 element_bounds,
141 }
142 }
143}
144
145impl<V: EntityInputHandler> InputHandler for ElementInputHandler<V> {
146 fn selected_text_range(
147 &mut self,
148 ignore_disabled_input: bool,
149 window: &mut Window,
150 cx: &mut App,
151 ) -> Option<UTF16Selection> {
152 self.view.update(cx, |view, cx| {
153 view.selected_text_range(ignore_disabled_input, window, cx)
154 })
155 }
156
157 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>> {
158 self.view
159 .update(cx, |view, cx| view.marked_text_range(window, cx))
160 }
161
162 fn text_for_range(
163 &mut self,
164 range_utf16: Range<usize>,
165 adjusted_range: &mut Option<Range<usize>>,
166 window: &mut Window,
167 cx: &mut App,
168 ) -> Option<String> {
169 self.view.update(cx, |view, cx| {
170 view.text_for_range(range_utf16, adjusted_range, window, cx)
171 })
172 }
173
174 fn replace_text_in_range(
175 &mut self,
176 replacement_range: Option<Range<usize>>,
177 text: &str,
178 window: &mut Window,
179 cx: &mut App,
180 ) {
181 self.view.update(cx, |view, cx| {
182 view.replace_text_in_range(replacement_range, text, window, cx)
183 });
184 }
185
186 fn replace_and_mark_text_in_range(
187 &mut self,
188 range_utf16: Option<Range<usize>>,
189 new_text: &str,
190 new_selected_range: Option<Range<usize>>,
191 window: &mut Window,
192 cx: &mut App,
193 ) {
194 self.view.update(cx, |view, cx| {
195 view.replace_and_mark_text_in_range(
196 range_utf16,
197 new_text,
198 new_selected_range,
199 window,
200 cx,
201 )
202 });
203 }
204
205 fn unmark_text(&mut self, window: &mut Window, cx: &mut App) {
206 self.view
207 .update(cx, |view, cx| view.unmark_text(window, cx));
208 }
209
210 fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
211 self.view
212 .update(cx, |view, cx| view.paste(item, window, cx));
213 }
214
215 fn bounds_for_range(
216 &mut self,
217 range_utf16: Range<usize>,
218 window: &mut Window,
219 cx: &mut App,
220 ) -> Option<Bounds<Pixels>> {
221 self.view.update(cx, |view, cx| {
222 view.bounds_for_range(range_utf16, self.element_bounds, window, cx)
223 })
224 }
225
226 fn character_index_for_point(
227 &mut self,
228 point: crate::Point<Pixels>,
229 window: &mut Window,
230 cx: &mut App,
231 ) -> Option<usize> {
232 self.view.update(cx, |view, cx| {
233 view.character_index_for_point(point, window, cx)
234 })
235 }
236
237 fn set_selected_text_range(
238 &mut self,
239 range_utf16: Range<usize>,
240 window: &mut Window,
241 cx: &mut App,
242 ) {
243 self.view.update(cx, |view, cx| {
244 view.set_selected_text_range(range_utf16, window, cx)
245 })
246 }
247
248 fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
249 Some(self.element_bounds)
250 }
251
252 fn text_length_utf16(&mut self, window: &mut Window, cx: &mut App) -> Option<usize> {
253 self.view
254 .update(cx, |view, cx| view.text_length_utf16(window, cx))
255 }
256
257 fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
258 self.view
259 .update(cx, |view, cx| view.accepts_text_input(window, cx))
260 }
261
262 fn prefers_ime_for_printable_keys(&mut self, window: &mut Window, cx: &mut App) -> bool {
263 self.view
264 .update(cx, |view, cx| view.accepts_text_input(window, cx))
265 }
266
267 fn text_input_configuration(
268 &mut self,
269 window: &mut Window,
270 cx: &mut App,
271 ) -> TextInputConfiguration {
272 self.view
273 .update(cx, |view, cx| view.text_input_configuration(window, cx))
274 }
275
276 fn text_input_editable_range(
277 &mut self,
278 window: &mut Window,
279 cx: &mut App,
280 ) -> Option<Range<usize>> {
281 self.view
282 .update(cx, |view, cx| view.text_input_editable_range(window, cx))
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use crate::{
290 AnyWindowHandle, AppContext as _, FocusHandle, InteractiveElement as _, IntoElement,
291 ParentElement as _, Render, Styled as _, TestAppContext, TextInputAction, canvas, div,
292 };
293
294 #[gpui::test]
295 fn text_input_configuration_forwarded_only_on_change(cx: &mut TestAppContext) {
296 let custom = TextInputConfiguration {
297 autocorrect: true,
298 input_action: TextInputAction::Send,
299 ..Default::default()
300 };
301 let window = cx.add_window({
302 let custom = custom.clone();
303 move |_, cx| ConfigurationTestView {
304 focus_handle: cx.focus_handle(),
305 configuration: custom,
306 }
307 });
308 let view = window.root(cx).unwrap();
309 let test_window = cx.test_window(window.into());
310 let window = AnyWindowHandle::from(window);
311 let draw = |cx: &mut TestAppContext| {
312 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
313 .unwrap();
314 };
315
316 draw(cx);
318 assert_eq!(
319 test_window.text_input_configurations(),
320 vec![TextInputConfiguration::default()]
321 );
322
323 cx.update_window(window, |_, window, cx| {
325 let focus_handle = view.read(cx).focus_handle.clone();
326 window.focus(&focus_handle, cx);
327 })
328 .unwrap();
329 draw(cx);
330 assert_eq!(
331 test_window.text_input_configurations(),
332 vec![TextInputConfiguration::default(), custom.clone()]
333 );
334
335 draw(cx);
337 assert_eq!(test_window.text_input_configurations().len(), 2);
338
339 let updated = TextInputConfiguration {
341 suggestions: true,
342 ..custom
343 };
344 view.update(cx, {
345 let updated = updated.clone();
346 |view, cx| {
347 view.configuration = updated;
348 cx.notify();
349 }
350 });
351 draw(cx);
352 assert_eq!(
353 test_window.text_input_configurations().last(),
354 Some(&updated)
355 );
356 assert_eq!(test_window.text_input_configurations().len(), 3);
357
358 cx.update_window(window, |_, window, cx| window.blur(cx))
360 .unwrap();
361 draw(cx);
362 assert_eq!(
363 test_window.text_input_configurations().last(),
364 Some(&TextInputConfiguration::default())
365 );
366 assert_eq!(test_window.text_input_configurations().len(), 4);
367 }
368
369 struct ConfigurationTestView {
370 focus_handle: FocusHandle,
371 configuration: TextInputConfiguration,
372 }
373
374 impl Render for ConfigurationTestView {
375 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
376 let view = cx.entity();
377 let focus_handle = self.focus_handle.clone();
378 div().size_full().track_focus(&self.focus_handle).child(
379 canvas(
380 |_, _, _| {},
381 move |bounds, _, window, cx| {
382 window.handle_input(
383 &focus_handle,
384 ElementInputHandler::new(bounds, view),
385 cx,
386 );
387 },
388 )
389 .size_full(),
390 )
391 }
392 }
393
394 impl EntityInputHandler for ConfigurationTestView {
395 fn text_for_range(
396 &mut self,
397 _range: std::ops::Range<usize>,
398 _adjusted_range: &mut Option<std::ops::Range<usize>>,
399 _window: &mut Window,
400 _cx: &mut Context<Self>,
401 ) -> Option<String> {
402 None
403 }
404
405 fn selected_text_range(
406 &mut self,
407 _ignore_disabled_input: bool,
408 _window: &mut Window,
409 _cx: &mut Context<Self>,
410 ) -> Option<UTF16Selection> {
411 None
412 }
413
414 fn marked_text_range(
415 &self,
416 _window: &mut Window,
417 _cx: &mut Context<Self>,
418 ) -> Option<std::ops::Range<usize>> {
419 None
420 }
421
422 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
423
424 fn replace_text_in_range(
425 &mut self,
426 _range: Option<std::ops::Range<usize>>,
427 _text: &str,
428 _window: &mut Window,
429 _cx: &mut Context<Self>,
430 ) {
431 }
432
433 fn replace_and_mark_text_in_range(
434 &mut self,
435 _range: Option<std::ops::Range<usize>>,
436 _new_text: &str,
437 _new_selected_range: Option<std::ops::Range<usize>>,
438 _window: &mut Window,
439 _cx: &mut Context<Self>,
440 ) {
441 }
442
443 fn bounds_for_range(
444 &mut self,
445 _range_utf16: std::ops::Range<usize>,
446 _element_bounds: Bounds<Pixels>,
447 _window: &mut Window,
448 _cx: &mut Context<Self>,
449 ) -> Option<Bounds<Pixels>> {
450 None
451 }
452
453 fn character_index_for_point(
454 &mut self,
455 _point: crate::Point<Pixels>,
456 _window: &mut Window,
457 _cx: &mut Context<Self>,
458 ) -> Option<usize> {
459 None
460 }
461
462 fn text_input_configuration(
463 &mut self,
464 _window: &mut Window,
465 _cx: &mut Context<Self>,
466 ) -> TextInputConfiguration {
467 self.configuration.clone()
468 }
469 }
470}