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 partial_scroll: f32,
483 highlighter: RefCell<Highlighter>,
484 highlighter_settings: Highlighter::Settings,
485 highlighter_format_address: usize,
486}
487
488#[derive(Debug, Clone, Copy)]
489struct Focus {
490 updated_at: Instant,
491 now: Instant,
492 is_window_focused: bool,
493}
494
495impl Focus {
496 const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
497
498 fn now() -> Self {
499 let now = Instant::now();
500
501 Self {
502 updated_at: now,
503 now,
504 is_window_focused: true,
505 }
506 }
507
508 fn is_cursor_visible(&self) -> bool {
509 self.is_window_focused
510 && ((self.now - self.updated_at).as_millis()
511 / Self::CURSOR_BLINK_INTERVAL_MILLIS)
512 % 2
513 == 0
514 }
515}
516
517impl<Highlighter: text::Highlighter> State<Highlighter> {
518 pub fn is_focused(&self) -> bool {
520 self.focus.is_some()
521 }
522}
523
524impl<Highlighter: text::Highlighter> operation::Focusable
525 for State<Highlighter>
526{
527 fn is_focused(&self) -> bool {
528 self.focus.is_some()
529 }
530
531 fn focus(&mut self) {
532 self.focus = Some(Focus::now());
533 }
534
535 fn unfocus(&mut self) {
536 self.focus = None;
537 }
538}
539
540impl<'a, Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
541 for TextEditor<'a, Highlighter, Message, Theme, Renderer>
542where
543 Highlighter: text::Highlighter,
544 Theme: Catalog,
545 Renderer: text::Renderer,
546{
547 fn tag(&self) -> widget::tree::Tag {
548 widget::tree::Tag::of::<State<Highlighter>>()
549 }
550
551 fn state(&self) -> widget::tree::State {
552 widget::tree::State::new(State {
553 focus: None,
554 last_click: None,
555 drag_click: None,
556 partial_scroll: 0.0,
557 highlighter: RefCell::new(Highlighter::new(
558 &self.highlighter_settings,
559 )),
560 highlighter_settings: self.highlighter_settings.clone(),
561 highlighter_format_address: self.highlighter_format as usize,
562 })
563 }
564
565 fn size(&self) -> Size<Length> {
566 Size {
567 width: self.width,
568 height: self.height,
569 }
570 }
571
572 fn layout(
573 &self,
574 tree: &mut widget::Tree,
575 renderer: &Renderer,
576 limits: &layout::Limits,
577 ) -> iced_renderer::core::layout::Node {
578 let mut internal = self.content.0.borrow_mut();
579 let state = tree.state.downcast_mut::<State<Highlighter>>();
580
581 if state.highlighter_format_address != self.highlighter_format as usize
582 {
583 state.highlighter.borrow_mut().change_line(0);
584
585 state.highlighter_format_address = self.highlighter_format as usize;
586 }
587
588 if state.highlighter_settings != self.highlighter_settings {
589 state
590 .highlighter
591 .borrow_mut()
592 .update(&self.highlighter_settings);
593
594 state.highlighter_settings = self.highlighter_settings.clone();
595 }
596
597 let limits = limits.width(self.width).height(self.height);
598
599 internal.editor.update(
600 limits.shrink(self.padding).max(),
601 self.font.unwrap_or_else(|| renderer.default_font()),
602 self.text_size.unwrap_or_else(|| renderer.default_size()),
603 self.line_height,
604 self.wrapping,
605 state.highlighter.borrow_mut().deref_mut(),
606 );
607
608 match self.height {
609 Length::Fill | Length::FillPortion(_) | Length::Fixed(_) => {
610 layout::Node::new(limits.max())
611 }
612 Length::Shrink => {
613 let min_bounds = internal.editor.min_bounds();
614
615 layout::Node::new(
616 limits
617 .height(min_bounds.height)
618 .max()
619 .expand(Size::new(0.0, self.padding.vertical())),
620 )
621 }
622 }
623 }
624
625 fn on_event(
626 &mut self,
627 tree: &mut widget::Tree,
628 event: Event,
629 layout: Layout<'_>,
630 cursor: mouse::Cursor,
631 _renderer: &Renderer,
632 clipboard: &mut dyn Clipboard,
633 shell: &mut Shell<'_, Message>,
634 _viewport: &Rectangle,
635 ) -> event::Status {
636 let Some(on_edit) = self.on_edit.as_ref() else {
637 return event::Status::Ignored;
638 };
639
640 let state = tree.state.downcast_mut::<State<Highlighter>>();
641
642 match event {
643 Event::Window(window::Event::Unfocused) => {
644 if let Some(focus) = &mut state.focus {
645 focus.is_window_focused = false;
646 }
647 }
648 Event::Window(window::Event::Focused) => {
649 if let Some(focus) = &mut state.focus {
650 focus.is_window_focused = true;
651 focus.updated_at = Instant::now();
652
653 shell.request_redraw(window::RedrawRequest::NextFrame);
654 }
655 }
656 Event::Window(window::Event::RedrawRequested(now)) => {
657 if let Some(focus) = &mut state.focus {
658 if focus.is_window_focused {
659 focus.now = now;
660
661 let millis_until_redraw =
662 Focus::CURSOR_BLINK_INTERVAL_MILLIS
663 - (now - focus.updated_at).as_millis()
664 % Focus::CURSOR_BLINK_INTERVAL_MILLIS;
665
666 shell.request_redraw(window::RedrawRequest::At(
667 now + Duration::from_millis(
668 millis_until_redraw as u64,
669 ),
670 ));
671 }
672 }
673 }
674 _ => {}
675 }
676
677 let Some(update) = Update::from_event(
678 event,
679 state,
680 layout.bounds(),
681 self.padding,
682 cursor,
683 self.key_binding.as_deref(),
684 ) else {
685 return event::Status::Ignored;
686 };
687
688 match update {
689 Update::Click(click) => {
690 let action = match click.kind() {
691 mouse::click::Kind::Single => {
692 Action::Click(click.position())
693 }
694 mouse::click::Kind::Double => Action::SelectWord,
695 mouse::click::Kind::Triple => Action::SelectLine,
696 };
697
698 state.focus = Some(Focus::now());
699 state.last_click = Some(click);
700 state.drag_click = Some(click.kind());
701
702 shell.publish(on_edit(action));
703 }
704 Update::Drag(position) => {
705 shell.publish(on_edit(Action::Drag(position)));
706 }
707 Update::Release => {
708 state.drag_click = None;
709 }
710 Update::Scroll(lines) => {
711 let bounds = self.content.0.borrow().editor.bounds();
712
713 if bounds.height >= i32::MAX as f32 {
714 return event::Status::Ignored;
715 }
716
717 let lines = lines + state.partial_scroll;
718 state.partial_scroll = lines.fract();
719
720 shell.publish(on_edit(Action::Scroll {
721 lines: lines as i32,
722 }));
723 }
724 Update::Binding(binding) => {
725 fn apply_binding<
726 H: text::Highlighter,
727 R: text::Renderer,
728 Message,
729 >(
730 binding: Binding<Message>,
731 content: &Content<R>,
732 state: &mut State<H>,
733 on_edit: &dyn Fn(Action) -> Message,
734 clipboard: &mut dyn Clipboard,
735 shell: &mut Shell<'_, Message>,
736 ) {
737 let mut publish = |action| shell.publish(on_edit(action));
738
739 match binding {
740 Binding::Unfocus => {
741 state.focus = None;
742 state.drag_click = None;
743 }
744 Binding::Copy => {
745 if let Some(selection) = content.selection() {
746 clipboard.write(
747 clipboard::Kind::Standard,
748 selection,
749 );
750 }
751 }
752 Binding::Cut => {
753 if let Some(selection) = content.selection() {
754 clipboard.write(
755 clipboard::Kind::Standard,
756 selection,
757 );
758
759 publish(Action::Edit(Edit::Delete));
760 }
761 }
762 Binding::Paste => {
763 if let Some(contents) =
764 clipboard.read(clipboard::Kind::Standard)
765 {
766 publish(Action::Edit(Edit::Paste(Arc::new(
767 contents,
768 ))));
769 }
770 }
771 Binding::Move(motion) => {
772 publish(Action::Move(motion));
773 }
774 Binding::Select(motion) => {
775 publish(Action::Select(motion));
776 }
777 Binding::SelectWord => {
778 publish(Action::SelectWord);
779 }
780 Binding::SelectLine => {
781 publish(Action::SelectLine);
782 }
783 Binding::SelectAll => {
784 publish(Action::SelectAll);
785 }
786 Binding::Insert(c) => {
787 publish(Action::Edit(Edit::Insert(c)));
788 }
789 Binding::Enter => {
790 publish(Action::Edit(Edit::Enter));
791 }
792 Binding::Backspace => {
793 publish(Action::Edit(Edit::Backspace));
794 }
795 Binding::Delete => {
796 publish(Action::Edit(Edit::Delete));
797 }
798 Binding::Sequence(sequence) => {
799 for binding in sequence {
800 apply_binding(
801 binding, content, state, on_edit,
802 clipboard, shell,
803 );
804 }
805 }
806 Binding::Custom(message) => {
807 shell.publish(message);
808 }
809 }
810 }
811
812 apply_binding(
813 binding,
814 self.content,
815 state,
816 on_edit,
817 clipboard,
818 shell,
819 );
820
821 if let Some(focus) = &mut state.focus {
822 focus.updated_at = Instant::now();
823 }
824 }
825 }
826
827 event::Status::Captured
828 }
829
830 fn draw(
831 &self,
832 tree: &widget::Tree,
833 renderer: &mut Renderer,
834 theme: &Theme,
835 _defaults: &renderer::Style,
836 layout: Layout<'_>,
837 cursor: mouse::Cursor,
838 _viewport: &Rectangle,
839 ) {
840 let bounds = layout.bounds();
841
842 let mut internal = self.content.0.borrow_mut();
843 let state = tree.state.downcast_ref::<State<Highlighter>>();
844
845 let font = self.font.unwrap_or_else(|| renderer.default_font());
846
847 internal.editor.highlight(
848 font,
849 state.highlighter.borrow_mut().deref_mut(),
850 |highlight| (self.highlighter_format)(highlight, theme),
851 );
852
853 let is_disabled = self.on_edit.is_none();
854 let is_mouse_over = cursor.is_over(bounds);
855
856 let status = if is_disabled {
857 Status::Disabled
858 } else if state.focus.is_some() {
859 Status::Focused
860 } else if is_mouse_over {
861 Status::Hovered
862 } else {
863 Status::Active
864 };
865
866 let style = theme.style(&self.class, status);
867
868 renderer.fill_quad(
869 renderer::Quad {
870 bounds,
871 border: style.border,
872 ..renderer::Quad::default()
873 },
874 style.background,
875 );
876
877 let text_bounds = bounds.shrink(self.padding);
878
879 if internal.editor.is_empty() {
880 if let Some(placeholder) = self.placeholder.clone() {
881 renderer.fill_text(
882 Text {
883 content: placeholder.into_owned(),
884 bounds: text_bounds.size(),
885 size: self
886 .text_size
887 .unwrap_or_else(|| renderer.default_size()),
888 line_height: self.line_height,
889 font,
890 horizontal_alignment: alignment::Horizontal::Left,
891 vertical_alignment: alignment::Vertical::Top,
892 shaping: text::Shaping::Advanced,
893 wrapping: self.wrapping,
894 },
895 text_bounds.position(),
896 style.placeholder,
897 text_bounds,
898 );
899 }
900 } else {
901 renderer.fill_editor(
902 &internal.editor,
903 text_bounds.position(),
904 style.value,
905 text_bounds,
906 );
907 }
908
909 let translation = text_bounds.position() - Point::ORIGIN;
910
911 if let Some(focus) = state.focus.as_ref() {
912 match internal.editor.cursor() {
913 Cursor::Caret(position) if focus.is_cursor_visible() => {
914 let cursor =
915 Rectangle::new(
916 position + translation,
917 Size::new(
918 1.0,
919 self.line_height
920 .to_absolute(self.text_size.unwrap_or_else(
921 || renderer.default_size(),
922 ))
923 .into(),
924 ),
925 );
926
927 if let Some(clipped_cursor) =
928 text_bounds.intersection(&cursor)
929 {
930 renderer.fill_quad(
931 renderer::Quad {
932 bounds: clipped_cursor,
933 ..renderer::Quad::default()
934 },
935 style.value,
936 );
937 }
938 }
939 Cursor::Selection(ranges) => {
940 for range in ranges.into_iter().filter_map(|range| {
941 text_bounds.intersection(&(range + translation))
942 }) {
943 renderer.fill_quad(
944 renderer::Quad {
945 bounds: range,
946 ..renderer::Quad::default()
947 },
948 style.selection,
949 );
950 }
951 }
952 Cursor::Caret(_) => {}
953 }
954 }
955 }
956
957 fn mouse_interaction(
958 &self,
959 _state: &widget::Tree,
960 layout: Layout<'_>,
961 cursor: mouse::Cursor,
962 _viewport: &Rectangle,
963 _renderer: &Renderer,
964 ) -> mouse::Interaction {
965 let is_disabled = self.on_edit.is_none();
966
967 if cursor.is_over(layout.bounds()) {
968 if is_disabled {
969 mouse::Interaction::NotAllowed
970 } else {
971 mouse::Interaction::Text
972 }
973 } else {
974 mouse::Interaction::default()
975 }
976 }
977
978 fn operate(
979 &self,
980 tree: &mut widget::Tree,
981 _layout: Layout<'_>,
982 _renderer: &Renderer,
983 operation: &mut dyn widget::Operation,
984 ) {
985 let state = tree.state.downcast_mut::<State<Highlighter>>();
986
987 operation.focusable(state, self.id.as_ref().map(|id| &id.0));
988 }
989
990 fn id(&self) -> Option<widget::Id> {
991 self.id.as_ref().map(|id| id.0.clone())
992 }
993 fn set_id(&mut self, id: widget::Id) {
994 self.id = Some(Id(id));
995 }
996}
997
998impl<'a, Highlighter, Message, Theme, Renderer>
999 From<TextEditor<'a, Highlighter, Message, Theme, Renderer>>
1000 for Element<'a, Message, Theme, Renderer>
1001where
1002 Highlighter: text::Highlighter,
1003 Message: 'a,
1004 Theme: Catalog + 'a,
1005 Renderer: text::Renderer,
1006{
1007 fn from(
1008 text_editor: TextEditor<'a, Highlighter, Message, Theme, Renderer>,
1009 ) -> Self {
1010 Self::new(text_editor)
1011 }
1012}
1013
1014#[derive(Debug, Clone, PartialEq)]
1016pub enum Binding<Message> {
1017 Unfocus,
1019 Copy,
1021 Cut,
1023 Paste,
1025 Move(Motion),
1027 Select(Motion),
1029 SelectWord,
1031 SelectLine,
1033 SelectAll,
1035 Insert(char),
1037 Enter,
1039 Backspace,
1041 Delete,
1043 Sequence(Vec<Self>),
1045 Custom(Message),
1047}
1048
1049#[derive(Debug, Clone, PartialEq, Eq)]
1051pub struct KeyPress {
1052 pub key: keyboard::Key,
1054 pub modifiers: keyboard::Modifiers,
1056 pub text: Option<SmolStr>,
1058 pub status: Status,
1060}
1061
1062impl<Message> Binding<Message> {
1063 pub fn from_key_press(event: KeyPress) -> Option<Self> {
1065 let KeyPress {
1066 key,
1067 modifiers,
1068 text,
1069 status,
1070 } = event;
1071
1072 if status != Status::Focused {
1073 return None;
1074 }
1075
1076 match key.as_ref() {
1077 keyboard::Key::Named(key::Named::Enter) => Some(Self::Enter),
1078 keyboard::Key::Named(key::Named::Backspace) => {
1079 Some(Self::Backspace)
1080 }
1081 keyboard::Key::Named(key::Named::Delete)
1082 if text.is_none() || text.as_deref() == Some("\u{7f}") =>
1083 {
1084 Some(Self::Delete)
1085 }
1086 keyboard::Key::Named(key::Named::Escape) => Some(Self::Unfocus),
1087 keyboard::Key::Character("c") if modifiers.command() => {
1088 Some(Self::Copy)
1089 }
1090 keyboard::Key::Character("x") if modifiers.command() => {
1091 Some(Self::Cut)
1092 }
1093 keyboard::Key::Character("v")
1094 if modifiers.command() && !modifiers.alt() =>
1095 {
1096 Some(Self::Paste)
1097 }
1098 keyboard::Key::Character("a") if modifiers.command() => {
1099 Some(Self::SelectAll)
1100 }
1101 _ => {
1102 if let Some(text) = text {
1103 let c = text.chars().find(|c| !c.is_control())?;
1104
1105 Some(Self::Insert(c))
1106 } else if let keyboard::Key::Named(named_key) = key.as_ref() {
1107 let motion = motion(named_key)?;
1108
1109 let motion = if modifiers.macos_command() {
1110 match motion {
1111 Motion::Left => Motion::Home,
1112 Motion::Right => Motion::End,
1113 _ => motion,
1114 }
1115 } else {
1116 motion
1117 };
1118
1119 let motion = if modifiers.jump() {
1120 motion.widen()
1121 } else {
1122 motion
1123 };
1124
1125 Some(if modifiers.shift() {
1126 Self::Select(motion)
1127 } else {
1128 Self::Move(motion)
1129 })
1130 } else {
1131 None
1132 }
1133 }
1134 }
1135 }
1136}
1137
1138enum Update<Message> {
1139 Click(mouse::Click),
1140 Drag(Point),
1141 Release,
1142 Scroll(f32),
1143 Binding(Binding<Message>),
1144}
1145
1146impl<Message> Update<Message> {
1147 fn from_event<H: Highlighter>(
1148 event: Event,
1149 state: &State<H>,
1150 bounds: Rectangle,
1151 padding: Padding,
1152 cursor: mouse::Cursor,
1153 key_binding: Option<&dyn Fn(KeyPress) -> Option<Binding<Message>>>,
1154 ) -> Option<Self> {
1155 let binding = |binding| Some(Update::Binding(binding));
1156
1157 match event {
1158 Event::Mouse(event) => match event {
1159 mouse::Event::ButtonPressed(mouse::Button::Left) => {
1160 if let Some(cursor_position) = cursor.position_in(bounds) {
1161 let cursor_position = cursor_position
1162 - Vector::new(padding.top, padding.left);
1163
1164 let click = mouse::Click::new(
1165 cursor_position,
1166 mouse::Button::Left,
1167 state.last_click,
1168 );
1169
1170 Some(Update::Click(click))
1171 } else if state.focus.is_some() {
1172 binding(Binding::Unfocus)
1173 } else {
1174 None
1175 }
1176 }
1177 mouse::Event::ButtonReleased(mouse::Button::Left) => {
1178 Some(Update::Release)
1179 }
1180 mouse::Event::CursorMoved { .. } => match state.drag_click {
1181 Some(mouse::click::Kind::Single) => {
1182 let cursor_position = cursor.position_in(bounds)?
1183 - Vector::new(padding.top, padding.left);
1184
1185 Some(Update::Drag(cursor_position))
1186 }
1187 _ => None,
1188 },
1189 mouse::Event::WheelScrolled { delta }
1190 if cursor.is_over(bounds) =>
1191 {
1192 Some(Update::Scroll(match delta {
1193 mouse::ScrollDelta::Lines { y, .. } => {
1194 if y.abs() > 0.0 {
1195 y.signum() * -(y.abs() * 4.0).max(1.0)
1196 } else {
1197 0.0
1198 }
1199 }
1200 mouse::ScrollDelta::Pixels { y, .. } => -y / 4.0,
1201 }))
1202 }
1203 _ => None,
1204 },
1205 Event::Keyboard(keyboard::Event::KeyPressed {
1206 key,
1207 modifiers,
1208 text,
1209 ..
1210 }) => {
1211 let status = if state.focus.is_some() {
1212 Status::Focused
1213 } else {
1214 Status::Active
1215 };
1216
1217 let key_press = KeyPress {
1218 key,
1219 modifiers,
1220 text,
1221 status,
1222 };
1223
1224 if let Some(key_binding) = key_binding {
1225 key_binding(key_press)
1226 } else {
1227 Binding::from_key_press(key_press)
1228 }
1229 .map(Self::Binding)
1230 }
1231 _ => None,
1232 }
1233 }
1234}
1235
1236fn motion(key: key::Named) -> Option<Motion> {
1237 match key {
1238 key::Named::ArrowLeft => Some(Motion::Left),
1239 key::Named::ArrowRight => Some(Motion::Right),
1240 key::Named::ArrowUp => Some(Motion::Up),
1241 key::Named::ArrowDown => Some(Motion::Down),
1242 key::Named::Home => Some(Motion::Home),
1243 key::Named::End => Some(Motion::End),
1244 key::Named::PageUp => Some(Motion::PageUp),
1245 key::Named::PageDown => Some(Motion::PageDown),
1246 _ => None,
1247 }
1248}
1249
1250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1252pub enum Status {
1253 Active,
1255 Hovered,
1257 Focused,
1259 Disabled,
1261}
1262
1263#[derive(Debug, Clone, Copy, PartialEq)]
1265pub struct Style {
1266 pub background: Background,
1268 pub border: Border,
1270 pub icon: Color,
1272 pub placeholder: Color,
1274 pub value: Color,
1276 pub selection: Color,
1278}
1279
1280pub trait Catalog {
1282 type Class<'a>;
1284
1285 fn default<'a>() -> Self::Class<'a>;
1287
1288 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
1290}
1291
1292pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
1294
1295impl Catalog for Theme {
1296 type Class<'a> = StyleFn<'a, Self>;
1297
1298 fn default<'a>() -> Self::Class<'a> {
1299 Box::new(default)
1300 }
1301
1302 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
1303 class(self, status)
1304 }
1305}
1306
1307pub fn default(theme: &Theme, status: Status) -> Style {
1309 let palette = theme.extended_palette();
1310
1311 let active = Style {
1312 background: Background::Color(palette.background.base.color),
1313 border: Border {
1314 radius: 2.0.into(),
1315 width: 1.0,
1316 color: palette.background.strong.color,
1317 },
1318 icon: palette.background.weak.text,
1319 placeholder: palette.background.strong.color,
1320 value: palette.background.base.text,
1321 selection: palette.primary.weak.color,
1322 };
1323
1324 match status {
1325 Status::Active => active,
1326 Status::Hovered => Style {
1327 border: Border {
1328 color: palette.background.base.text,
1329 ..active.border
1330 },
1331 ..active
1332 },
1333 Status::Focused => Style {
1334 border: Border {
1335 color: palette.primary.strong.color,
1336 ..active.border
1337 },
1338 ..active
1339 },
1340 Status::Disabled => Style {
1341 background: Background::Color(palette.background.weak.color),
1342 value: active.placeholder,
1343 ..active
1344 },
1345 }
1346}