input.rs

  1use std::ops::Range;
  2
  3use gpui::*;
  4use unicode_segmentation::*;
  5
  6actions!(
  7    text_input,
  8    [
  9        Backspace,
 10        Delete,
 11        Left,
 12        Right,
 13        SelectLeft,
 14        SelectRight,
 15        SelectAll,
 16        Home,
 17        End,
 18        ShowCharacterPalette
 19    ]
 20);
 21
 22struct TextInput {
 23    focus_handle: FocusHandle,
 24    content: SharedString,
 25    selected_range: Range<usize>,
 26    selection_reversed: bool,
 27    marked_range: Option<Range<usize>>,
 28    last_layout: Option<ShapedLine>,
 29    last_bounds: Option<Bounds<Pixels>>,
 30    is_selecting: bool,
 31}
 32
 33impl TextInput {
 34    fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {
 35        if self.selected_range.is_empty() {
 36            self.move_to(self.previous_boundary(self.cursor_offset()), cx);
 37        } else {
 38            self.move_to(self.selected_range.start, cx)
 39        }
 40    }
 41
 42    fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {
 43        if self.selected_range.is_empty() {
 44            self.move_to(self.next_boundary(self.selected_range.end), cx);
 45        } else {
 46            self.move_to(self.selected_range.end, cx)
 47        }
 48    }
 49
 50    fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
 51        self.select_to(self.previous_boundary(self.cursor_offset()), cx);
 52    }
 53
 54    fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
 55        self.select_to(self.next_boundary(self.cursor_offset()), cx);
 56    }
 57
 58    fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
 59        self.move_to(0, cx);
 60        self.select_to(self.content.len(), cx)
 61    }
 62
 63    fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) {
 64        self.move_to(0, cx);
 65    }
 66
 67    fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) {
 68        self.move_to(self.content.len(), cx);
 69    }
 70
 71    fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
 72        if self.selected_range.is_empty() {
 73            self.select_to(self.previous_boundary(self.cursor_offset()), cx)
 74        }
 75        self.replace_text_in_range(None, "", cx)
 76    }
 77
 78    fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
 79        if self.selected_range.is_empty() {
 80            self.select_to(self.next_boundary(self.cursor_offset()), cx)
 81        }
 82        self.replace_text_in_range(None, "", cx)
 83    }
 84
 85    fn on_mouse_down(&mut self, event: &MouseDownEvent, cx: &mut ViewContext<Self>) {
 86        self.is_selecting = true;
 87        self.move_to(self.index_for_mouse_position(event.position), cx)
 88    }
 89
 90    fn on_mouse_up(&mut self, _: &MouseUpEvent, _: &mut ViewContext<Self>) {
 91        self.is_selecting = false;
 92    }
 93
 94    fn on_mouse_move(&mut self, event: &MouseMoveEvent, cx: &mut ViewContext<Self>) {
 95        if self.is_selecting {
 96            self.select_to(self.index_for_mouse_position(event.position), cx);
 97        }
 98    }
 99
