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