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