100    fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
101        cx.show_character_palette();
102    }
103
104    fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
105        self.selected_range = offset..offset;
106        cx.notify()
107    }
108
109    fn cursor_offset(&self) -> usize {
110        if self.selection_reversed {
111            self.selected_range.start
112        } else {
113            self.selected_range.end
114        }
115    }
116
117    fn index_for_mouse_position(&self, position: Point<Pixels>) -> usize {
118        let (Some(bounds), Some(line)) = (self.last_bounds.as_ref(), self.last_layout.as_ref())
119        else {
120            return 0;
121        };
122        if position.y < bounds.top() {
123            return 0;
124        }
125        if position.y > bounds.bottom() {
126            return self.content.len();
127        }
128        line.closest_index_for_x(position.x - bounds.left())
129    }
130
131    fn select_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
132        if self.selection_reversed {
133            self.selected_range.start = offset
134        } else {
135            self.selected_range.end = offset
136        };
137        if self.selected_range.end < self.selected_range.start {
138            self.selection_reversed = !self.selection_reversed;
139            self.selected_range = self.selected_range.end..self.selected_range.start;
140        }
141        cx.notify()
142    }
143
144    fn offset_from_utf16(&self, offset: usize) -> usize {
145        let mut utf8_offset = 0;
146        let mut utf16_count = 0;
147
148        for ch in self.content.chars() {
149            if utf16_count >= offset {
150                break;
151            }
152            utf16_count += ch.len_utf16();
153            utf8_offset += ch.len_utf8();
154        }
155
156        utf8_offset
157    }
158
159    fn offset_to_utf16(&self, offset: usize) -> usize {
160        let mut utf16_offset = 0;
161        let mut utf8_count = 0;
162
163        for ch in self.content.chars() {
164            if utf8_count >= offset {
165                break;
166            }
167            utf8_count += ch.len_utf8();
168            utf16_offset += ch.len_utf16();
169        }
170
171        utf16_offset
172    }
173
174    fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
175        self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
176    }
177
178    fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
179        self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
180    }
181
182    fn previous_boundary(&self, offset: usize) -> usize {
183        self.content
184            .grapheme_indices(true)
185            .rev()
186            .find_map(|(idx, _)| (idx < offset).then_some(idx))
187            .unwrap_or(0)
188    }
189
190    fn next_boundary(&self, offset: usize) -> usize {
191        self.content
192            .grapheme_indices(true)
193            .find_map(|(idx, _)| (idx > offset).then_some(idx))
194            .unwrap_or(self.content.len())
195    }
196}
197
198impl ViewInputHandler for TextInput {
199    fn text_for_range(
200        &mut self,
201        range_utf16: Range<usize>,
202        _cx: &mut ViewContext<Self>,
203    ) -> Option<String> {
204        let range = self.range_from_utf16(&range_utf16);
205        Some(self.content[range].to_string())
206    }
207
208    fn selected_text_range(&mut self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
209        Some(self.range_to_utf16(&self.selected_range))
210    }
211
212    fn marked_text_range(&self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
213        self.marked_range
214            .as_ref()
215            .map(|range| self.range_to_utf16(range))
216    }
217
218    fn unmark_text(&mut self, _cx: &mut ViewContext<Self>) {
219        self.marked_range = None;
220    }
221
222    fn replace_text_in_range(
223        &mut self,
224        range_utf16: Option<Range<usize>>,
225        new_text: &str,
226        cx: &mut ViewContext<Self>,
227    ) {
228        let range = range_utf16
229            .as_ref()
230            .map(|range_utf16| self.range_from_utf16(range_utf16))
231            .or(self.marked_range.clone())
232            .unwrap_or(self.selected_range.clone());
233
234        self.content =
235            (self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
236                .into();
237        self.selected_range = range.start + new_text.len()..range.start + new_text.len();
238        self.marked_range.take();
239        cx.notify();
240    }
241
242    fn replace_and_mark_text_in_range(
243        &mut self,
244        range_utf16: Option<Range<usize>>,
245        new_text: &str,
246        new_selected_range_utf16: Option<Range<usize>>,
247        cx: &mut ViewContext<Self>,
248    ) {
249        let range = range_utf16
250            .as_ref()
251            .map(|range_utf16| self.range_from_utf16(range_utf16))
252            .or(self.marked_range.clone())
253            .unwrap_or(self.selected_range.clone());
254
255        self.content =
256            (self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
257                .into();
258        self.marked_range = Some(range.start..range.start + new_text.len());
259        self.selected_range = new_selected_range_utf16
260            .as_ref()
261            .map(|range_utf16| self.range_from_utf16(range_utf16))
262            .map(|new_range| new_range.start + range.start..new_range.end + range.end)
263            .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
264
265        cx.notify();
266    }
267
268    fn bounds_for_range(
269        &mut self,
270        range_utf16: Range<usize>,
271        bounds: Bounds<Pixels>,
272        _cx: &mut ViewContext<Self>,
273    ) -> Option<Bounds<Pixels>> {
274        let Some(last_layout) = self.last_layout.as_ref() else {
275            return None;
276        };
277        let range = self.range_from_utf16(&range_utf16);
278        Some(Bounds::from_corners(
279            point(
280                bounds.left() + last_layout.x_for_index(range.start),
281                bounds.top(),
282            ),
283            point(
284                bounds.left() + last_layout.x_for_index(range.end),
285                bounds.bottom(),
286            ),
287        ))
288    }
289}
290
291struct TextElement {
292    input: View<TextInput>,
293}
294
295struct PrepaintState {
296    line: Option<ShapedLine>,
297    cursor: Option<PaintQuad>,
298    selection: Option<PaintQuad>,
299}
300
301impl IntoElement for TextElement {
302    type Element = Self;
303
304    fn into_element(self) -> Self::Element {
305        self
306    }
307}
308
309impl Element for TextElement {
310    type RequestLayoutState = ();
311
312    type PrepaintState = PrepaintState;
313
314    fn id(&self) -> Option<ElementId> {
315        None
316    }
317
318    fn request_layout(
319        &mut self,
320        _id: Option<&GlobalElementId>,
321        cx: &mut WindowContext,
322    ) -> (LayoutId, Self::RequestLayoutState) {
323        let mut style = Style::default();
324        style.size.width = relative(1.).into();
325        style.size.height = cx.line_height().into();
326        (cx.request_layout(style, []), ())
327    }
328
329    fn prepaint(
330        &mut self,
331        _id: Option<&GlobalElementId>,
332        bounds: Bounds<Pixels>,
333        _request_layout: &mut Self::RequestLayoutState,
334        cx: &mut WindowContext,
335    ) -> Self::PrepaintState {
336        let input = self.input.read(cx);
337        let content = input.content.clone();
338        let selected_range = input.selected_range.clone();
339        let cursor = input.cursor_offset();
340        let style = cx.text_style();
341        let run = TextRun {
342            len: input.content.len(),
343            font: style.font(),
344            color: style.color,
345            background_color: None,
346            underline: None,
347            strikethrough: None,
348        };
349        let runs = if let Some(marked_range) = input.marked_range.as_ref() {
350            vec![
351                TextRun {
352                    len: marked_range.start,
353                    ..run.clone()
354                },
355                TextRun {
356                    len: marked_range.end - marked_range.start,
357                    underline: Some(UnderlineStyle {
358                        color: Some(run.color),
359                        thickness: px(1.0),
360                        wavy: false,
361                    }),
362                    ..run.clone()
363                },
364                TextRun {
365                    len: input.content.len() - marked_range.end,
366                    ..run.clone()
367                },
368            ]
369            .into_iter()
370            .filter(|run| run.len > 0)
371            .collect()
372        } else {
373            vec![run]
374        };
375
376        let font_size = style.font_size.to_pixels(cx.rem_size());
377        let line = cx
378            .text_system()
379            .shape_line(content, font_size, &runs)
380            .unwrap();
381
382        let cursor_pos = line.x_for_index(cursor);
383        let (selection, cursor) = if selected_range.is_empty() {
384            (
385                None,
386                Some(fill(
387                    Bounds::new(
388                        point(bounds.left() + cursor_pos, bounds.top()),
389                        size(px(2.), bounds.bottom() - bounds.top()),
390                    ),
391                    gpui::blue(),
392                )),
393            )
394        } else {
395            (
396                Some(fill(
397                    Bounds::from_corners(
398                        point(
399                            bounds.left() + line.x_for_index(selected_range.start),
400                            bounds.top(),
401                        ),
402                        point(
403                            bounds.left() + line.x_for_index(selected_range.end),
404                            bounds.bottom(),
405                        ),
406                    ),
407                    rgba(0x3311FF30),
408                )),
409                None,
410            )
411        };
412        PrepaintState {
413            line: Some(line),
414            cursor,
415            selection,
416        }
417    }
418
419    fn paint(
420        &mut self,
421        _id: Option<&GlobalElementId>,
422        bounds: Bounds<Pixels>,
423        _request_layout: &mut Self::RequestLayoutState,
424        prepaint: &mut Self::PrepaintState,
425        cx: &mut WindowContext,
426    ) {
427        let focus_handle = self.input.read(cx).focus_handle.clone();
428        cx.handle_input(
429            &focus_handle,
430            ElementInputHandler::new(bounds, self.input.clone()),
431        );
432        if let Some(selection) = prepaint.selection.take() {
433            cx.paint_quad(selection)
434        }
435        let line = prepaint.line.take().unwrap();
436        line.paint(bounds.origin, cx.line_height(), cx).unwrap();
437
438        if let Some(cursor) = prepaint.cursor.take() {
439            cx.paint_quad(cursor);
440        }
441        self.input.update(cx, |input, _cx| {
442            input.last_layout = Some(line);
443            input.last_bounds = Some(bounds);
444        });
445    }
446}
447
448impl Render for TextInput {
449    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
450        div()
451            .flex()
452            .key_context("TextInput")
453            .track_focus(&self.focus_handle)
454            .cursor(CursorStyle::IBeam)
455            .on_action(cx.listener(Self::backspace))
456            .on_action(cx.listener(Self::delete))
457            .on_action(cx.listener(Self::left))
458            .on_action(cx.listener(Self::right))
459            .on_action(cx.listener(Self::select_left))
460            .on_action(cx.listener(Self::select_right))
461            .on_action(cx.listener(Self::select_all))
462            .on_action(cx.listener(Self::home))
463            .on_action(cx.listener(Self::end))
464            .on_action(cx.listener(Self::show_character_palette))
465            .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
466            .on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
467            .on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
468            .on_mouse_move(cx.listener(Self::on_mouse_move))
469            .bg(rgb(0xeeeeee))
470            .size_full()
471            .line_height(px(30.))
472            .text_size(px(24.))
473            .child(
474                div()
475                    .h(px(30. + 4. * 2.))
476                    .w_full()
477                    .p(px(4.))
478                    .bg(white())
479                    .child(TextElement {
480                        input: cx.view().clone(),
481                    }),
482            )
483    }
484}
485
486impl FocusableView for TextInput {
487    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
488        self.focus_handle.clone()
489    }
490}
491
492struct InputExample {
493    text_input: View<TextInput>,
494    recent_keystrokes: Vec<Keystroke>,
495}
496
497impl Render for InputExample {
498    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
499        div()
500            .bg(rgb(0xaaaaaa))
501            .flex()
502            .flex_col()
503            .size_full()
504            .child(self.text_input.clone())
505            .children(self.recent_keystrokes.iter().rev().map(|ks| {
506                format!(
507                    "{:} {}",
508                    ks,
509                    if let Some(ime_key) = ks.ime_key.as_ref() {
510                        format!("-> {}", ime_key)
511                    } else {
512                        "".to_owned()
513                    }
514                )
515            }))
516    }
517}
518
519fn main() {
520    App::new().run(|cx: &mut AppContext| {
521        let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
522        cx.bind_keys([
523            KeyBinding::new("backspace", Backspace, None),
524            KeyBinding::new("delete", Delete, None),
525            KeyBinding::new("left", Left, None),
526            KeyBinding::new("right", Right, None),
527            KeyBinding::new("shift-left", SelectLeft, None),
528            KeyBinding::new("shift-right", SelectRight, None),
529            KeyBinding::new("cmd-a", SelectAll, None),
530            KeyBinding::new("home", Home, None),
531            KeyBinding::new("end", End, None),
532            KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None),
533        ]);
534        let window = cx
535            .open_window(
536                WindowOptions {
537                    window_bounds: Some(WindowBounds::Windowed(bounds)),
538                    ..Default::default()
539                },
540                |cx| {
541                    let text_input = cx.new_view(|cx| TextInput {
542                        focus_handle: cx.focus_handle(),
543                        content: "".into(),
544                        selected_range: 0..0,
545                        selection_reversed: false,
546                        marked_range: None,
547                        last_layout: None,
548                        last_bounds: None,
549                        is_selecting: false,
550                    });
551                    cx.new_view(|_| InputExample {
552                        text_input,
553                        recent_keystrokes: vec![],
554                    })
555                },
556            )
557            .unwrap();
558        cx.observe_keystrokes(move |ev, cx| {
559            window
560                .update(cx, |view, cx| {
561                    view.recent_keystrokes.push(ev.keystroke.clone());
562                    cx.notify();
563                })
564                .unwrap();
565        })
566        .detach();
567        window
568            .update(cx, |view, cx| {
569                cx.focus_view(&view.text_input);
570                cx.activate(true);
571            })
572            .unwrap();
573    });
574}