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