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