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,
292 TextInputStateChange, canvas, div,
293 };
294
295 #[gpui::test]
296 fn text_input_configuration_and_focus_state_are_forwarded_on_change(cx: &mut TestAppContext) {
297 let custom = TextInputConfiguration {
298 autocorrect: true,
299 input_action: TextInputAction::Send,
300 ..Default::default()
301 };
302 let window = cx.add_window({
303 let custom = custom.clone();
304 move |_, cx| ConfigurationTestView {
305 focus_handle: cx.focus_handle(),
306 configuration: custom,
307 }
308 });
309 let view = window.root(cx).unwrap();
310 let test_window = cx.test_window(window.into());
311 let window = AnyWindowHandle::from(window);
312 let draw = |cx: &mut TestAppContext| {
313 cx.update_window(window, |_, window, cx| window.draw(cx).clear(cx))
314 .unwrap();
315 };
316
317 draw(cx);
319 assert_eq!(
320 test_window.text_input_configurations(),
321 vec![TextInputConfiguration::default()]
322 );
323 assert!(test_window.text_input_state_changes().is_empty());
324
325 cx.update_window(window, |_, window, cx| {
327 let focus_handle = view.read(cx).focus_handle.clone();
328 window.focus(&focus_handle, cx);
329 })
330 .unwrap();
331 draw(cx);
332 assert_eq!(
333 test_window.text_input_configurations(),
334 vec![TextInputConfiguration::default(), custom.clone()]
335 );
336 assert_eq!(
337 test_window.text_input_state_changes(),
338 vec![TextInputStateChange::FocusGained]
339 );
340
341 draw(cx);
343 assert_eq!(test_window.text_input_configurations().len(), 2);
344 assert_eq!(test_window.text_input_state_changes().len(), 1);
345
346 let updated = TextInputConfiguration {
348 suggestions: true,
349 ..custom
350 };
351 view.update(cx, {
352 let updated = updated.clone();
353 |view, cx| {
354 view.configuration = updated;
355 cx.notify();
356 }
357 });
358 draw(cx);
359 assert_eq!(
360 test_window.text_input_configurations().last(),
361 Some(&updated)
362 );
363 assert_eq!(test_window.text_input_configurations().len(), 3);
364 assert_eq!(test_window.text_input_state_changes().len(), 1);
365
366 cx.update_window(window, |_, window, cx| window.blur(cx))
368 .unwrap();
369 draw(cx);
370 assert_eq!(
371 test_window.text_input_configurations().last(),
372 Some(&TextInputConfiguration::default())
373 );
374 assert_eq!(test_window.text_input_configurations().len(), 4);
375 assert_eq!(
376 test_window.text_input_state_changes(),
377 vec![
378 TextInputStateChange::FocusGained,
379 TextInputStateChange::FocusLost
380 ]
381 );
382 }
383
384 struct ConfigurationTestView {
385 focus_handle: FocusHandle,
386 configuration: TextInputConfiguration,
387 }
388
389 impl Render for ConfigurationTestView {
390 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
391 let view = cx.entity();
392 let focus_handle = self.focus_handle.clone();
393 div().size_full().track_focus(&self.focus_handle).child(
394 canvas(
395 |_, _, _| {},
396 move |bounds, _, window, cx| {
397 window.handle_input(
398 &focus_handle,
399 ElementInputHandler::new(bounds, view),
400 cx,
401 );
402 },
403 )
404 .size_full(),
405 )
406 }
407 }
408
409 impl EntityInputHandler for ConfigurationTestView {
410 fn text_for_range(
411 &mut self,
412 _range: std::ops::Range<usize>,
413 _adjusted_range: &mut Option<std::ops::Range<usize>>,
414 _window: &mut Window,
415 _cx: &mut Context<Self>,
416 ) -> Option<String> {
417 None
418 }
419
420 fn selected_text_range(
421 &mut self,
422 _ignore_disabled_input: bool,
423 _window: &mut Window,
424 _cx: &mut Context<Self>,
425 ) -> Option<UTF16Selection> {
426 None
427 }
428
429 fn marked_text_range(
430 &self,
431 _window: &mut Window,
432 _cx: &mut Context<Self>,
433 ) -> Option<std::ops::Range<usize>> {
434 None
435 }
436
437 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
438
439 fn replace_text_in_range(
440 &mut self,
441 _range: Option<std::ops::Range<usize>>,
442 _text: &str,
443 _window: &mut Window,
444 _cx: &mut Context<Self>,
445 ) {
446 }
447
448 fn replace_and_mark_text_in_range(
449 &mut self,
450 _range: Option<std::ops::Range<usize>>,
451 _new_text: &str,
452 _new_selected_range: Option<std::ops::Range<usize>>,
453 _window: &mut Window,
454 _cx: &mut Context<Self>,
455 ) {
456 }
457
458 fn bounds_for_range(
459 &mut self,
460 _range_utf16: std::ops::Range<usize>,
461 _element_bounds: Bounds<Pixels>,
462 _window: &mut Window,
463 _cx: &mut Context<Self>,
464 ) -> Option<Bounds<Pixels>> {
465 None
466 }
467
468 fn character_index_for_point(
469 &mut self,
470 _point: crate::Point<Pixels>,
471 _window: &mut Window,
472 _cx: &mut Context<Self>,
473 ) -> Option<usize> {
474 None
475 }
476
477 fn text_input_configuration(
478 &mut self,
479 _window: &mut Window,
480 _cx: &mut Context<Self>,
481 ) -> TextInputConfiguration {
482 self.configuration.clone()
483 }
484 }
485}