Skip to main content

gpui/
input.rs

1use crate::{
2    App, Bounds, ClipboardItem, Context, Entity, InputHandler, Pixels, TextInputConfiguration,
3    UTF16Selection, Window,
4};
5use std::ops::Range;
6
7/// Implement this trait to allow views to handle textual input when implementing an editor, field, etc.
8///
9/// Once your view implements this trait, you can use it to construct an [`ElementInputHandler<V>`].
10/// This input handler can then be assigned during paint by calling [`Window::handle_input`].
11///
12/// Dismissing the software keyboard can leave the component focused. Call
13/// [`Window::request_virtual_keyboard`] from an editing gesture, such as tapping
14/// the field, to reopen it; repainting alone should not undo a dismissal.
15///
16/// After application-driven edits or selection changes, also call
17/// [`Window::notify_text_input_changed`] or [`Window::notify_text_selection_changed`]
18/// so native text services can invalidate their state.
19///
20/// UIKit text input also needs `text_length_utf16` (or a bounded
21/// `text_input_editable_range`) and native selection updates through
22/// `set_selected_text_range`. Supply `selection_bounds_for_range` for native
23/// selection rectangles; it should return line/run fragments in one batch.
24///
25/// See [`InputHandler`] for details on how to implement each method.
26pub trait EntityInputHandler: 'static + Sized {
27    /// See [`InputHandler::text_for_range`] for details
28    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    /// See [`InputHandler::selected_text_range`] for details
37    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    /// See [`InputHandler::marked_text_range`] for details
45    fn marked_text_range(
46        &self,
47        window: &mut Window,
48        cx: &mut Context<Self>,
49    ) -> Option<Range<usize>>;
50
51    /// See [`InputHandler::unmark_text`] for details
52    fn unmark_text(&mut self, window: &mut Window, cx: &mut Context<Self>);
53
54    /// See [`InputHandler::paste`] for details
55    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    /// See [`InputHandler::replace_text_in_range`] for details
62    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    /// See [`InputHandler::replace_and_mark_text_in_range`] for details
71    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    /// See [`InputHandler::bounds_for_range`] for details
81    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    /// See [`InputHandler::selection_bounds_for_range`] for details.
90    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    /// See [`InputHandler::character_index_for_point`] for details
101    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    /// See [`InputHandler::set_selected_text_range`] for details
109    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    /// See [`InputHandler::text_length_utf16`] for details
118    fn text_length_utf16(
119        &mut self,
120        _window: &mut Window,
121        _cx: &mut Context<Self>,
122    ) -> Option<usize> {
123        None
124    }
125
126    /// See [`InputHandler::accepts_text_input`] for details
127    fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
128        true
129    }
130
131    /// See [`InputHandler::text_input_configuration`] for details
132    fn text_input_configuration(
133        &mut self,
134        _window: &mut Window,
135        _cx: &mut Context<Self>,
136    ) -> TextInputConfiguration {
137        TextInputConfiguration::default()
138    }
139
140    /// See [`InputHandler::text_input_editable_range`] for details
141    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
150/// The canonical implementation of [`crate::PlatformInputHandler`]. Call [`Window::handle_input`]
151/// with an instance during your element's paint.
152pub struct ElementInputHandler<V> {
153    view: Entity<V>,
154    element_bounds: Bounds<Pixels>,
155}
156
157impl<V: 'static> ElementInputHandler<V> {
158    /// Used in [`Element::paint`][element_paint] with the element's bounds, a `Window`, and a `App` context.
159    ///
160    /// [element_paint]: crate::Element::paint
161    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        // Nothing is focused, so the platform learns the default configuration.
354        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        // Focusing the view routes its configuration to the platform.
362        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        // Redrawing without a change forwards nothing.
378        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        // Changing the configuration forwards the new value.
383        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        // Losing focus reverts the platform to the default configuration.
403        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}