gpui/input.rs
1use crate::{
2 App, Bounds, ClipboardItem, Context, Entity, InputHandler, Pixels, TextInputConfiguration,
3 UTF16Selection, Window,
4};
5use std::ops::Range;
6
7/// Converts a UTF-8 byte offset into a UTF-16 code-unit offset.
8///
9/// Platform text input counts in UTF-16 code units, while Rust strings index
10/// by UTF-8 bytes. Use this to translate an offset in a `String` into the offset
11/// the platform expects. An offset past the end returns the UTF-16 length of
12/// `text`. An offset inside a character returns the offset of that character's
13/// end.
14///
15/// # Examples
16///
17/// ```
18/// use gpui::utf8_to_utf16_offset;
19///
20/// // "A" is 1 byte and 1 code unit. The emoji is 4 bytes and 2 code units.
21/// assert_eq!(utf8_to_utf16_offset("A😀B", 5), 3);
22/// assert_eq!(utf8_to_utf16_offset("A😀B", 100), 4);
23/// ```
24pub fn utf8_to_utf16_offset(text: &str, utf8_offset: usize) -> usize {
25 text.char_indices()
26 .take_while(|(offset, _)| *offset < utf8_offset)
27 .map(|(_, character)| character.len_utf16())
28 .sum()
29}
30
31/// Converts a UTF-16 code-unit offset into a UTF-8 byte offset.
32///
33/// The reverse of [`utf8_to_utf16_offset`]. Use it on ranges that the platform
34/// sends, before slicing a `String`. An offset past the end returns the UTF-8
35/// length of `text`. An offset inside a surrogate pair returns the offset of the
36/// end of that character.
37///
38/// # Examples
39///
40/// ```
41/// use gpui::utf16_to_utf8_offset;
42///
43/// assert_eq!(utf16_to_utf8_offset("A😀B", 3), 5);
44/// assert_eq!(utf16_to_utf8_offset("A😀B", 100), 6);
45/// ```
46pub fn utf16_to_utf8_offset(text: &str, utf16_offset: usize) -> usize {
47 let mut consumed_utf16 = 0;
48 for (utf8_offset, character) in text.char_indices() {
49 if consumed_utf16 >= utf16_offset {
50 return utf8_offset;
51 }
52 consumed_utf16 += character.len_utf16();
53 }
54 text.len()
55}
56
57/// Text input for a view: the methods the platform calls for typed text, IME
58/// composition, paste and selection.
59///
60/// Implement this on a view that edits text, such as a text field. Then wrap the
61/// view in an [`ElementInputHandler`] and install it during paint with
62/// [`Window::handle_input`](crate::Window::handle_input). The platform's input method sends its requests
63/// through that wrapper, and each method runs with the view updated.
64///
65/// Ranges are in UTF-16 code units, the unit the platform uses, and are
66/// relative to the whole document. Convert with [`utf8_to_utf16_offset`] and
67/// [`utf16_to_utf8_offset`] when your text is a UTF-8 `String`. Every method has
68/// the same meaning as the matching method on [`InputHandler`], which documents
69/// the platform side.
70///
71/// On touch platforms, dismissing the software keyboard can leave the view focused. Call
72/// [`Window::request_virtual_keyboard`] from an editing gesture, such as a tap on the field,
73/// to show it again. After edits or selection changes that your app makes itself, call
74/// [`Window::notify_text_input_changed`] or [`Window::notify_text_selection_changed`] so the
75/// platform's text services stay in sync. UIKit also needs
76/// [`text_length_utf16`](Self::text_length_utf16), or a bounded
77/// [`text_input_editable_range`](Self::text_input_editable_range), and
78/// [`set_selected_text_range`](Self::set_selected_text_range). For native selection
79/// rectangles, implement [`selection_bounds_for_range`](Self::selection_bounds_for_range),
80/// which returns all of a range's line fragments at once.
81///
82/// The text guide has a complete editable field: [text input](crate::_documentation::guides::text_input).
83///
84/// # Examples
85///
86/// ```
87/// use gpui::{
88/// Bounds, Context, ElementInputHandler, EntityInputHandler, FocusHandle, IntoElement,
89/// Pixels, Point, Render, TestApp, UTF16Selection, Window, canvas, div, prelude::*,
90/// };
91/// use std::ops::Range;
92///
93/// struct Field {
94/// focus_handle: FocusHandle,
95/// text: String,
96/// }
97///
98/// impl EntityInputHandler for Field {
99/// fn text_for_range(
100/// &mut self,
101/// range: Range<usize>,
102/// adjusted_range: &mut Option<Range<usize>>,
103/// _window: &mut Window,
104/// _cx: &mut Context<Self>,
105/// ) -> Option<String> {
106/// *adjusted_range = Some(range.clone());
107/// let start = gpui::utf16_to_utf8_offset(&self.text, range.start);
108/// let end = gpui::utf16_to_utf8_offset(&self.text, range.end);
109/// Some(self.text[start..end].to_string())
110/// }
111///
112/// fn selected_text_range(
113/// &mut self,
114/// _ignore_disabled_input: bool,
115/// _window: &mut Window,
116/// _cx: &mut Context<Self>,
117/// ) -> Option<UTF16Selection> {
118/// let end = gpui::utf8_to_utf16_offset(&self.text, self.text.len());
119/// Some(UTF16Selection { range: end..end, reversed: false })
120/// }
121///
122/// fn marked_text_range(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<Range<usize>> {
123/// None
124/// }
125///
126/// fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
127///
128/// fn replace_text_in_range(
129/// &mut self,
130/// range: Option<Range<usize>>,
131/// text: &str,
132/// _window: &mut Window,
133/// cx: &mut Context<Self>,
134/// ) {
135/// let range = range.unwrap_or_else(|| {
136/// let end = gpui::utf8_to_utf16_offset(&self.text, self.text.len());
137/// end..end
138/// });
139/// let start = gpui::utf16_to_utf8_offset(&self.text, range.start);
140/// let end = gpui::utf16_to_utf8_offset(&self.text, range.end);
141/// self.text.replace_range(start..end, text);
142/// cx.notify();
143/// }
144///
145/// fn replace_and_mark_text_in_range(
146/// &mut self,
147/// range: Option<Range<usize>>,
148/// new_text: &str,
149/// _new_selected_range: Option<Range<usize>>,
150/// window: &mut Window,
151/// cx: &mut Context<Self>,
152/// ) {
153/// self.replace_text_in_range(range, new_text, window, cx);
154/// }
155///
156/// fn bounds_for_range(
157/// &mut self,
158/// _range: Range<usize>,
159/// element_bounds: Bounds<Pixels>,
160/// _window: &mut Window,
161/// _cx: &mut Context<Self>,
162/// ) -> Option<Bounds<Pixels>> {
163/// Some(element_bounds)
164/// }
165///
166/// fn character_index_for_point(
167/// &mut self,
168/// _point: Point<Pixels>,
169/// _window: &mut Window,
170/// _cx: &mut Context<Self>,
171/// ) -> Option<usize> {
172/// Some(0)
173/// }
174/// }
175///
176/// impl Render for Field {
177/// fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
178/// let view = cx.entity();
179/// let focus_handle = self.focus_handle.clone();
180/// div().size_full().track_focus(&self.focus_handle).child(
181/// canvas(
182/// |_, _, _| {},
183/// move |bounds, _, window, cx| {
184/// window.handle_input(&focus_handle, ElementInputHandler::new(bounds, view), cx);
185/// },
186/// )
187/// .size_full(),
188/// )
189/// }
190/// }
191///
192/// let mut app = TestApp::new();
193/// let mut window = app.open_window(|window, cx| {
194/// let focus_handle = cx.focus_handle();
195/// window.focus(&focus_handle, cx);
196/// Field { focus_handle, text: String::new() }
197/// });
198///
199/// window.simulate_input("Ada");
200/// assert_eq!(window.read(|field, _| field.text.clone()), "Ada");
201/// ```
202pub trait EntityInputHandler: 'static + Sized {
203 /// Returns the text in a UTF-16 range, for the platform to read back.
204 ///
205 /// The platform uses this to read text around the cursor, for example for
206 /// autocorrect or the character picker. If you return text for a different
207 /// range than was asked for, such as one snapped to a character boundary,
208 /// write the range you returned into `adjusted_range`.
209 fn text_for_range(
210 &mut self,
211 range: Range<usize>,
212 adjusted_range: &mut Option<Range<usize>>,
213 window: &mut Window,
214 cx: &mut Context<Self>,
215 ) -> Option<String>;
216
217 /// Returns the current selection as a UTF-16 range and direction.
218 ///
219 /// Returns `None` if there is no selection to report. `ignore_disabled_input`
220 /// is passed through from the platform, and each implementation decides what it
221 /// means. The bundled text elements ignore it.
222 fn selected_text_range(
223 &mut self,
224 ignore_disabled_input: bool,
225 window: &mut Window,
226 cx: &mut Context<Self>,
227 ) -> Option<UTF16Selection>;
228
229 /// Returns the UTF-16 range of the text in the current IME composition, or
230 /// `None` if no composition is active.
231 ///
232 /// Composition is the text an input method is still building, such as the
233 /// underlined kana before a Japanese conversion is confirmed.
234 fn marked_text_range(
235 &self,
236 window: &mut Window,
237 cx: &mut Context<Self>,
238 ) -> Option<Range<usize>>;
239
240 /// Ends the IME composition and keeps its text as ordinary text.
241 ///
242 /// The platform calls this when it finishes composing. After this call,
243 /// [`EntityInputHandler::marked_text_range`] returns `None`.
244 fn unmark_text(&mut self, window: &mut Window, cx: &mut Context<Self>);
245
246 /// Inserts clipboard contents at the selection, for platforms that deliver
247 /// paste as an input event, such as the web.
248 ///
249 /// The default inserts only the plain-text part of `item`, through
250 /// [`EntityInputHandler::replace_text_in_range`] with a `None` range. Override
251 /// it to handle rich content.
252 fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut Context<Self>) {
253 if let Some(text) = item.text() {
254 self.replace_text_in_range(None, &text, window, cx);
255 }
256 }
257
258 /// Replaces a UTF-16 range with typed text, or inserts at a cursor.
259 ///
260 /// This is how typed characters reach your field. A `None` range means the
261 /// platform gave no range. The bundled text elements then replace the marked
262 /// text if there is any, and otherwise the selection.
263 fn replace_text_in_range(
264 &mut self,
265 range: Option<Range<usize>>,
266 text: &str,
267 window: &mut Window,
268 cx: &mut Context<Self>,
269 );
270
271 /// Replaces a range with IME text and marks that text as composing.
272 ///
273 /// The platform calls this for each step of an input method's composition.
274 /// The marked text is underlined and stays editable until
275 /// [`EntityInputHandler::unmark_text`] is called. `new_selected_range` is the
276 /// selection within `new_text`, in UTF-16 units, counted from the start of
277 /// `new_text`. When it is `None`, the cursor goes to the end of the new text.
278 fn replace_and_mark_text_in_range(
279 &mut self,
280 range: Option<Range<usize>>,
281 new_text: &str,
282 new_selected_range: Option<Range<usize>>,
283 window: &mut Window,
284 cx: &mut Context<Self>,
285 );
286
287 /// Returns where a UTF-16 range is drawn, so the platform can place its
288 /// candidate window and other overlays next to the text.
289 ///
290 /// `element_bounds` is the bounds of the element that installed this handler.
291 /// Return `None` when the text cannot be measured. Returned bounds are in window
292 /// coordinates, in logical pixels.
293 fn bounds_for_range(
294 &mut self,
295 range_utf16: Range<usize>,
296 element_bounds: Bounds<Pixels>,
297 window: &mut Window,
298 cx: &mut Context<Self>,
299 ) -> Option<Bounds<Pixels>>;
300
301 /// Returns the UTF-16 index of the character at `point`, which the platform uses
302 /// to map a screen position to text.
303 ///
304 /// `point` is in window coordinates, in logical pixels.
305 /// See [`InputHandler::selection_bounds_for_range`] for details.
306 fn selection_bounds_for_range(
307 &mut self,
308 _range_utf16: Range<usize>,
309 _element_bounds: Bounds<Pixels>,
310 _window: &mut Window,
311 _cx: &mut Context<Self>,
312 ) -> Vec<Bounds<Pixels>> {
313 Vec::new()
314 }
315
316 /// See [`InputHandler::character_index_for_point`] for details
317 fn character_index_for_point(
318 &mut self,
319 point: crate::Point<Pixels>,
320 window: &mut Window,
321 cx: &mut Context<Self>,
322 ) -> Option<usize>;
323
324 /// Sets the selection to a UTF-16 range, when the platform moves it.
325 ///
326 /// Platforms call this for system-driven selection changes, such as a dragged
327 /// selection handle or Select All from system UI. It is the reverse of
328 /// [`EntityInputHandler::selected_text_range`]. The default does nothing.
329 fn set_selected_text_range(
330 &mut self,
331 _range_utf16: Range<usize>,
332 _window: &mut Window,
333 _cx: &mut Context<Self>,
334 ) {
335 }
336
337 /// Returns the length of the document in UTF-16 code units, or `None` if it is
338 /// not known. The default is `None`.
339 fn text_length_utf16(
340 &mut self,
341 _window: &mut Window,
342 _cx: &mut Context<Self>,
343 ) -> Option<usize> {
344 None
345 }
346
347 /// Returns whether the field accepts typed text right now.
348 ///
349 /// Return `false` for a read-only or disabled field. The default is `true`.
350 /// For a keystroke with [`KeyDownEvent::prefer_character_input`](crate::KeyDownEvent::prefer_character_input) set, the window
351 /// sends the keystroke to text input instead of key bindings only when this
352 /// returns `true`.
353 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
354 true
355 }
356
357 /// Returns platform text assistance settings for this field, such as
358 /// autocorrect and the action key on the software keyboard.
359 ///
360 /// The window asks on every frame and forwards the result only when it
361 /// changes, so return a cheap value. You can vary it with application state,
362 /// such as the cursor position. The default disables text assistance.
363 fn text_input_configuration(
364 &mut self,
365 _window: &mut Window,
366 _cx: &mut Context<Self>,
367 ) -> TextInputConfiguration {
368 TextInputConfiguration::default()
369 }
370
371 /// Returns the UTF-16 range that the platform may read and edit around the
372 /// selection, or `None` for no limit.
373 ///
374 /// Platforms that mirror text into an IME-editable buffer use this range to
375 /// keep multi-step edits, such as word deletion and autocorrect, inside the
376 /// region you choose. The range should contain the selection. The default is
377 /// `None`.
378 fn text_input_editable_range(
379 &mut self,
380 _window: &mut Window,
381 _cx: &mut Context<Self>,
382 ) -> Option<Range<usize>> {
383 None
384 }
385}
386
387/// Connects an [`EntityInputHandler`] view to the platform's text input.
388///
389/// This is the usual way to install an [`EntityInputHandler`]. It implements
390/// [`InputHandler`] by forwarding each call to the view, with the element's
391/// bounds filled in for the methods that need them. Create one during paint and
392/// pass it to [`Window::handle_input`](crate::Window::handle_input). The handler is active for the next
393/// frame only, so install it again on every paint.
394pub struct ElementInputHandler<V> {
395 view: Entity<V>,
396 element_bounds: Bounds<Pixels>,
397}
398
399impl<V: 'static> ElementInputHandler<V> {
400 /// Creates a handler that forwards platform text input to `view`.
401 ///
402 /// Pass the bounds of the element that is painting. They are returned from
403 /// [`InputHandler::element_bounds`] and passed to
404 /// [`EntityInputHandler::bounds_for_range`]. Call this in
405 /// [`Element::paint`][element_paint], then pass the handler to
406 /// [`Window::handle_input`](crate::Window::handle_input). The window installs the handler only while the
407 /// given focus handle is focused.
408 ///
409 /// [element_paint]: crate::Element::paint
410 pub fn new(element_bounds: Bounds<Pixels>, view: Entity<V>) -> Self {
411 ElementInputHandler {
412 view,
413 element_bounds,
414 }
415 }
416}
417
418impl<V: EntityInputHandler> InputHandler for ElementInputHandler<V> {
419 fn selected_text_range(
420 &mut self,
421 ignore_disabled_input: bool,
422 window: &mut Window,
423 cx: &mut App,
424 ) -> Option<UTF16Selection> {
425 self.view.update(cx, |view, cx| {
426 view.selected_text_range(ignore_disabled_input, window, cx)
427 })
428 }
429
430 fn marked_text_range(&mut self, window: &mut Window, cx: &mut App) -> Option<Range<usize>> {
431 self.view
432 .update(cx, |view, cx| view.marked_text_range(window, cx))
433 }
434
435 fn text_for_range(
436 &mut self,
437 range_utf16: Range<usize>,
438 adjusted_range: &mut Option<Range<usize>>,
439 window: &mut Window,
440 cx: &mut App,
441 ) -> Option<String> {
442 self.view.update(cx, |view, cx| {
443 view.text_for_range(range_utf16, adjusted_range, window, cx)
444 })
445 }
446
447 fn replace_text_in_range(
448 &mut self,
449 replacement_range: Option<Range<usize>>,
450 text: &str,
451 window: &mut Window,
452 cx: &mut App,
453 ) {
454 self.view.update(cx, |view, cx| {
455 view.replace_text_in_range(replacement_range, text, window, cx)
456 });
457 }
458
459 fn replace_and_mark_text_in_range(
460 &mut self,
461 range_utf16: Option<Range<usize>>,
462 new_text: &str,
463 new_selected_range: Option<Range<usize>>,
464 window: &mut Window,
465 cx: &mut App,
466 ) {
467 self.view.update(cx, |view, cx| {
468 view.replace_and_mark_text_in_range(
469 range_utf16,
470 new_text,
471 new_selected_range,
472 window,
473 cx,
474 )
475 });
476 }
477
478 fn unmark_text(&mut self, window: &mut Window, cx: &mut App) {
479 self.view
480 .update(cx, |view, cx| view.unmark_text(window, cx));
481 }
482
483 fn paste(&mut self, item: ClipboardItem, window: &mut Window, cx: &mut App) {
484 self.view
485 .update(cx, |view, cx| view.paste(item, window, cx));
486 }
487
488 fn bounds_for_range(
489 &mut self,
490 range_utf16: Range<usize>,
491 window: &mut Window,
492 cx: &mut App,
493 ) -> Option<Bounds<Pixels>> {
494 self.view.update(cx, |view, cx| {
495 view.bounds_for_range(range_utf16, self.element_bounds, window, cx)
496 })
497 }
498
499 fn selection_bounds_for_range(
500 &mut self,
501 range_utf16: Range<usize>,
502 window: &mut Window,
503 cx: &mut App,
504 ) -> Vec<Bounds<Pixels>> {
505 self.view.update(cx, |view, cx| {
506 view.selection_bounds_for_range(range_utf16, self.element_bounds, window, cx)
507 })
508 }
509
510 fn character_index_for_point(
511 &mut self,
512 point: crate::Point<Pixels>,
513 window: &mut Window,
514 cx: &mut App,
515 ) -> Option<usize> {
516 self.view.update(cx, |view, cx| {
517 view.character_index_for_point(point, window, cx)
518 })
519 }
520
521 fn set_selected_text_range(
522 &mut self,
523 range_utf16: Range<usize>,
524 window: &mut Window,
525 cx: &mut App,
526 ) {
527 self.view.update(cx, |view, cx| {
528 view.set_selected_text_range(range_utf16, window, cx)
529 })
530 }
531
532 fn element_bounds(&mut self, _window: &mut Window, _cx: &mut App) -> Option<Bounds<Pixels>> {
533 Some(self.element_bounds)
534 }
535
536 fn text_length_utf16(&mut self, window: &mut Window, cx: &mut App) -> Option<usize> {
537 self.view
538 .update(cx, |view, cx| view.text_length_utf16(window, cx))
539 }
540
541 fn accepts_text_input(&mut self, window: &mut Window, cx: &mut App) -> bool {
542 self.view
543 .update(cx, |view, cx| view.accepts_text_input(window, cx))
544 }
545
546 fn prefers_ime_for_printable_keys(&mut self, window: &mut Window, cx: &mut App) -> bool {
547 self.view
548 .update(cx, |view, cx| view.accepts_text_input(window, cx))
549 }
550
551 fn text_input_configuration(
552 &mut self,
553 window: &mut Window,
554 cx: &mut App,
555 ) -> TextInputConfiguration {
556 self.view
557 .update(cx, |view, cx| view.text_input_configuration(window, cx))
558 }
559
560 fn text_input_editable_range(
561 &mut self,
562 window: &mut Window,
563 cx: &mut App,
564 ) -> Option<Range<usize>> {
565 self.view
566 .update(cx, |view, cx| view.text_input_editable_range(window, cx))
567 }
568}
569
570#[cfg(test)]
571mod configuration_tests {
572 use super::*;
573 use crate::{
574 AnyWindowHandle, AppContext as _, FocusHandle, InteractiveElement as _, IntoElement,
575 ParentElement as _, Render, Styled as _, TestAppContext, TextInputAction,
576 TextInputStateChange, canvas, div,
577 };
578
579 #[gpui::test]
580 fn text_input_configuration_and_focus_state_are_forwarded_on_change(cx: &mut TestAppContext) {
581 let custom = TextInputConfiguration {
582 autocorrect: true,
583 input_action: TextInputAction::Send,
584 ..Default::default()
585 };
586 let window = cx.add_window({
587 let custom = custom.clone();
588 move |_, cx| ConfigurationTestView {
589 focus_handle: cx.focus_handle(),
590 configuration: custom,
591 accepts_text_input: true,
592 }
593 });
594 let view = window.root(cx).unwrap();
595 let test_window = cx.test_window(window.into());
596 let window = AnyWindowHandle::from(window);
597 let draw = |cx: &mut TestAppContext| {
598 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
599 .unwrap();
600 };
601
602 // Nothing is focused, so the platform learns the default configuration.
603 draw(cx);
604 assert_eq!(
605 test_window.text_input_configurations(),
606 vec![TextInputConfiguration::default()]
607 );
608 assert!(test_window.text_input_state_changes().is_empty());
609
610 // Focusing the view routes its configuration to the platform.
611 cx.update_window(window, |_, window, cx| {
612 let focus_handle = view.read(cx).focus_handle.clone();
613 window.focus(&focus_handle, cx);
614 })
615 .unwrap();
616 draw(cx);
617 assert_eq!(
618 test_window.text_input_configurations(),
619 vec![TextInputConfiguration::default(), custom.clone()]
620 );
621 assert_eq!(
622 test_window.text_input_state_changes(),
623 vec![TextInputStateChange::FocusGained]
624 );
625
626 // Redrawing without a change forwards nothing.
627 draw(cx);
628 assert_eq!(test_window.text_input_configurations().len(), 2);
629 assert_eq!(test_window.text_input_state_changes().len(), 1);
630
631 // Changing the configuration forwards the new value.
632 let updated = TextInputConfiguration {
633 suggestions: true,
634 ..custom
635 };
636 view.update(cx, {
637 let updated = updated.clone();
638 |view, cx| {
639 view.configuration = updated;
640 cx.notify();
641 }
642 });
643 draw(cx);
644 assert_eq!(
645 test_window.text_input_configurations().last(),
646 Some(&updated)
647 );
648 assert_eq!(test_window.text_input_configurations().len(), 3);
649 assert_eq!(test_window.text_input_state_changes().len(), 1);
650
651 // Losing focus reverts the platform to the default configuration.
652 cx.update_window(window, |_, window, cx| window.blur(cx))
653 .unwrap();
654 draw(cx);
655 assert_eq!(
656 test_window.text_input_configurations().last(),
657 Some(&TextInputConfiguration::default())
658 );
659 assert_eq!(test_window.text_input_configurations().len(), 4);
660 assert_eq!(
661 test_window.text_input_state_changes(),
662 vec![
663 TextInputStateChange::FocusGained,
664 TextInputStateChange::FocusLost
665 ]
666 );
667 }
668
669 #[gpui::test]
670 fn text_notifications_require_a_focused_input_and_track_field_changes(cx: &mut TestAppContext) {
671 let window = cx.add_window(|_, cx| ConfigurationTestView {
672 focus_handle: cx.focus_handle(),
673 configuration: TextInputConfiguration::default(),
674 accepts_text_input: true,
675 });
676 let view = window.root(cx).unwrap();
677 let mut test_window = cx.test_window(window.into());
678 let window = AnyWindowHandle::from(window);
679 let first_focus = view.read_with(cx, |view, _| view.focus_handle.clone());
680 cx.update_window(window, |_, window, cx| {
681 window.focus(&first_focus, cx);
682 window.draw(cx).clear(cx);
683 window.notify_text_input_changed(&first_focus);
684 window.notify_text_selection_changed(&first_focus);
685 })
686 .unwrap();
687
688 use crate::PlatformWindow as _;
689 let mut installed_handler = test_window.take_input_handler().unwrap();
690 assert!(installed_handler.query_accepts_focused_text_input());
691 assert_eq!(installed_handler.focus_id(), Some(first_focus.id));
692 let second_focus = cx
693 .update_window(window, |_, window, cx| {
694 let second_focus = view.update(cx, |view, cx| {
695 view.focus_handle = cx.focus_handle();
696 cx.notify();
697 view.focus_handle.clone()
698 });
699 window.focus(&second_focus, cx);
700 second_focus
701 })
702 .unwrap();
703 assert!(!installed_handler.query_accepts_focused_text_input());
704 test_window.set_input_handler(installed_handler);
705 cx.update_window(window, |_, window, cx| {
706 window.draw(cx).clear(cx);
707 window.notify_text_input_changed(&first_focus);
708 })
709 .unwrap();
710 assert_eq!(
711 test_window.text_input_state_changes(),
712 [
713 TextInputStateChange::FocusGained,
714 TextInputStateChange::ContentChanged,
715 TextInputStateChange::SelectionChanged,
716 ]
717 );
718
719 let installed_handler = test_window.take_input_handler().unwrap();
720 assert_eq!(installed_handler.focus_id(), Some(second_focus.id));
721 test_window.set_input_handler(installed_handler);
722 cx.update_window(window, |_, window, cx| {
723 window.draw(cx).clear(cx);
724 window.notify_text_selection_changed(&second_focus);
725 })
726 .unwrap();
727
728 view.update(cx, |view, cx| {
729 view.accepts_text_input = false;
730 cx.notify();
731 });
732 cx.update_window(window, |_, window, cx| {
733 window.draw(cx).clear(cx);
734 window.notify_text_input_changed(&second_focus);
735 window.notify_text_selection_changed(&second_focus);
736 let ordinary_focus = cx.focus_handle();
737 window.focus(&ordinary_focus, cx);
738 window.draw(cx).clear(cx);
739 window.notify_text_input_changed(&ordinary_focus);
740 })
741 .unwrap();
742 assert_eq!(
743 test_window.text_input_state_changes(),
744 [
745 TextInputStateChange::FocusGained,
746 TextInputStateChange::ContentChanged,
747 TextInputStateChange::SelectionChanged,
748 TextInputStateChange::SelectionChanged,
749 TextInputStateChange::FocusLost,
750 ]
751 );
752 }
753
754 struct ConfigurationTestView {
755 focus_handle: FocusHandle,
756 configuration: TextInputConfiguration,
757 accepts_text_input: bool,
758 }
759
760 impl Render for ConfigurationTestView {
761 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
762 let view = cx.entity();
763 let focus_handle = self.focus_handle.clone();
764 div().size_full().track_focus(&self.focus_handle).child(
765 canvas(
766 |_, _, _| {},
767 move |bounds, _, window, cx| {
768 window.handle_input(
769 &focus_handle,
770 ElementInputHandler::new(bounds, view),
771 cx,
772 );
773 },
774 )
775 .size_full(),
776 )
777 }
778 }
779
780 impl EntityInputHandler for ConfigurationTestView {
781 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
782 self.accepts_text_input
783 }
784
785 fn text_for_range(
786 &mut self,
787 _range: std::ops::Range<usize>,
788 _adjusted_range: &mut Option<std::ops::Range<usize>>,
789 _window: &mut Window,
790 _cx: &mut Context<Self>,
791 ) -> Option<String> {
792 None
793 }
794
795 fn selected_text_range(
796 &mut self,
797 _ignore_disabled_input: bool,
798 _window: &mut Window,
799 _cx: &mut Context<Self>,
800 ) -> Option<UTF16Selection> {
801 None
802 }
803
804 fn marked_text_range(
805 &self,
806 _window: &mut Window,
807 _cx: &mut Context<Self>,
808 ) -> Option<std::ops::Range<usize>> {
809 None
810 }
811
812 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
813
814 fn replace_text_in_range(
815 &mut self,
816 _range: Option<std::ops::Range<usize>>,
817 _text: &str,
818 _window: &mut Window,
819 _cx: &mut Context<Self>,
820 ) {
821 }
822
823 fn replace_and_mark_text_in_range(
824 &mut self,
825 _range: Option<std::ops::Range<usize>>,
826 _new_text: &str,
827 _new_selected_range: Option<std::ops::Range<usize>>,
828 _window: &mut Window,
829 _cx: &mut Context<Self>,
830 ) {
831 }
832
833 fn bounds_for_range(
834 &mut self,
835 _range_utf16: std::ops::Range<usize>,
836 _element_bounds: Bounds<Pixels>,
837 _window: &mut Window,
838 _cx: &mut Context<Self>,
839 ) -> Option<Bounds<Pixels>> {
840 None
841 }
842
843 fn character_index_for_point(
844 &mut self,
845 _point: crate::Point<Pixels>,
846 _window: &mut Window,
847 _cx: &mut Context<Self>,
848 ) -> Option<usize> {
849 None
850 }
851
852 fn text_input_configuration(
853 &mut self,
854 _window: &mut Window,
855 _cx: &mut Context<Self>,
856 ) -> TextInputConfiguration {
857 self.configuration.clone()
858 }
859 }
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865
866 #[test]
867 fn text_offsets_convert_across_unicode_encodings() {
868 let cases = [
869 ("", vec![(0, 0)]),
870 ("plain", vec![(0, 0), (1, 1), (5, 5)]),
871 (
872 "A😀日本e\u{301}",
873 vec![(0, 0), (1, 1), (5, 3), (8, 4), (11, 5), (12, 6), (14, 7)],
874 ),
875 ];
876
877 for (text, boundaries) in cases {
878 for (utf8, utf16) in boundaries {
879 assert_eq!(utf8_to_utf16_offset(text, utf8), utf16, "{text:?}");
880 assert_eq!(utf16_to_utf8_offset(text, utf16), utf8, "{text:?}");
881 }
882
883 assert_eq!(
884 utf8_to_utf16_offset(text, usize::MAX),
885 text.encode_utf16().count()
886 );
887 assert_eq!(utf16_to_utf8_offset(text, usize::MAX), text.len());
888 }
889
890 let text = "A😀B";
891 assert_eq!(utf16_to_utf8_offset(text, 2), 5);
892 assert_eq!(utf8_to_utf16_offset(text, 2), 3);
893 }
894}