1use crate::core::alignment;
35use crate::core::clipboard::{self, Clipboard};
36use crate::core::event::{self, Event};
37use crate::core::keyboard;
38use crate::core::keyboard::key;
39use crate::core::layout::{self, Layout};
40use crate::core::mouse;
41use crate::core::renderer;
42use crate::core::text::editor::{Cursor, Editor as _};
43use crate::core::text::highlighter::{self, Highlighter};
44use crate::core::text::{self, LineHeight, Text, Wrapping};
45use crate::core::time::{Duration, Instant};
46use crate::core::widget::operation;
47use crate::core::widget::{self, Widget};
48use crate::core::window;
49use crate::core::{
50 Background, Border, Color, Element, Length, Padding, Pixels, Point,
51 Rectangle, Shell, Size, SmolStr, Theme, Vector,
52};
53use crate::runtime::{task, Action as RuntimeAction, Task};
54
55use std::cell::RefCell;
56use std::fmt;
57use std::ops::DerefMut;
58use std::sync::Arc;
59
60pub use text::editor::{Action, Edit, Motion};
61
62#[derive(Debug, Clone, PartialEq, Eq, Hash)]
64pub struct Id(widget::Id);
65
66impl From<widget::Id> for Id {
67 fn from(value: widget::Id) -> Self {
68 Id(value)
69 }
70}
71
72#[allow(missing_debug_implementations)]
106pub struct TextEditor<
107 'a,
108 Highlighter,
109 Message,
110 Theme = crate::Theme,
111 Renderer = crate::Renderer,
112> where
113 Highlighter: text::Highlighter,
114 Theme: Catalog,
115 Renderer: text::Renderer,
116{
117 id: Option<Id>,
118 content: &'a Content<Renderer>,
119 placeholder: Option<text::Fragment<'a>>,
120 font: Option<Renderer::Font>,
121 text_size: Option<Pixels>,
122 line_height: LineHeight,
123 width: Length,
124 height: Length,
125 padding: Padding,
126 wrapping: Wrapping,
127 class: Theme::Class<'a>,
128 key_binding: Option<Box<dyn Fn(KeyPress) -> Option<Binding<Message>> + 'a>>,
129 on_edit: Option<Box<dyn Fn(Action) -> Message + 'a>>,
130 highlighter_settings: Highlighter::Settings,
131 highlighter_format: fn(
132 &Highlighter::Highlight,
133 &Theme,
134 ) -> highlighter::Format<Renderer::Font>,
135}
136
137impl<'a, Message, Theme, Renderer>
138 TextEditor<'a, highlighter::PlainText, Message, Theme, Renderer>
139where
140 Theme: Catalog,
141 Renderer: text::Renderer,
142{
143 pub fn new(content: &'a Content<Renderer>) -> Self {
145 Self {
146 id: None,
147 content,
148 placeholder: None,
149 font: None,
150 text_size: None,
151 line_height: LineHeight::default(),
152 width: Length::Fill,
153 height: Length::Shrink,
154 padding: Padding::new(5.0),
155 wrapping: Wrapping::default(),
156 class: Theme::default(),
157 key_binding: None,
158 on_edit: None,
159 highlighter_settings: (),
160 highlighter_format: |_highlight, _theme| {
161 highlighter::Format::default()
162 },
163 }
164 }
165
166 pub fn id(mut self, id: impl Into<Id>) -> Self {
167 self.id = Some(id.into());
168 self
169 }
170}
171
172pub fn focus<T>(id: impl Into<Id>) -> Task<T> {
174 task::effect(RuntimeAction::widget(operation::focusable::focus(
175 id.into().0,
176 )))
177}
178
179impl<'a, Highlighter, Message, Theme, Renderer>
180 TextEditor<'a, Highlighter, Message, Theme, Renderer>
181where
182 Highlighter: text::Highlighter,
183 Theme: Catalog,
184 Renderer: text::Renderer,
185{
186 pub fn placeholder(
188 mut self,
189 placeholder: impl text::IntoFragment<'a>,
190 ) -> Self {
191 self.placeholder = Some(placeholder.into_fragment());
192 self
193 }
194
195 pub fn height(mut self, height: impl Into<Length>) -> Self {
197 self.height = height.into();
198 self
199 }
200
201 pub fn width(mut self, width: impl Into<Pixels>) -> Self {
203 self.width = Length::from(width.into());
204 self
205 }
206
207 pub fn on_action(
212 mut self,
213 on_edit: impl Fn(Action) -> Message + 'a,
214 ) -> Self {
215 self.on_edit = Some(Box::new(on_edit));
216 self
217 }
218
219 pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
223 self.font = Some(font.into());
224 self
225 }
226
227 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
229 self.text_size = Some(size.into());
230 self
231 }
232
233 pub fn line_height(
235 mut self,
236 line_height: impl Into<text::LineHeight>,
237 ) -> Self {
238 self.line_height = line_height.into();
239 self
240 }
241
242 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
244 self.padding = padding.into();
245 self
246 }
247
248 pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
250 self.wrapping = wrapping;
251 self
252 }
253
254 #[cfg(feature = "highlighter")]
256 pub fn highlight(
257 self,
258 syntax: &str,
259 theme: iced_highlighter::Theme,
260 ) -> TextEditor<'a, iced_highlighter::Highlighter, Message, Theme, Renderer>
261 where
262 Renderer: text::Renderer<Font = crate::core::Font>,
263 {
264 self.highlight_with::<iced_highlighter::Highlighter>(
265 iced_highlighter::Settings {
266 theme,
267 token: syntax.to_owned(),
268 },
269 |highlight, _theme| highlight.to_format(),
270 )
271 }
272
273 pub fn highlight_with<H: text::Highlighter>(
276 self,
277 settings: H::Settings,
278 to_format: fn(
279 &H::Highlight,
280 &Theme,
281 ) -> highlighter::Format<Renderer::Font>,
282 ) -> TextEditor<'a, H, Message, Theme, Renderer> {
283 TextEditor {
284 id: self.id,
285 content: self.content,
286 placeholder: self.placeholder,
287 font: self.font,
288 text_size: self.text_size,
289 line_height: self.line_height,
290 width: self.width,
291 height: self.height,
292 padding: self.padding,
293 wrapping: self.wrapping,
294 class: self.class,
295 key_binding: self.key_binding,
296 on_edit: self.on_edit,
297 highlighter_settings: settings,
298 highlighter_format: to_format,
299 }
300 }
301
302 pub fn key_binding(
306 mut self,
307 key_binding: impl Fn(KeyPress) -> Option<Binding<Message>> + 'a,
308 ) -> Self {
309 self.key_binding = Some(Box::new(key_binding));
310 self
311 }
312
313 #[must_use]
315 pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
316 where
317 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
318 {
319 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
320 self
321 }
322
323 #[cfg(feature = "advanced")]
325 #[must_use]
326 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
327 self.class = class.into();
328 self
329 }
330}
331
332pub struct Content<R = crate::Renderer>(RefCell<Internal<R>>)
334where
335 R: text::Renderer;
336
337struct Internal<R>
338where
339 R: text::Renderer,
340{
341 editor: R::Editor,
342 is_dirty: bool,
343}
344
345impl<R> Content<R>
346where
347 R: text::Renderer,
348{
349 pub fn new() -> Self {
351 Self::with_text("")
352 }
353
354 pub fn with_text(text: &str) -> Self {
356 Self(RefCell::new(Internal {
357 editor: R::Editor::with_text(text),
358 is_dirty: true,
359 }))
360 }
361
362 pub fn perform(&mut self, action: Action) {
364 let internal = self.0.get_mut();
365
366 internal.editor.perform(action);
367 internal.is_dirty = true;
368 }
369
370 pub fn line_count(&self) -> usize {
372 self.0.borrow().editor.line_count()
373 }
374
375 pub fn line(
377 &self,
378 index: usize,
379 ) -> Option<impl std::ops::Deref<Target = str> + '_> {
380 std::cell::Ref::filter_map(self.0.borrow(), |internal| {
381 internal.editor.line(index)
382 })
383 .ok()
384 }
385
386 pub fn lines(
388 &self,
389 ) -> impl Iterator<Item = impl std::ops::Deref<Target = str> + '_> {
390 struct Lines<'a, Renderer: text::Renderer> {
391 internal: std::cell::Ref<'a, Internal<Renderer>>,
392 current: usize,
393 }
394
395 impl<'a, Renderer: text::Renderer> Iterator for Lines<'a, Renderer> {
396 type Item = std::cell::Ref<'a, str>;
397
398 fn next(&mut self) -> Option<Self::Item> {
399 let line = std::cell::Ref::filter_map(
400 std::cell::Ref::clone(&self.internal),
401 |internal| internal.editor.line(self.current),
402 )
403 .ok()?;
404
405 self.current += 1;
406
407 Some(line)
408 }
409 }
410
411 Lines {
412 internal: self.0.borrow(),
413 current: 0,
414 }
415 }
416
417 pub fn text(&self) -> String {
421 let mut text = self.lines().enumerate().fold(
422 String::new(),
423 |mut contents, (i, line)| {
424 if i > 0 {
425 contents.push('\n');
426 }
427
428 contents.push_str(&line);
429
430 contents
431 },
432 );
433
434 if !text.ends_with('\n') {
435 text.push('\n');
436 }
437
438 text
439 }
440
441 pub fn selection(&self) -> Option<String> {
443 self.0.borrow().editor.selection()
444 }
445
446 pub fn cursor_position(&self) -> (usize, usize) {
448 self.0.borrow().editor.cursor_position()
449 }
450}
451
452impl<Renderer> Default for Content<Renderer>
453where
454 Renderer: text::Renderer,
455{
456 fn default() -> Self {
457 Self::new()
458 }
459}
460
461impl<Renderer> fmt::Debug for Content<Renderer>
462where
463 Renderer: text::Renderer,
464 Renderer::Editor: fmt::Debug,
465{
466 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
467 let internal = self.0.borrow();
468
469 f.debug_struct("Content")
470 .field("editor", &internal.editor)
471 .field("is_dirty", &internal.is_dirty)
472 .finish()
473 }
474}
475
476#[derive(Debug)]
478pub struct State<Highlighter: text::Highlighter> {
479 focus: Option<Focus>,
480 last_click: Option<mouse::Click>,
481 drag_click: Option<mouse::click::Kind>,
482 highlighter: RefCell<Highlighter>,
483 highlighter_settings: Highlighter::Settings,
484 highlighter_format_address: usize,
485}
486
487#[derive(Debug, Clone, Copy)]
488struct Focus {
489 updated_at: Instant,
490 now: Instant,
491 is_window_focused: bool,
492}
493
494impl Focus {
495 const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
496
497 fn now() -> Self {
498 let now = Instant::now();
499
500 Self {
501 updated_at: now,
502 now,
503 is_window_focused: true,
504 }
505 }
506
507 fn is_cursor_visible(&self) -> bool {
508 self.is_window_focused
509 && ((self.now - self.updated_at).as_millis()
510 / Self::CURSOR_BLINK_INTERVAL_MILLIS)
511 % 2
512 == 0
513 }
514}
515
516impl<Highlighter: text::Highlighter> State<Highlighter> {
517 pub fn is_focused(&self) -> bool {
519 self.focus.is_some()
520 }
521}
522
523impl<Highlighter: text::Highlighter> operation::Focusable
524 for State<Highlighter>
525{
526 fn is_focused(&self) -> bool {
527 self.focus.is_some()
528 }
529
530 fn focus(&mut self) {
531 self.focus = Some(Focus::now());
532 }
533
534 fn unfocus(&mut self) {
535 self.focus = None;
536 }
537}
538
539impl<'a, Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
540 for TextEditor<'a, Highlighter, Message, Theme, Renderer>
541where
542 Highlighter: text::Highlighter,
543 Theme: Catalog,
544 Renderer: text::Renderer,
545{
546 fn tag(&self) -> widget::tree::Tag {
547 widget::tree::Tag::of::<State<Highlighter>>()
548 }
549
550 fn state(&self) -> widget::tree::State {
551 widget::tree::State::new(State {
552 focus: None,
553 last_click: None,
554 drag_click: None,
555 highlighter: RefCell::new(Highlighter::new(
556 &self.highlighter_settings,
557 )),
558 highlighter_settings: self.highlighter_settings.clone(),
559 highlighter_format_address: self.highlighter_format as usize,
560 })
561 }
562
563 fn size(&self) -> Size<Length> {
564 Size {
565 width: self.width,
566 height: self.height,
567 }
568 }
569
570 fn layout(
571 &self,
572 tree: &mut widget::Tree,
573 renderer: &Renderer,
574 limits: &layout::Limits,
575 ) -> iced_renderer::core::layout::Node {
576 let mut internal = self.content.0.borrow_mut();
577 let state = tree.state.downcast_mut::<State<Highlighter>>();
578
579 if state.highlighter_format_address != self.highlighter_format as usize
580 {
581 state.highlighter.borrow_mut().change_line(0);
582
583 state.highlighter_format_address = self.highlighter_format as usize;
584 }
585
586 if state.highlighter_settings != self.highlighter_settings {
587 state
588 .highlighter
589 .borrow_mut()
590 .update(&self.highlighter_settings);
591
592 state.highlighter_settings = self.highlighter_settings.clone();
593 }
594
595 let limits = limits.width(self.width).height(self.height);
596
597 internal.editor.update(
598 limits.shrink(self.padding).max(),
599 self.font.unwrap_or_else(|| renderer.default_font()),
600 self.text_size.unwrap_or_else(|| renderer.default_size()),
601 self.line_height,
602 self.wrapping,
603 state.highlighter.borrow_mut().deref_mut(),
604 );
605
606 match self.height {
607 Length::Fill | Length::FillPortion(_) | Length::Fixed(_) => {
608 layout::Node::new(limits.max())
609 }
610 Length::Shrink => {
611 let min_bounds = internal.editor.min_bounds();
612
613 layout::Node::new(
614 limits
615 .height(min_bounds.height)
616 .max()
617 .expand(Size::new(0.0, self.padding.vertical())),
618 )
619 }
620 }
621 }
622
623 fn on_event(
624 &mut self,
625 tree: &mut widget::Tree,
626 event: Event,
627 layout: Layout<'_>,
628 cursor: mouse::Cursor,
629 _renderer: &Renderer,
630 clipboard: &mut dyn Clipboard,
631 shell: &mut Shell<'_, Message>,
632 _viewport: &Rectangle,
633 ) -> event::Status {
634 let Some(on_edit) = self.on_edit.as_ref() else {
635 return event::Status::Ignored;
636 };
637
638 let state = tree.state.downcast_mut::<State<Highlighter>>();
639
640 match event {
641 Event::Window(window::Event::Unfocused) => {
642 if let Some(focus) = &mut state.focus {
643 focus.is_window_focused = false;
644 }
645 }
646 Event::Window(window::Event::Focused) => {
647 if let Some(focus) = &mut state.focus {
648 focus.is_window_focused = true;
649 focus.updated_at = Instant::now();
650
651 shell.request_redraw(window::RedrawRequest::NextFrame);
652 }
653 }
654 Event::Window(window::Event::RedrawRequested(now)) => {
655 if let Some(focus) = &mut state.focus {
656 if focus.is_window_focused {
657 focus.now = now;
658
659 let millis_until_redraw =
660 Focus::CURSOR_BLINK_INTERVAL_MILLIS
661 - (now - focus.updated_at).as_millis()
662 % Focus::CURSOR_BLINK_INTERVAL_MILLIS;
663
664 shell.request_redraw(window::RedrawRequest::At(
665 now + Duration::from_millis(
666 millis_until_redraw as u64,
667 ),
668 ));
669 }
670 }
671 }
672 _ => {}
673 }
674
675 let Some(update) = Update::from_event(
676 event,
677 state,
678 layout.bounds(),
679 self.padding,
680 cursor,
681 self.key_binding.as_deref(),
682 ) else {
683 return event::Status::Ignored;
684 };
685
686 match update {
687 Update::Click(click) => {
688 let action = match click.kind() {
689 mouse::click::Kind::Single => {
690 Action::Click(click.position())
691 }
692 mouse::click::Kind::Double => Action::SelectWord,
693 mouse::click::Kind::Triple => Action::SelectLine,
694 };
695
696 state.focus = Some(Focus::now());
697 state.last_click = Some(click);
698 state.drag_click = Some(click.kind());
699
700 shell.publish(on_edit(action));
701 }
702 Update::Drag(position) => {
703 shell.publish(on_edit(Action::Drag(position)));
704 }
705 Update::Release => {
706 state.drag_click = None;
707 }
708 Update::Scroll(lines) => {
709 let bounds = self.content.0.borrow().editor.bounds();
710
711 if bounds.height >= i32::MAX as f32 {
712 return event::Status::Ignored;
713 }
714
715 shell.publish(on_edit(Action::Scroll {
716 pixels: lines * 4.0,
718 }));
719 }
720 Update::Binding(binding) => {
721 fn apply_binding<
722 H: text::Highlighter,
723 R: text::Renderer,
724 Message,
725 >(
726 binding: Binding<Message>,
727 content: &Content<R>,
728 state: &mut State<H>,
729 on_edit: &dyn Fn(Action) -> Message,
730 clipboard: &mut dyn Clipboard,
731 shell: &mut Shell<'_, Message>,
732 ) {
733 let mut publish = |action| shell.publish(on_edit(action));
734
735 match binding {
736 Binding::Unfocus => {
737 state.focus = None;
738 state.drag_click = None;
739 }
740 Binding::Copy => {
741 if let Some(selection) = content.selection() {
742 clipboard.write(
743 clipboard::Kind::Standard,
744 selection,
745 );
746 }
747 }
748 Binding::Cut => {
749 if let Some(selection) = content.selection() {
750 clipboard.write(
751 clipboard::Kind::Standard,
752 selection,
753 );
754
755 publish(Action::Edit(Edit::Delete));
756 }
757 }
758 Binding::Paste => {
759 if let Some(contents) =
760 clipboard.read(clipboard::Kind::Standard)
761 {
762 publish(Action::Edit(Edit::Paste(Arc::new(
763 contents,
764 ))));
765 }
766 }
767 Binding::Move(motion) => {
768 publish(Action::Move(motion));
769 }
770 Binding::Select(motion) => {
771 publish(Action::Select(motion));
772 }
773 Binding::SelectWord => {
774 publish(Action::SelectWord);
775 }
776 Binding::SelectLine => {
777 publish(Action::SelectLine);
778 }
779 Binding::SelectAll => {
780 publish(Action::SelectAll);
781 }
782 Binding::Insert(c) => {
783 publish(Action::Edit(Edit::Insert(c)));
784 }
785 Binding::Enter => {
786 publish(Action::Edit(Edit::Enter));
787 }
788 Binding::Backspace => {
789 publish(Action::Edit(Edit::Backspace));
790 }
791 Binding::Delete => {
792 publish(Action::Edit(Edit::Delete));
793 }
794 Binding::Sequence(sequence) => {
795 for binding in sequence {
796 apply_binding(
797 binding, content, state, on_edit,
798 clipboard, shell,
799 );
800 }
801 }
802 Binding::Custom(message) => {
803 shell.publish(message);
804 }
805 }
806 }
807
808 apply_binding(
809 binding,
810 self.content,
811 state,
812 on_edit,
813 clipboard,
814 shell,
815 );
816
817 if let Some(focus) = &mut state.focus {
818 focus.updated_at = Instant::now();
819 }
820 }
821 }
822
823 event::Status::Captured
824 }
825
826 fn draw(
827 &self,
828 tree: &widget::Tree,
829 renderer: &mut Renderer,
830 theme: &Theme,
831 _defaults: &renderer::Style,
832 layout: Layout<'_>,
833 cursor: mouse::Cursor,
834 _viewport: &Rectangle,
835 ) {
836 let bounds = layout.bounds();
837
838 let mut internal = self.content.0.borrow_mut();
839 let state = tree.state.downcast_ref::<State<Highlighter>>();
840
841 let font = self.font.unwrap_or_else(|| renderer.default_font());
842
843 internal.editor.highlight(
844 font,
845 state.highlighter.borrow_mut().deref_mut(),
846 |highlight| (self.highlighter_format)(highlight, theme),
847 );
848
849 let is_disabled = self.on_edit.is_none();
850 let is_mouse_over = cursor.is_over(bounds);
851
852 let status = if is_disabled {
853 Status::Disabled
854 } else if state.focus.is_some() {
855 Status::Focused
856 } else if is_mouse_over {
857 Status::Hovered
858 } else {
859 Status::Active
860 };
861
862 let style = theme.style(&self.class, status);
863
864 renderer.fill_quad(
865 renderer::Quad {
866 bounds,
867 border: style.border,
868 ..renderer::Quad::default()
869 },
870 style.background,
871 );
872
873 let text_bounds = bounds.shrink(self.padding);
874
875 if internal.editor.is_empty() {
876 if let Some(placeholder) = self.placeholder.clone() {
877 renderer.fill_text(
878 Text {
879 content: placeholder.into_owned(),
880 bounds: text_bounds.size(),
881 size: self
882 .text_size
883 .unwrap_or_else(|| renderer.default_size()),
884 line_height: self.line_height,
885 font,
886 horizontal_alignment: alignment::Horizontal::Left,
887 vertical_alignment: alignment::Vertical::Top,
888 shaping: text::Shaping::Advanced,
889 wrapping: self.wrapping,
890 },
891 text_bounds.position(),
892 style.placeholder,
893 text_bounds,
894 );
895 }
896 } else {
897 renderer.fill_editor(
898 &internal.editor,
899 text_bounds.position(),
900 style.value,
901 text_bounds,
902 );
903 }
904
905 let translation = text_bounds.position() - Point::ORIGIN;
906
907 if let Some(focus) = state.focus.as_ref() {
908 match internal.editor.cursor() {
909 Cursor::Caret(position) if focus.is_cursor_visible() => {
910 let cursor =
911 Rectangle::new(
912 position + translation,
913 Size::new(
914 1.0,
915 self.line_height
916 .to_absolute(self.text_size.unwrap_or_else(
917 || renderer.default_size(),
918 ))
919 .into(),
920 ),
921 );
922
923 if let Some(clipped_cursor) =
924 text_bounds.intersection(&cursor)
925 {
926 renderer.fill_quad(
927 renderer::Quad {
928 bounds: clipped_cursor,
929 ..renderer::Quad::default()
930 },
931 style.value,
932 );
933 }
934 }
935 Cursor::Selection(ranges) => {
936 for range in ranges.into_iter().filter_map(|range| {
937 text_bounds.intersection(&(range + translation))
938 }) {
939 renderer.fill_quad(
940 renderer::Quad {
941 bounds: range,
942 ..renderer::Quad::default()
943 },
944 style.selection,
945 );
946 }
947 }
948 Cursor::Caret(_) => {}
949 }
950 }
951 }
952
953 fn mouse_interaction(
954 &self,
955 _state: &widget::Tree,
956 layout: Layout<'_>,
957 cursor: mouse::Cursor,
958 _viewport: &Rectangle,
959 _renderer: &Renderer,
960 ) -> mouse::Interaction {
961 let is_disabled = self.on_edit.is_none();
962
963 if cursor.is_over(layout.bounds()) {
964 if is_disabled {
965 mouse::Interaction::NotAllowed
966 } else {
967 mouse::Interaction::Text
968 }
969 } else {
970 mouse::Interaction::default()
971 }
972 }
973
974 fn operate(
975 &self,
976 tree: &mut widget::Tree,
977 _layout: Layout<'_>,
978 _renderer: &Renderer,
979 operation: &mut dyn widget::Operation,
980 ) {
981 let state = tree.state.downcast_mut::<State<Highlighter>>();
982
983 operation.focusable(state, self.id.as_ref().map(|id| &id.0));
984 }
985
986 fn id(&self) -> Option<widget::Id> {
987 self.id.as_ref().map(|id| id.0.clone())
988 }
989 fn set_id(&mut self, id: widget::Id) {
990 self.id = Some(Id(id));
991 }
992}
993
994impl<'a, Highlighter, Message, Theme, Renderer>
995 From<TextEditor<'a, Highlighter, Message, Theme, Renderer>>
996 for Element<'a, Message, Theme, Renderer>
997where
998 Highlighter: text::Highlighter,
999 Message: 'a,
1000 Theme: Catalog + 'a,
1001 Renderer: text::Renderer,
1002{
1003 fn from(
1004 text_editor: TextEditor<'a, Highlighter, Message, Theme, Renderer>,
1005 ) -> Self {
1006 Self::new(text_editor)
1007 }
1008}
1009
1010#[derive(Debug, Clone, PartialEq)]
1012pub enum Binding<Message> {
1013 Unfocus,
1015 Copy,
1017 Cut,
1019 Paste,
1021 Move(Motion),
1023 Select(Motion),
1025 SelectWord,
1027 SelectLine,
1029 SelectAll,
1031 Insert(char),
1033 Enter,
1035 Backspace,
1037 Delete,
1039 Sequence(Vec<Self>),
1041 Custom(Message),
1043}
1044
1045#[derive(Debug, Clone, PartialEq, Eq)]
1047pub struct KeyPress {
1048 pub key: keyboard::Key,
1050 pub modifiers: keyboard::Modifiers,
1052 pub text: Option<SmolStr>,
1054 pub status: Status,
1056}
1057
1058impl<Message> Binding<Message> {
1059 pub fn from_key_press(event: KeyPress) -> Option<Self> {
1061 let KeyPress {
1062 key,
1063 modifiers,
1064 text,
1065 status,
1066 } = event;
1067
1068 if status != Status::Focused {
1069 return None;
1070 }
1071
1072 match key.as_ref() {
1073 keyboard::Key::Named(key::Named::Enter) => Some(Self::Enter),
1074 keyboard::Key::Named(key::Named::Backspace) => {
1075 Some(Self::Backspace)
1076 }
1077 keyboard::Key::Named(key::Named::Delete)
1078 if text.is_none() || text.as_deref() == Some("\u{7f}") =>
1079 {
1080 Some(Self::Delete)
1081 }
1082 keyboard::Key::Named(key::Named::Escape) => Some(Self::Unfocus),
1083 keyboard::Key::Character("c") if modifiers.command() => {
1084 Some(Self::Copy)
1085 }
1086 keyboard::Key::Character("x") if modifiers.command() => {
1087 Some(Self::Cut)
1088 }
1089 keyboard::Key::Character("v")
1090 if modifiers.command() && !modifiers.alt() =>
1091 {
1092 Some(Self::Paste)
1093 }
1094 keyboard::Key::Character("a") if modifiers.command() => {
1095 Some(Self::SelectAll)
1096 }
1097 _ => {
1098 if let Some(text) = text {
1099 let c = text.chars().find(|c| !c.is_control())?;
1100
1101 Some(Self::Insert(c))
1102 } else if let keyboard::Key::Named(named_key) = key.as_ref() {
1103 let motion = motion(named_key)?;
1104
1105 let motion = if modifiers.macos_command() {
1106 match motion {
1107 Motion::Left => Motion::Home,
1108 Motion::Right => Motion::End,
1109 _ => motion,
1110 }
1111 } else {
1112 motion
1113 };
1114
1115 let motion = if modifiers.jump() {
1116 motion.widen()
1117 } else {
1118 motion
1119 };
1120
1121 Some(if modifiers.shift() {
1122 Self::Select(motion)
1123 } else {
1124 Self::Move(motion)
1125 })
1126 } else {
1127 None
1128 }
1129 }
1130 }
1131 }
1132}
1133
1134enum Update<Message> {
1135 Click(mouse::Click),
1136 Drag(Point),
1137 Release,
1138 Scroll(f32),
1139 Binding(Binding<Message>),
1140}
1141
1142impl<Message> Update<Message> {
1143 fn from_event<H: Highlighter>(
1144 event: Event,
1145 state: &State<H>,
1146 bounds: Rectangle,
1147 padding: Padding,
1148 cursor: mouse::Cursor,
1149 key_binding: Option<&dyn Fn(KeyPress) -> Option<Binding<Message>>>,
1150 ) -> Option<Self> {
1151 let binding = |binding| Some(Update::Binding(binding));
1152
1153 match event {
1154 Event::Mouse(event) => match event {
1155 mouse::Event::ButtonPressed(mouse::Button::Left) => {
1156 if let Some(cursor_position) = cursor.position_in(bounds) {
1157 let cursor_position = cursor_position
1158 - Vector::new(padding.top, padding.left);
1159
1160 let click = mouse::Click::new(
1161 cursor_position,
1162 mouse::Button::Left,
1163 state.last_click,
1164 );
1165
1166 Some(Update::Click(click))
1167 } else if state.focus.is_some() {
1168 binding(Binding::Unfocus)
1169 } else {
1170 None
1171 }
1172 }
1173 mouse::Event::ButtonReleased(mouse::Button::Left) => {
1174 Some(Update::Release)
1175 }
1176 mouse::Event::CursorMoved { .. } => match state.drag_click {
1177 Some(mouse::click::Kind::Single) => {
1178 let cursor_position = cursor.position_in(bounds)?
1179 - Vector::new(padding.top, padding.left);
1180
1181 Some(Update::Drag(cursor_position))
1182 }
1183 _ => None,
1184 },
1185 mouse::Event::WheelScrolled { delta }
1186 if cursor.is_over(bounds) =>
1187 {
1188 Some(Update::Scroll(match delta {
1189 mouse::ScrollDelta::Lines { y, .. } => {
1190 if y.abs() > 0.0 {
1191 y.signum() * -(y.abs() * 4.0).max(1.0)
1192 } else {
1193 0.0
1194 }
1195 }
1196 mouse::ScrollDelta::Pixels { y, .. } => -y / 4.0,
1197 }))
1198 }
1199 _ => None,
1200 },
1201 Event::Keyboard(keyboard::Event::KeyPressed {
1202 key,
1203 modifiers,
1204 text,
1205 ..
1206 }) => {
1207 let status = if state.focus.is_some() {
1208 Status::Focused
1209 } else {
1210 Status::Active
1211 };
1212
1213 let key_press = KeyPress {
1214 key,
1215 modifiers,
1216 text,
1217 status,
1218 };
1219
1220 if let Some(key_binding) = key_binding {
1221 key_binding(key_press)
1222 } else {
1223 Binding::from_key_press(key_press)
1224 }
1225 .map(Self::Binding)
1226 }
1227 _ => None,
1228 }
1229 }
1230}
1231
1232fn motion(key: key::Named) -> Option<Motion> {
1233 match key {
1234 key::Named::ArrowLeft => Some(Motion::Left),
1235 key::Named::ArrowRight => Some(Motion::Right),
1236 key::Named::ArrowUp => Some(Motion::Up),
1237 key::Named::ArrowDown => Some(Motion::Down),
1238 key::Named::Home => Some(Motion::Home),
1239 key::Named::End => Some(Motion::End),
1240 key::Named::PageUp => Some(Motion::PageUp),
1241 key::Named::PageDown => Some(Motion::PageDown),
1242 _ => None,
1243 }
1244}
1245
1246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1248pub enum Status {
1249 Active,
1251 Hovered,
1253 Focused,
1255 Disabled,
1257}
1258
1259#[derive(Debug, Clone, Copy, PartialEq)]
1261pub struct Style {
1262 pub background: Background,
1264 pub border: Border,
1266 pub icon: Color,
1268 pub placeholder: Color,
1270 pub value: Color,
1272 pub selection: Color,
1274}
1275
1276pub trait Catalog {
1278 type Class<'a>;
1280
1281 fn default<'a>() -> Self::Class<'a>;
1283
1284 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
1286}
1287
1288pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
1290
1291impl Catalog for Theme {
1292 type Class<'a> = StyleFn<'a, Self>;
1293
1294 fn default<'a>() -> Self::Class<'a> {
1295 Box::new(default)
1296 }
1297
1298 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
1299 class(self, status)
1300 }
1301}
1302
1303pub fn default(theme: &Theme, status: Status) -> Style {
1305 let palette = theme.extended_palette();
1306
1307 let active = Style {
1308 background: Background::Color(palette.background.base.color),
1309 border: Border {
1310 radius: 2.0.into(),
1311 width: 1.0,
1312 color: palette.background.strong.color,
1313 },
1314 icon: palette.background.weak.text,
1315 placeholder: palette.background.strong.color,
1316 value: palette.background.base.text,
1317 selection: palette.primary.weak.color,
1318 };
1319
1320 match status {
1321 Status::Active => active,
1322 Status::Hovered => Style {
1323 border: Border {
1324 color: palette.background.base.text,
1325 ..active.border
1326 },
1327 ..active
1328 },
1329 Status::Focused => Style {
1330 border: Border {
1331 color: palette.primary.strong.color,
1332 ..active.border
1333 },
1334 ..active
1335 },
1336 Status::Disabled => Style {
1337 background: Background::Color(palette.background.weak.color),
1338 value: active.placeholder,
1339 ..active
1340 },
1341 }
1342}