1use crate::clipboard;
3use crate::input_method;
4use crate::keyboard;
5use crate::keyboard::key;
6use crate::mouse;
7use crate::renderer;
8use crate::text::highlighter::{self, Highlighter};
9use crate::text::{self, Alignment, LineHeight, Position, Wrapping};
10use crate::time::{Duration, Instant};
11use crate::widget::operation::{Focusable, TextInput};
12use crate::window;
13use crate::{Color, Event, InputMethod, Padding, Pixels, Point, Rectangle, Size, SmolStr, Vector};
14
15use std::borrow::Cow;
16use std::sync::Arc;
17
18pub trait Editor: Sized + Default {
20 type Font: Copy + PartialEq + Default;
22
23 fn with_text(text: &str) -> Self;
25
26 fn is_empty(&self) -> bool;
28
29 fn cursor(&self) -> Cursor;
31
32 fn selection(&self) -> Selection;
34
35 fn copy(&self) -> Option<String>;
37
38 fn line(&self, index: usize) -> Option<Line<'_>>;
40
41 fn line_count(&self) -> usize;
43
44 fn perform(&mut self, action: Action);
46
47 fn move_to(&mut self, cursor: Cursor);
49
50 fn bounds(&self) -> Size;
52
53 fn min_bounds(&self) -> Size;
56
57 fn hint_factor(&self) -> Option<f32>;
59
60 fn update(
62 &mut self,
63 new_bounds: Size,
64 new_font: Self::Font,
65 new_size: Pixels,
66 new_line_height: LineHeight,
67 new_wrapping: Wrapping,
68 new_alignment: Alignment,
69 new_hint_factor: Option<f32>,
70 new_highlighter: &mut impl Highlighter,
71 );
72
73 fn overwrite(&mut self, new_text: &str);
75
76 fn highlight<H: Highlighter>(
78 &mut self,
79 font: Self::Font,
80 highlighter: &mut H,
81 format_highlight: impl Fn(&H::Highlight) -> highlighter::Format<Self::Font>,
82 );
83
84 fn lines(&self) -> impl Iterator<Item = Line<'_>> {
86 (0..)
87 .map(|i| self.line(i))
88 .take_while(Option::is_some)
89 .flatten()
90 }
91
92 fn text(&self) -> String {
94 let mut contents = String::new();
95 let mut lines = self.lines().peekable();
96
97 while let Some(line) = lines.next() {
98 contents.push_str(&line.text);
99
100 if lines.peek().is_some() {
101 contents.push_str(if line.ending == LineEnding::None {
102 LineEnding::default().as_str()
103 } else {
104 line.ending.as_str()
105 });
106 }
107 }
108
109 contents
110 }
111
112 fn font(&self) -> Self::Font;
114
115 fn text_size(&self) -> Pixels;
117
118 fn line_height(&self) -> LineHeight;
120}
121
122#[derive(Debug, Clone, PartialEq)]
124pub enum Action {
125 Move(Motion),
127 Select(Motion),
129 SelectWord,
131 SelectLine,
133 SelectAll,
135 Edit(Edit),
137 Click(Point, mouse::click::Kind),
139 Drag(Point),
141 Scroll {
143 lines: i32,
145 },
146}
147
148impl Action {
149 pub fn is_edit(&self) -> bool {
151 matches!(self, Self::Edit(_))
152 }
153}
154
155#[derive(Debug, Clone, PartialEq)]
157pub enum Edit {
158 Insert(char),
160 Paste(Arc<String>),
162 Enter,
164 Indent,
166 Unindent,
168 Backspace,
170 BackspaceWord,
172 BackspaceLine,
174 Delete,
176 DeleteWord,
178 DeleteLine,
180 Undo,
182 Redo,
184}
185
186#[derive(Debug, Clone, Copy, PartialEq)]
188pub enum Motion {
189 Left,
191 Right,
193 Up,
195 Down,
197 WordLeft,
199 WordRight,
201 Home,
203 End,
205 PageUp,
207 PageDown,
209 DocumentStart,
211 DocumentEnd,
213}
214
215impl Motion {
216 pub fn widen(self) -> Self {
218 match self {
219 Self::Left => Self::WordLeft,
220 Self::Right => Self::WordRight,
221 Self::Home => Self::DocumentStart,
222 Self::End => Self::DocumentEnd,
223 _ => self,
224 }
225 }
226
227 pub fn direction(&self) -> Direction {
229 match self {
230 Self::Left
231 | Self::Up
232 | Self::WordLeft
233 | Self::Home
234 | Self::PageUp
235 | Self::DocumentStart => Direction::Left,
236 Self::Right
237 | Self::Down
238 | Self::WordRight
239 | Self::End
240 | Self::PageDown
241 | Self::DocumentEnd => Direction::Right,
242 }
243 }
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
248pub enum Direction {
249 Left,
251 Right,
253}
254
255#[derive(Debug, Clone)]
257pub enum Selection {
258 Caret(Point),
260
261 Range(Vec<Rectangle>),
263}
264
265#[derive(Debug, Clone, Copy, PartialEq)]
267pub struct Cursor {
268 pub position: Position,
270
271 pub selection: Option<Position>,
273}
274
275#[derive(Clone, Debug, Default, Eq, PartialEq)]
277pub struct Line<'a> {
278 pub text: Cow<'a, str>,
280 pub ending: LineEnding,
282}
283
284#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
286pub enum LineEnding {
287 #[default]
289 Lf,
290 CrLf,
292 Cr,
294 LfCr,
296 None,
298}
299
300impl LineEnding {
301 pub fn as_str(self) -> &'static str {
303 match self {
304 Self::Lf => "\n",
305 Self::CrLf => "\r\n",
306 Self::Cr => "\r",
307 Self::LfCr => "\n\r",
308 Self::None => "",
309 }
310 }
311}
312
313#[derive(Debug, Clone, Default)]
315pub struct State {
316 focus: Option<Focus>,
317 preedit: Option<input_method::Preedit>,
318 last_click: Option<mouse::Click>,
319 is_dragging: bool,
320 partial_scroll: f32,
321}
322
323impl State {
324 pub fn new() -> Self {
326 Self::default()
327 }
328
329 pub fn update<Message>(
331 &mut self,
332 editor: &impl Editor,
333 event: &Event,
334 bounds: Rectangle,
335 padding: Padding,
336 cursor: mouse::Cursor,
337 key_binding: impl Fn(KeyPress) -> Option<Binding<Message>>,
338 ) -> Option<Update<Message>> {
339 match event {
340 Event::Window(window::Event::Unfocused) => {
341 if let Some(focus) = &mut self.focus {
342 focus.is_window_focused = false;
343 }
344
345 None
346 }
347 Event::Window(window::Event::Focused) => {
348 if let Some(focus) = &mut self.focus {
349 focus.is_window_focused = true;
350 focus.updated_at = Instant::now();
351 }
352
353 Some(Update::Focus)
354 }
355 Event::Window(window::Event::RedrawRequested(now)) => {
356 let focus = self.focus.as_mut()?;
357
358 if !focus.is_window_focused {
359 return None;
360 }
361
362 focus.now = *now;
363
364 let millis_until_redraw = Focus::CURSOR_BLINK_INTERVAL_MILLIS
365 - (focus.now - focus.updated_at).as_millis()
366 % Focus::CURSOR_BLINK_INTERVAL_MILLIS;
367
368 Some(Update::RedrawAt(
369 focus.now + Duration::from_millis(millis_until_redraw as u64),
370 ))
371 }
372 Event::Clipboard(clipboard::Event::Read(Ok(content))) => {
373 let focus = self.focus.as_ref()?;
374
375 if !focus.is_window_focused {
376 return None;
377 }
378
379 let clipboard::Content::Text(text) = content.as_ref() else {
380 return None;
381 };
382
383 Some(Update::Action(Action::Edit(Edit::Paste(Arc::new(
384 text.clone(),
385 )))))
386 }
387 Event::Mouse(event) => match event {
388 mouse::Event::ButtonPressed(mouse::Button::Left) => {
389 if let Some(cursor_position) = cursor.position_in(bounds) {
390 let cursor_position =
391 cursor_position - Vector::new(padding.left, padding.top);
392
393 let click = mouse::Click::new(
394 cursor_position,
395 mouse::Button::Left,
396 self.last_click,
397 );
398
399 self.focus = Some(Focus::now());
400 self.last_click = Some(click);
401 self.is_dragging = true;
402
403 Some(Update::Action(Action::Click(
404 click.position(),
405 click.kind(),
406 )))
407 } else if self.focus.is_some() {
408 self.focus = None;
409
410 Some(Update::Unfocus)
411 } else {
412 None
413 }
414 }
415 mouse::Event::ButtonReleased(mouse::Button::Left) => {
416 self.is_dragging = false;
417
418 Some(Update::Release)
419 }
420 mouse::Event::CursorMoved { .. } if self.is_dragging => {
421 let position =
422 cursor.position_in(bounds)? - Vector::new(padding.left, padding.top);
423
424 Some(Update::Action(Action::Drag(position)))
425 }
426 mouse::Event::WheelScrolled { delta } if cursor.is_over(bounds) => {
427 let bounds = editor.bounds();
428
429 if bounds.height >= i32::MAX as f32 {
430 return None;
431 }
432
433 let lines = match delta {
434 mouse::ScrollDelta::Lines { y, .. } => {
435 if y.abs() > 0.0 {
436 y.signum() * -(y.abs() * 4.0).max(1.0)
437 } else {
438 0.0
439 }
440 }
441 mouse::ScrollDelta::Pixels { y, .. } => -y / 4.0,
442 };
443
444 let lines = lines + self.partial_scroll;
445 self.partial_scroll = lines.fract();
446
447 Some(Update::Action(Action::Scroll {
448 lines: lines as i32,
449 }))
450 }
451 _ => None,
452 },
453 Event::InputMethod(event) => match event {
454 input_method::Event::Opened | input_method::Event::Closed => {
455 let is_open = matches!(event, input_method::Event::Opened);
456 self.preedit = is_open.then(input_method::Preedit::new);
457
458 Some(Update::InputMethod)
459 }
460 input_method::Event::Preedit(content, selection) if self.focus.is_some() => {
461 self.preedit = Some(input_method::Preedit {
462 content: content.clone(),
463 selection: selection.clone(),
464 text_size: Some(editor.text_size()),
465 });
466
467 Some(Update::InputMethod)
468 }
469 input_method::Event::Commit(content) if self.focus.is_some() => Some(
470 Update::Action(Action::Edit(Edit::Paste(Arc::new(content.clone())))),
471 ),
472 _ => None,
473 },
474 Event::Keyboard(keyboard::Event::KeyPressed {
475 key,
476 modified_key,
477 physical_key,
478 modifiers,
479 text,
480 ..
481 }) => {
482 let key_press = KeyPress {
483 key: key.clone(),
484 modified_key: modified_key.clone(),
485 physical_key: *physical_key,
486 modifiers: *modifiers,
487 text: text.clone(),
488 is_focused: self.is_focused(),
489 };
490
491 fn apply_binding<Message>(
492 binding: Binding<Message>,
493 editor: &impl Editor,
494 state: &mut State,
495 ) -> Option<Update<Message>> {
496 let action = |action| Update::Action(action);
497 let edit = |edit| action(Action::Edit(edit));
498
499 match binding {
500 Binding::Unfocus => {
501 state.focus = None;
502 state.is_dragging = false;
503
504 None
505 }
506 Binding::Copy => {
507 let selection = editor.copy()?;
508
509 Some(Update::Copy(selection))
510 }
511 Binding::Cut => {
512 let selection = editor.copy()?;
513
514 Some(Update::Sequence(vec![
515 Update::Copy(selection),
516 edit(Edit::Backspace),
517 ]))
518 }
519 Binding::Paste => Some(Update::Paste),
520 Binding::Undo => Some(edit(Edit::Undo)),
521 Binding::Redo => Some(edit(Edit::Redo)),
522 Binding::Move(motion) => Some(action(Action::Move(motion))),
523 Binding::Select(motion) => Some(action(Action::Select(motion))),
524 Binding::SelectWord => Some(action(Action::SelectWord)),
525 Binding::SelectLine => Some(action(Action::SelectLine)),
526 Binding::SelectAll => Some(action(Action::SelectAll)),
527 Binding::Insert(c) => Some(edit(Edit::Insert(c))),
528 Binding::Enter => Some(edit(Edit::Enter)),
529 Binding::Backspace => Some(edit(Edit::Backspace)),
530 Binding::BackspaceWord => Some(edit(Edit::BackspaceWord)),
531 Binding::BackspaceLine => Some(edit(Edit::BackspaceLine)),
532 Binding::Delete => Some(action(Action::Edit(Edit::Delete))),
533 Binding::DeleteWord => Some(edit(Edit::DeleteWord)),
534 Binding::DeleteLine => Some(edit(Edit::DeleteLine)),
535 Binding::Sequence(sequence) => {
536 let updates: Vec<_> = sequence
537 .into_iter()
538 .flat_map(|binding| apply_binding(binding, editor, state))
539 .collect();
540
541 if updates.is_empty() {
542 return None;
543 }
544
545 Some(Update::Sequence(updates))
546 }
547 Binding::Custom(message) => Some(Update::Custom(message)),
548 }
549 }
550
551 let update = apply_binding(key_binding(key_press)?, editor, self);
552
553 if let Some(focus) = &mut self.focus {
554 focus.updated_at = Instant::now();
555 }
556
557 update
558 }
559 _ => None,
560 }
561 }
562
563 pub fn input_method<'a>(
565 &'a self,
566 editor: &impl Editor,
567 position: Point,
568 ) -> InputMethod<&'a str> {
569 let Some(Focus {
570 is_window_focused: true,
571 ..
572 }) = &self.focus
573 else {
574 return InputMethod::Disabled;
575 };
576
577 let translation = position - Point::ORIGIN;
578
579 let cursor = match editor.selection() {
580 Selection::Caret(position) => position,
581 Selection::Range(ranges) => ranges.first().cloned().unwrap_or_default().position(),
582 };
583
584 let line_height = editor.line_height().to_absolute(editor.text_size());
585
586 let position = cursor + translation;
587
588 InputMethod::Enabled {
589 cursor: Rectangle::new(position, Size::new(1.0, f32::from(line_height))),
590 purpose: input_method::Purpose::Normal,
591 preedit: self.preedit.as_ref().map(input_method::Preedit::as_ref),
592 }
593 }
594
595 pub fn draw<Renderer: text::Renderer>(
597 &self,
598 editor: &Renderer::Editor,
599 renderer: &mut Renderer,
600 position: Point,
601 clip_bounds: Rectangle,
602 style: Style,
603 ) {
604 let bounds = Rectangle::new(position, editor.bounds());
605
606 let Some(clip_bounds) = clip_bounds.intersection(&bounds) else {
607 return;
608 };
609
610 if !editor.is_empty() {
611 renderer.fill_editor(editor, position, style.value, clip_bounds);
612 }
613
614 if !self.is_focused() {
615 return;
616 }
617
618 let translation = position - Point::ORIGIN;
619 let text_size = editor.text_size();
620 let line_height = editor.line_height();
621
622 match editor.selection() {
623 Selection::Caret(position) if self.is_cursor_visible() => {
624 let cursor = Rectangle::new(
625 position + translation,
626 Size::new(
627 if renderer::CRISP {
628 (1.0 / renderer.hint_factor().unwrap_or(1.0)).max(1.0)
629 } else {
630 1.0
631 },
632 line_height.to_absolute(text_size).into(),
633 ),
634 );
635
636 if let Some(clipped_cursor) = clip_bounds.intersection(&cursor) {
637 renderer.fill_quad(
638 renderer::Quad {
639 bounds: clipped_cursor,
640 ..renderer::Quad::default()
641 },
642 style.value,
643 );
644 }
645 }
646 Selection::Range(ranges) => {
647 for range in ranges
648 .into_iter()
649 .filter_map(|range| clip_bounds.intersection(&(range + translation)))
650 {
651 renderer.fill_quad(
652 renderer::Quad {
653 bounds: range.round(),
654 ..renderer::Quad::default()
655 },
656 style.selection,
657 );
658 }
659 }
660 Selection::Caret(_) => {
661 renderer.fill_quad(renderer::Quad::default(), Color::TRANSPARENT);
663 }
664 }
665 }
666
667 pub fn is_cursor_visible(&self) -> bool {
669 self.focus.as_ref().is_some_and(Focus::is_cursor_visible)
670 }
671}
672
673pub struct Style {
675 pub value: Color,
677
678 pub selection: Color,
680}
681
682#[derive(Debug, Clone)]
683struct Focus {
684 updated_at: Instant,
685 now: Instant,
686 is_window_focused: bool,
687}
688
689impl Focus {
690 const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
691
692 fn now() -> Self {
693 let now = Instant::now();
694
695 Self {
696 updated_at: now,
697 now,
698 is_window_focused: true,
699 }
700 }
701
702 fn is_cursor_visible(&self) -> bool {
703 self.is_window_focused
704 && ((self.now - self.updated_at).as_millis() / Self::CURSOR_BLINK_INTERVAL_MILLIS)
705 .is_multiple_of(2)
706 }
707}
708
709impl State {
710 pub fn is_focused(&self) -> bool {
712 self.focus.is_some()
713 }
714}
715
716impl Focusable for State {
717 fn is_focused(&self) -> bool {
718 self.focus.is_some()
719 }
720
721 fn focus(&mut self) {
722 self.focus = Some(Focus::now());
723 }
724
725 fn unfocus(&mut self) {
726 self.focus = None;
727 }
728}
729
730#[derive(Debug, Clone, PartialEq)]
732pub enum Binding<Message> {
733 Unfocus,
735 Copy,
737 Cut,
739 Paste,
741 Undo,
743 Redo,
745 Move(Motion),
747 Select(Motion),
749 SelectWord,
751 SelectLine,
753 SelectAll,
755 Insert(char),
757 Enter,
759 Backspace,
761 BackspaceWord,
763 BackspaceLine,
765 Delete,
767 DeleteWord,
769 DeleteLine,
771 Sequence(Vec<Self>),
773 Custom(Message),
775}
776
777#[derive(Debug, Clone, PartialEq, Eq)]
779pub struct KeyPress {
780 pub key: keyboard::Key,
784 pub modified_key: keyboard::Key,
788 pub physical_key: keyboard::key::Physical,
792 pub modifiers: keyboard::Modifiers,
794 pub text: Option<SmolStr>,
796 pub is_focused: bool,
798}
799
800impl<Message> Binding<Message> {
801 pub fn from_key_press(event: KeyPress) -> Option<Self> {
803 let KeyPress {
804 key,
805 modified_key,
806 physical_key,
807 modifiers,
808 text,
809 is_focused,
810 } = event;
811
812 if !is_focused {
813 return None;
814 }
815
816 let combination = match key.to_latin(physical_key) {
817 Some('c') if modifiers.command() => Some(Self::Copy),
818 Some('x') if modifiers.command() => Some(Self::Cut),
819 Some('v') if modifiers.command() && !modifiers.alt() => Some(Self::Paste),
820 Some('a') if modifiers.command() => Some(Self::SelectAll),
821 Some('z') if modifiers.command() => Some(Self::Undo),
822 Some('y') if modifiers.command() => Some(Self::Redo),
823 _ => None,
824 };
825
826 if let Some(binding) = combination {
827 return Some(binding);
828 }
829
830 #[cfg(target_os = "macos")]
831 let modified_key = convert_macos_shortcut(&key, modifiers).unwrap_or(modified_key);
832
833 match modified_key.as_ref() {
834 keyboard::Key::Named(key::Named::Enter) => Some(Self::Enter),
835 keyboard::Key::Named(key::Named::Backspace) => Some(if modifiers.command() {
836 if modifiers.shift() {
837 Self::BackspaceLine
838 } else {
839 Self::BackspaceWord
840 }
841 } else {
842 Self::Backspace
843 }),
844 keyboard::Key::Named(key::Named::Delete)
845 if text.is_none() || text.as_deref() == Some("\u{7f}") =>
846 {
847 Some(if modifiers.command() {
848 if modifiers.shift() {
849 Self::DeleteLine
850 } else {
851 Self::DeleteWord
852 }
853 } else {
854 Self::Delete
855 })
856 }
857 keyboard::Key::Named(key::Named::Escape) => Some(Self::Unfocus),
858 _ => {
859 if let Some(text) = text {
860 let c = text.chars().find(|c| !c.is_control())?;
861
862 Some(Self::Insert(c))
863 } else if let keyboard::Key::Named(named_key) = key.as_ref() {
864 let motion = motion(named_key)?;
865
866 let motion = if modifiers.macos_command() {
867 match motion {
868 Motion::Left => Motion::Home,
869 Motion::Right => Motion::End,
870 _ => motion,
871 }
872 } else {
873 motion
874 };
875
876 let motion = if modifiers.jump() {
877 motion.widen()
878 } else {
879 motion
880 };
881
882 Some(if modifiers.shift() {
883 Self::Select(motion)
884 } else {
885 Self::Move(motion)
886 })
887 } else {
888 None
889 }
890 }
891 }
892 }
893}
894
895pub enum Update<Message> {
897 Action(Action),
899 Focus,
901 Unfocus,
903 InputMethod,
905 Release,
907 Copy(String),
909 Paste,
911 RedrawAt(Instant),
913 Custom(Message),
915 Sequence(Vec<Update<Message>>),
917}
918
919fn motion(key: key::Named) -> Option<Motion> {
920 match key {
921 key::Named::ArrowLeft => Some(Motion::Left),
922 key::Named::ArrowRight => Some(Motion::Right),
923 key::Named::ArrowUp => Some(Motion::Up),
924 key::Named::ArrowDown => Some(Motion::Down),
925 key::Named::Home => Some(Motion::Home),
926 key::Named::End => Some(Motion::End),
927 key::Named::PageUp => Some(Motion::PageUp),
928 key::Named::PageDown => Some(Motion::PageDown),
929 _ => None,
930 }
931}
932
933#[cfg(target_os = "macos")]
934fn convert_macos_shortcut(
935 key: &keyboard::Key,
936 modifiers: keyboard::Modifiers,
937) -> Option<keyboard::Key> {
938 if modifiers != keyboard::Modifiers::CTRL {
939 return None;
940 }
941
942 let key = match key.as_ref() {
943 keyboard::Key::Character("b") => key::Named::ArrowLeft,
944 keyboard::Key::Character("f") => key::Named::ArrowRight,
945 keyboard::Key::Character("a") => key::Named::Home,
946 keyboard::Key::Character("e") => key::Named::End,
947 keyboard::Key::Character("h") => key::Named::Backspace,
948 keyboard::Key::Character("d") => key::Named::Delete,
949 _ => return None,
950 };
951
952 Some(keyboard::Key::Named(key))
953}
954
955impl<T: Editor> TextInput for T {
956 fn text(&self) -> text::Fragment<'_> {
957 text::Fragment::Owned(Editor::text(self))
958 }
959
960 fn move_cursor_to_front(&mut self) {
961 self.perform(Action::Move(Motion::DocumentStart));
962 }
963
964 fn move_cursor_to_end(&mut self) {
965 self.perform(Action::Move(Motion::DocumentEnd));
966 }
967
968 fn move_cursor_to(&mut self, position: text::Position) {
969 self.move_to(Cursor {
970 position,
971 selection: None,
972 });
973 }
974
975 fn select_all(&mut self) {
976 self.perform(Action::SelectAll);
977 }
978
979 fn select_range(&mut self, start: text::Position, end: text::Position) {
980 self.move_to(Cursor {
981 position: start,
982 selection: Some(end),
983 });
984 }
985}