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 {
27 fn text_for_range(
29 &mut self,
30 range: Range<usize>,
31 adjusted_range: &mut Option<Range<usize>>,
32 window: &mut Window,
33 cx: &mut Context<Self>,
34 ) -> Option<String>;
35
36 fn selected_text_range(
38 &mut self,
39 ignore_disabled_input: bool,
40 window: &mut Window,
41 cx: &mut Context<Self>,
42 ) -> Option<UTF16Selection>;
43
44 fn marked_text_range(
46 &self,
47 window: &mut Window,
48 cx: &mut Context<Self>,
49 ) -> Option<Range<usize>>;
50
51 fn unmark_text(&mut self, window: &mut Window, cx: &mut Context<Self>);
53
54 fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut Context<Self>) {
56 if let Some(text) = item.text() {
57 self.replace_text_in_range(None, &text, window, cx);
58 }
59 }
60
61 fn replace_text_in_range(
63 &mut self,
64 range: Option<Range<usize>>,
65 text: &str,
66 window: &mut Window,
67 cx: &mut Context<Self>,
68 );
69
70 fn replace_and_mark_text_in_range(
72 &mut self,
73 range: Option<Range<usize>>,
74 new_text: &str,
75 new_selected_range: Option<Range<usize>>,
76 window: &mut Window,
77 cx: &mut Context<Self>,
78 );
79
80 fn bounds_for_range(
82 &mut self,
83 range_utf16: Range<usize>,
84 element_bounds: Bounds<Pixels>,
85 window: &mut Window,
86 cx: &mut Context<Self>,
87 ) -> Option<Bounds<Pixels>>;
88
89 fn selection_bounds_for_range(
91 &mut self,
92 _range_utf16: Range<usize>,
93 _element_bounds: Bounds<Pixels>,
94 _window: &mut Window,
95 _cx: &mut Context<Self>,
96 ) -> Vec<Bounds<Pixels>> {
97 Vec::new()
98 }
99
100 fn character_index_for_point(
102 &mut self,
103 point: crate::Point<Pixels>,
104 window: &mut Window,
105 cx: &mut Context<Self>,
106 ) -> Option<usize>;
107
108 fn set_selected_text_range(
110 &mut self,
111 _range_utf16: Range<usize>,
112 _window: &mut Window,
113 _cx: &mut Context<Self>,
114 ) {
115 }
116
117 fn text_length_utf16(
119 &mut self,
120 _window: &mut Window,
121 _cx: &mut Context<Self>,
122 ) -> Option<usize> {
123 None
124 }
125
126 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
128 true
129 }
130
131 fn text_input_configuration(
133 &mut self,
134 _window: &mut Window,
135 _cx: &mut Context<Self>,
136 ) -> TextInputConfiguration {
137 TextInputConfiguration::default()
138 }
139
140 fn text_input_editable_range(
142 &mut self,
143 _window: &mut Window,
144 _cx: &mut Context<Self>,
145 ) -> Option<Range<usize>> {
146 None
147 }
148}
149
150pub struct ElementInputHandler<V> {
153 view: Entity<V>,
154 element_bounds: Bounds<Pixels>,
155}
156
157impl<V: 'static> ElementInputHandler<V> {
158 pub fn new(element_bounds: Bounds<Pixels>, view: Entity<V>) -> Self {
162 ElementInputHandler {
163 view,
164 element_bounds,
165 }
166 }
167}
168
169impl<V: EntityInputHandler> InputHandler for ElementInputHandler<V> {
170 fn selected_text_range(
171 &mut self,
172 ignore_disabled_input: bool,
173 window: &mut Window,
174 cx: &mut App,
175 ) -> Option<UTF16Selection> {
176 self.view.update(cx, |view, cx| {
177 view.selected_text_range(ignore_disabled_input, window, cx)
178 })
179 }
180
181 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>> {
182 self.view
183 .update(cx, |view, cx| view.marked_text_range(window, cx))
184 }
185
186 fn text_for_range(
187 &mut self,
188 range_utf16: Range<usize>,
189 adjusted_range: &mut Option<Range<usize>>,
190 window: &mut Window,
191 cx: &mut App,
192 ) -> Option<String> {
193 self.view.update(cx, |view, cx| {
194 view.text_for_range(range_utf16, adjusted_range, window, cx)
195 })
196 }
197
198 fn replace_text_in_range(
199 &mut self,
200 replacement_range: Option<Range<usize>>,
201 text: &str,
202 window: &mut Window,
203 cx: &mut App,
204 ) {
205 self.view.update(cx, |view, cx| {
206 view.replace_text_in_range(replacement_range, text, window, cx)
207 });
208 }
209
210 fn replace_and_mark_text_in_range(
211 &mut self,
212 range_utf16: Option<Range<usize>>,
213 new_text: &str,
214 new_selected_range: Option<Range<usize>>,
215 window: &mut Window,
216 cx: &mut App,
217 ) {
218 self.view.update(cx, |view, cx| {
219 view.replace_and_mark_text_in_range(
220 range_utf16,
221 new_text,
222 new_selected_range,
223 window,
224 cx,
225 )
226 });
227 }
228
229 fn unmark_text(&mut self, window: &mut Window, cx: &mut App) {
230 self.view
231 .update(cx, |view, cx| view.unmark_text(window, cx));
232 }
233
234 fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
235 self.view
236 .update(cx, |view, cx| view.paste(item, window, cx));
237 }
238
239 fn bounds_for_range(
240 &mut self,
241 range_utf16: Range<usize>,
242 window: &mut Window,
243 cx: &mut App,
244 ) -> Option<Bounds<Pixels>> {
245 self.view.update(cx, |view, cx| {
246 view.bounds_for_range(range_utf16, self.element_bounds, window, cx)
247 })
248 }
249
250 fn selection_bounds_for_range(
251 &mut self,
252 range_utf16: Range<usize>,
253 window: &mut Window,
254 cx: &mut App,
255 ) -> Vec<Bounds<Pixels>> {
256 self.view.update(cx, |view, cx| {
257 view.selection_bounds_for_range(range_utf16, self.element_bounds, window, cx)
258 })
259 }
260
261 fn character_index_for_point(
262 &mut self,
263 point: crate::Point<Pixels>,
264 window: &mut Window,
265 cx: &mut App,
266 ) -> Option<usize> {
267 self.view.update(cx, |view, cx| {
268 view.character_index_for_point(point, window, cx)
269 })
270 }
271
272 fn set_selected_text_range(
273 &mut self,
274 range_utf16: Range<usize>,
275 window: &mut Window,
276 cx: &mut App,
277 ) {
278 self.view.update(cx, |view, cx| {
279 view.set_selected_text_range(range_utf16, window, cx)
280 })
281 }
282
283 fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
284 Some(self.element_bounds)
285 }
286
287 fn text_length_utf16(&mut self, window: &mut Window, cx: &mut App) -> Option<usize> {
288 self.view
289 .update(cx, |view, cx| view.text_length_utf16(window, cx))
290 }
291
292 fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
293 self.view
294 .update(cx, |view, cx| view.accepts_text_input(window, cx))
295 }
296
297 fn prefers_ime_for_printable_keys(&mut self, window: &mut Window, cx: &mut App) -> bool {
298 self.view
299 .update(cx, |view, cx| view.accepts_text_input(window, cx))
300 }
301
302 fn text_input_configuration(
303 &mut self,
304 window: &mut Window,
305 cx: &mut App,
306 ) -> TextInputConfiguration {
307 self.view
308 .update(cx, |view, cx| view.text_input_configuration(window, cx))
309 }
310
311 fn text_input_editable_range(
312 &mut self,
313 window: &mut Window,
314 cx: &mut App,
315 ) -> Option<Range<usize>> {
316 self.view
317 .update(cx, |view, cx| view.text_input_editable_range(window, cx))
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324 use crate::{
325 AnyWindowHandle, AppContext as _, FocusHandle, InteractiveElement as _, IntoElement,
326 ParentElement as _, Render, Styled as _, TestAppContext, TextInputAction,
327 TextInputStateChange, canvas, div,
328 };
329
330 #[gpui::test]
331 fn text_input_configuration_and_focus_state_are_forwarded_on_change(cx: &mut TestAppContext) {
332 let custom = TextInputConfiguration {
333 autocorrect: true,
334 input_action: TextInputAction::Send,
335 ..Default::default()
336 };
337 let window = cx.add_window({
338 let custom = custom.clone();
339 move |_, cx| ConfigurationTestView {
340 focus_handle: cx.focus_handle(),
341 configuration: custom,
342 accepts_text_input: true,
343 }
344 });
345 let view = window.root(cx).unwrap();
346 let test_window = cx.test_window(window.into());
347 let window = AnyWindowHandle::from(window);
348 let draw = |cx: &mut TestAppContext| {
349 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
350 .unwrap();
351 };
352
353 draw(cx);
355 assert_eq!(
356 test_window.text_input_configurations(),
357 vec![TextInputConfiguration::default()]
358 );
359 assert!(test_window.text_input_state_changes().is_empty());
360
361 cx.update_window(window, |_, window, cx| {
363 let focus_handle = view.read(cx).focus_handle.clone();
364 window.focus(&focus_handle, cx);
365 })
366 .unwrap();
367 draw(cx);
368 assert_eq!(
369 test_window.text_input_configurations(),
370 vec![TextInputConfiguration::default(), custom.clone()]
371 );
372 assert_eq!(
373 test_window.text_input_state_changes(),
374 vec![TextInputStateChange::FocusGained]
375 );
376
377 draw(cx);
379 assert_eq!(test_window.text_input_configurations().len(), 2);
380 assert_eq!(test_window.text_input_state_changes().len(), 1);
381
382 let updated = TextInputConfiguration {
384 suggestions: true,
385 ..custom
386 };
387 view.update(cx, {
388 let updated = updated.clone();
389 |view, cx| {
390 view.configuration = updated;
391 cx.notify();
392 }
393 });
394 draw(cx);
395 assert_eq!(
396 test_window.text_input_configurations().last(),
397 Some(&updated)
398 );
399 assert_eq!(test_window.text_input_configurations().len(), 3);
400 assert_eq!(test_window.text_input_state_changes().len(), 1);
401
402 cx.update_window(window, |_, window, cx| window.blur(cx))
404 .unwrap();
405 draw(cx);
406 assert_eq!(
407 test_window.text_input_configurations().last(),
408 Some(&TextInputConfiguration::default())
409 );
410 assert_eq!(test_window.text_input_configurations().len(), 4);
411 assert_eq!(
412 test_window.text_input_state_changes(),
413 vec![
414 TextInputStateChange::FocusGained,
415 TextInputStateChange::FocusLost
416 ]
417 );
418 }
419
420 #[gpui::test]
421 fn text_notifications_require_a_focused_input_and_track_field_changes(cx: &mut TestAppContext) {
422 let window = cx.add_window(|_, cx| ConfigurationTestView {
423 focus_handle: cx.focus_handle(),
424 configuration: TextInputConfiguration::default(),
425 accepts_text_input: true,
426 });
427 let view = window.root(cx).unwrap();
428 let mut test_window = cx.test_window(window.into());
429 let window = AnyWindowHandle::from(window);
430 let first_focus = view.read_with(cx, |view, _| view.focus_handle.clone());
431 cx.update_window(window, |_, window, cx| {
432 window.focus(&first_focus, cx);
433 window.draw(cx).clear(cx);
434 window.notify_text_input_changed(&first_focus);
435 window.notify_text_selection_changed(&first_focus);
436 })
437 .unwrap();
438
439 use crate::PlatformWindow as _;
440 let mut installed_handler = test_window.take_input_handler().unwrap();
441 assert!(installed_handler.query_accepts_focused_text_input());
442 assert_eq!(installed_handler.focus_id(), Some(first_focus.id));
443 let second_focus = cx
444 .update_window(window, |_, window, cx| {
445 let second_focus = view.update(cx, |view, cx| {
446 view.focus_handle = cx.focus_handle();
447 cx.notify();
448 view.focus_handle.clone()
449 });
450 window.focus(&second_focus, cx);
451 second_focus
452 })
453 .unwrap();
454 assert!(!installed_handler.query_accepts_focused_text_input());
455 test_window.set_input_handler(installed_handler);
456 cx.update_window(window, |_, window, cx| {
457 window.draw(cx).clear(cx);
458 window.notify_text_input_changed(&first_focus);
459 })
460 .unwrap();
461 assert_eq!(
462 test_window.text_input_state_changes(),
463 [
464 TextInputStateChange::FocusGained,
465 TextInputStateChange::ContentChanged,
466 TextInputStateChange::SelectionChanged,
467 ]
468 );
469
470 let installed_handler = test_window.take_input_handler().unwrap();
471 assert_eq!(installed_handler.focus_id(), Some(second_focus.id));
472 test_window.set_input_handler(installed_handler);
473 cx.update_window(window, |_, window, cx| {
474 window.draw(cx).clear(cx);
475 window.notify_text_selection_changed(&second_focus);
476 })
477 .unwrap();
478
479 view.update(cx, |view, cx| {
480 view.accepts_text_input = false;
481 cx.notify();
482 });
483 cx.update_window(window, |_, window, cx| {
484 window.draw(cx).clear(cx);
485 window.notify_text_input_changed(&second_focus);
486 window.notify_text_selection_changed(&second_focus);
487 let ordinary_focus = cx.focus_handle();
488 window.focus(&ordinary_focus, cx);
489 window.draw(cx).clear(cx);
490 window.notify_text_input_changed(&ordinary_focus);
491 })
492 .unwrap();
493 assert_eq!(
494 test_window.text_input_state_changes(),
495 [
496 TextInputStateChange::FocusGained,
497 TextInputStateChange::ContentChanged,
498 TextInputStateChange::SelectionChanged,
499 TextInputStateChange::SelectionChanged,
500 TextInputStateChange::FocusLost,
501 ]
502 );
503 }
504
505 struct ConfigurationTestView {
506 focus_handle: FocusHandle,
507 configuration: TextInputConfiguration,
508 accepts_text_input: bool,
509 }
510
511 impl Render for ConfigurationTestView {
512 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
513 let view = cx.entity();
514 let focus_handle = self.focus_handle.clone();
515 div().size_full().track_focus(&self.focus_handle).child(
516 canvas(
517 |_, _, _| {},
518 move |bounds, _, window, cx| {
519 window.handle_input(
520 &focus_handle,
521 ElementInputHandler::new(bounds, view),
522 cx,
523 );
524 },
525 )
526 .size_full(),
527 )
528 }
529 }
530
531 impl EntityInputHandler for ConfigurationTestView {
532 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
533 self.accepts_text_input
534 }
535
536 fn text_for_range(
537 &mut self,
538 _range: std::ops::Range<usize>,
539 _adjusted_range: &mut Option<std::ops::Range<usize>>,
540 _window: &mut Window,
541 _cx: &mut Context<Self>,
542 ) -> Option<String> {
543 None
544 }
545
546 fn selected_text_range(
547 &mut self,
548 _ignore_disabled_input: bool,
549 _window: &mut Window,
550 _cx: &mut Context<Self>,
551 ) -> Option<UTF16Selection> {
552 None
553 }
554
555 fn marked_text_range(
556 &self,
557 _window: &mut Window,
558 _cx: &mut Context<Self>,
559 ) -> Option<std::ops::Range<usize>> {
560 None
561 }
562
563 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
564
565 fn replace_text_in_range(
566 &mut self,
567 _range: Option<std::ops::Range<usize>>,
568 _text: &str,
569 _window: &mut Window,
570 _cx: &mut Context<Self>,
571 ) {
572 }
573
574 fn replace_and_mark_text_in_range(
575 &mut self,
576 _range: Option<std::ops::Range<usize>>,
577 _new_text: &str,
578 _new_selected_range: Option<std::ops::Range<usize>>,
579 _window: &mut Window,
580 _cx: &mut Context<Self>,
581 ) {
582 }
583
584 fn bounds_for_range(
585 &mut self,
586 _range_utf16: std::ops::Range<usize>,
587 _element_bounds: Bounds<Pixels>,
588 _window: &mut Window,
589 _cx: &mut Context<Self>,
590 ) -> Option<Bounds<Pixels>> {
591 None
592 }
593
594 fn character_index_for_point(
595 &mut self,
596 _point: crate::Point<Pixels>,
597 _window: &mut Window,
598 _cx: &mut Context<Self>,
599 ) -> Option<usize> {
600 None
601 }
602
603 fn text_input_configuration(
604 &mut self,
605 _window: &mut Window,
606 _cx: &mut Context<Self>,
607 ) -> TextInputConfiguration {
608 self.configuration.clone()
609 }
610 }
611}