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 &mut 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 && focus.is_window_focused
694 {
695 focus.now = *now;
696
697 let millis_until_redraw =
698 Focus::CURSOR_BLINK_INTERVAL_MILLIS
699 - (focus.now - focus.updated_at).as_millis()
700 % Focus::CURSOR_BLINK_INTERVAL_MILLIS;
701
702 shell.request_redraw_at(
703 focus.now
704 + Duration::from_millis(millis_until_redraw as u64),
705 );
706 }
707 }
708 _ => {}
709 }
710
711 if let Some(update) = Update::from_event(
712 event,
713 state,
714 layout.bounds(),
715 self.padding,
716 cursor,
717 self.key_binding.as_deref(),
718 ) {
719 match update {
720 Update::Click(click) => {
721 let action = match click.kind() {
722 mouse::click::Kind::Single => {
723 Action::Click(click.position())
724 }
725 mouse::click::Kind::Double => Action::SelectWord,
726 mouse::click::Kind::Triple => Action::SelectLine,
727 };
728
729 state.focus = Some(Focus::now());
730 state.last_click = Some(click);
731 state.drag_click = Some(click.kind());
732
733 shell.publish(on_edit(action));
734 shell.capture_event();
735 }
736 Update::Drag(position) => {
737 shell.publish(on_edit(Action::Drag(position)));
738 }
739 Update::Release => {
740 state.drag_click = None;
741 }
742 Update::Scroll(lines) => {
743 let bounds = self.content.0.borrow().editor.bounds();
744
745 if bounds.height >= i32::MAX as f32 {
746 return;
747 }
748
749 let lines = lines + state.partial_scroll;
750 state.partial_scroll = lines.fract();
751
752 shell.publish(on_edit(Action::Scroll {
753 lines: lines as i32,
754 }));
755 shell.capture_event();
756 }
757 Update::InputMethod(update) => match update {
758 Ime::Toggle(is_open) => {
759 state.preedit =
760 is_open.then(input_method::Preedit::new);
761
762 shell.request_redraw();
763 }
764 Ime::Preedit { content, selection } => {
765 state.preedit = Some(input_method::Preedit {
766 content,
767 selection,
768 text_size: self.text_size,
769 });
770
771 shell.request_redraw();
772 }
773 Ime::Commit(text) => {
774 shell.publish(on_edit(Action::Edit(Edit::Paste(
775 Arc::new(text),
776 ))));
777 }
778 },
779 Update::Binding(binding) => {
780 fn apply_binding<
781 H: text::Highlighter,
782 R: text::Renderer,
783 Message,
784 >(
785 binding: Binding<Message>,
786 content: &Content<R>,
787 state: &mut State<H>,
788 on_edit: &dyn Fn(Action) -> Message,
789 clipboard: &mut dyn Clipboard,
790 shell: &mut Shell<'_, Message>,
791 ) {
792 let mut publish =
793 |action| shell.publish(on_edit(action));
794
795 match binding {
796 Binding::Unfocus => {
797 state.focus = None;
798 state.drag_click = None;
799 }
800 Binding::Copy => {
801 if let Some(selection) = content.selection() {
802 clipboard.write(
803 clipboard::Kind::Standard,
804 selection,
805 );
806 }
807 }
808 Binding::Cut => {
809 if let Some(selection) = content.selection() {
810 clipboard.write(
811 clipboard::Kind::Standard,
812 selection,
813 );
814
815 publish(Action::Edit(Edit::Delete));
816 }
817 }
818 Binding::Paste => {
819 if let Some(contents) =
820 clipboard.read(clipboard::Kind::Standard)
821 {
822 publish(Action::Edit(Edit::Paste(
823 Arc::new(contents),
824 )));
825 }
826 }
827 Binding::Move(motion) => {
828 publish(Action::Move(motion));
829 }
830 Binding::Select(motion) => {
831 publish(Action::Select(motion));
832 }
833 Binding::SelectWord => {
834 publish(Action::SelectWord);
835 }
836 Binding::SelectLine => {
837 publish(Action::SelectLine);
838 }
839 Binding::SelectAll => {
840 publish(Action::SelectAll);
841 }
842 Binding::Insert(c) => {
843 publish(Action::Edit(Edit::Insert(c)));
844 }
845 Binding::Enter => {
846 publish(Action::Edit(Edit::Enter));
847 }
848 Binding::Backspace => {
849 publish(Action::Edit(Edit::Backspace));
850 }
851 Binding::Delete => {
852 publish(Action::Edit(Edit::Delete));
853 }
854 Binding::Sequence(sequence) => {
855 for binding in sequence {
856 apply_binding(
857 binding, content, state, on_edit,
858 clipboard, shell,
859 );
860 }
861 }
862 Binding::Custom(message) => {
863 shell.publish(message);
864 }
865 }
866 }
867
868 if !matches!(binding, Binding::Unfocus) {
869 shell.capture_event();
870 }
871
872 apply_binding(
873 binding,
874 self.content,
875 state,
876 on_edit,
877 clipboard,
878 shell,
879 );
880
881 if let Some(focus) = &mut state.focus {
882 focus.updated_at = Instant::now();
883 }
884 }
885 }
886 }
887
888 let status = {
889 let is_disabled = self.on_edit.is_none();
890 let is_hovered = cursor.is_over(layout.bounds());
891
892 if is_disabled {
893 Status::Disabled
894 } else if state.focus.is_some() {
895 Status::Focused { is_hovered }
896 } else if is_hovered {
897 Status::Hovered
898 } else {
899 Status::Active
900 }
901 };
902
903 if is_redraw {
904 self.last_status = Some(status);
905
906 shell.request_input_method(
907 &self.input_method(state, renderer, layout),
908 );
909 } else if self
910 .last_status
911 .is_some_and(|last_status| status != last_status)
912 {
913 shell.request_redraw();
914 }
915 }
916
917 fn draw(
918 &self,
919 tree: &widget::Tree,
920 renderer: &mut Renderer,
921 theme: &Theme,
922 _defaults: &renderer::Style,
923 layout: Layout<'_>,
924 _cursor: mouse::Cursor,
925 _viewport: &Rectangle,
926 ) {
927 let bounds = layout.bounds();
928
929 let mut internal = self.content.0.borrow_mut();
930 let state = tree.state.downcast_ref::<State<Highlighter>>();
931
932 let font = self.font.unwrap_or_else(|| renderer.default_font());
933
934 internal.editor.highlight(
935 font,
936 state.highlighter.borrow_mut().deref_mut(),
937 |highlight| (self.highlighter_format)(highlight, theme),
938 );
939
940 let style = theme
941 .style(&self.class, self.last_status.unwrap_or(Status::Active));
942
943 renderer.fill_quad(
944 renderer::Quad {
945 bounds,
946 border: style.border,
947 ..renderer::Quad::default()
948 },
949 style.background,
950 );
951
952 let text_bounds = bounds.shrink(self.padding);
953
954 if internal.editor.is_empty() {
955 if let Some(placeholder) = self.placeholder.clone() {
956 renderer.fill_text(
957 Text {
958 content: placeholder.into_owned(),
959 bounds: text_bounds.size(),
960 size: self
961 .text_size
962 .unwrap_or_else(|| renderer.default_size()),
963 line_height: self.line_height,
964 font,
965 align_x: text::Alignment::Default,
966 align_y: alignment::Vertical::Top,
967 shaping: text::Shaping::Advanced,
968 wrapping: self.wrapping,
969 },
970 text_bounds.position(),
971 style.placeholder,
972 text_bounds,
973 );
974 }
975 } else {
976 renderer.fill_editor(
977 &internal.editor,
978 text_bounds.position(),
979 style.value,
980 text_bounds,
981 );
982 }
983
984 let translation = text_bounds.position() - Point::ORIGIN;
985
986 if let Some(focus) = state.focus.as_ref() {
987 match internal.editor.cursor() {
988 Cursor::Caret(position) if focus.is_cursor_visible() => {
989 let cursor =
990 Rectangle::new(
991 position + translation,
992 Size::new(
993 1.0,
994 self.line_height
995 .to_absolute(self.text_size.unwrap_or_else(
996 || renderer.default_size(),
997 ))
998 .into(),
999 ),
1000 );
1001
1002 if let Some(clipped_cursor) =
1003 text_bounds.intersection(&cursor)
1004 {
1005 renderer.fill_quad(
1006 renderer::Quad {
1007 bounds: clipped_cursor,
1008 ..renderer::Quad::default()
1009 },
1010 style.value,
1011 );
1012 }
1013 }
1014 Cursor::Selection(ranges) => {
1015 for range in ranges.into_iter().filter_map(|range| {
1016 text_bounds.intersection(&(range + translation))
1017 }) {
1018 renderer.fill_quad(
1019 renderer::Quad {
1020 bounds: range,
1021 ..renderer::Quad::default()
1022 },
1023 style.selection,
1024 );
1025 }
1026 }
1027 Cursor::Caret(_) => {}
1028 }
1029 }
1030 }
1031
1032 fn mouse_interaction(
1033 &self,
1034 _state: &widget::Tree,
1035 layout: Layout<'_>,
1036 cursor: mouse::Cursor,
1037 _viewport: &Rectangle,
1038 _renderer: &Renderer,
1039 ) -> mouse::Interaction {
1040 let is_disabled = self.on_edit.is_none();
1041
1042 if cursor.is_over(layout.bounds()) {
1043 if is_disabled {
1044 mouse::Interaction::NotAllowed
1045 } else {
1046 mouse::Interaction::Text
1047 }
1048 } else {
1049 mouse::Interaction::default()
1050 }
1051 }
1052
1053 fn operate(
1054 &mut self,
1055 tree: &mut widget::Tree,
1056 layout: Layout<'_>,
1057 _renderer: &Renderer,
1058 operation: &mut dyn widget::Operation,
1059 ) {
1060 let state = tree.state.downcast_mut::<State<Highlighter>>();
1061
1062 operation.focusable(None, layout.bounds(), state);
1063 }
1064}
1065
1066impl<'a, Highlighter, Message, Theme, Renderer>
1067 From<TextEditor<'a, Highlighter, Message, Theme, Renderer>>
1068 for Element<'a, Message, Theme, Renderer>
1069where
1070 Highlighter: text::Highlighter,
1071 Message: 'a,
1072 Theme: Catalog + 'a,
1073 Renderer: text::Renderer,
1074{
1075 fn from(
1076 text_editor: TextEditor<'a, Highlighter, Message, Theme, Renderer>,
1077 ) -> Self {
1078 Self::new(text_editor)
1079 }
1080}
1081
1082#[derive(Debug, Clone, PartialEq)]
1084pub enum Binding<Message> {
1085 Unfocus,
1087 Copy,
1089 Cut,
1091 Paste,
1093 Move(Motion),
1095 Select(Motion),
1097 SelectWord,
1099 SelectLine,
1101 SelectAll,
1103 Insert(char),
1105 Enter,
1107 Backspace,
1109 Delete,
1111 Sequence(Vec<Self>),
1113 Custom(Message),
1115}
1116
1117#[derive(Debug, Clone, PartialEq, Eq)]
1119pub struct KeyPress {
1120 pub key: keyboard::Key,
1122 pub modifiers: keyboard::Modifiers,
1124 pub text: Option<SmolStr>,
1126 pub status: Status,
1128}
1129
1130impl<Message> Binding<Message> {
1131 pub fn from_key_press(event: KeyPress) -> Option<Self> {
1133 let KeyPress {
1134 key,
1135 modifiers,
1136 text,
1137 status,
1138 } = event;
1139
1140 if !matches!(status, Status::Focused { .. }) {
1141 return None;
1142 }
1143
1144 match key.as_ref() {
1145 keyboard::Key::Named(key::Named::Enter) => Some(Self::Enter),
1146 keyboard::Key::Named(key::Named::Backspace) => {
1147 Some(Self::Backspace)
1148 }
1149 keyboard::Key::Named(key::Named::Delete)
1150 if text.is_none() || text.as_deref() == Some("\u{7f}") =>
1151 {
1152 Some(Self::Delete)
1153 }
1154 keyboard::Key::Named(key::Named::Escape) => Some(Self::Unfocus),
1155 keyboard::Key::Character("c") if modifiers.command() => {
1156 Some(Self::Copy)
1157 }
1158 keyboard::Key::Character("x") if modifiers.command() => {
1159 Some(Self::Cut)
1160 }
1161 keyboard::Key::Character("v")
1162 if modifiers.command() && !modifiers.alt() =>
1163 {
1164 Some(Self::Paste)
1165 }
1166 keyboard::Key::Character("a") if modifiers.command() => {
1167 Some(Self::SelectAll)
1168 }
1169 _ => {
1170 if let Some(text) = text {
1171 let c = text.chars().find(|c| !c.is_control())?;
1172
1173 Some(Self::Insert(c))
1174 } else if let keyboard::Key::Named(named_key) = key.as_ref() {
1175 let motion = motion(named_key)?;
1176
1177 let motion = if modifiers.macos_command() {
1178 match motion {
1179 Motion::Left => Motion::Home,
1180 Motion::Right => Motion::End,
1181 _ => motion,
1182 }
1183 } else {
1184 motion
1185 };
1186
1187 let motion = if modifiers.jump() {
1188 motion.widen()
1189 } else {
1190 motion
1191 };
1192
1193 Some(if modifiers.shift() {
1194 Self::Select(motion)
1195 } else {
1196 Self::Move(motion)
1197 })
1198 } else {
1199 None
1200 }
1201 }
1202 }
1203 }
1204}
1205
1206enum Update<Message> {
1207 Click(mouse::Click),
1208 Drag(Point),
1209 Release,
1210 Scroll(f32),
1211 InputMethod(Ime),
1212 Binding(Binding<Message>),
1213}
1214
1215enum Ime {
1216 Toggle(bool),
1217 Preedit {
1218 content: String,
1219 selection: Option<Range<usize>>,
1220 },
1221 Commit(String),
1222}
1223
1224impl<Message> Update<Message> {
1225 fn from_event<H: Highlighter>(
1226 event: &Event,
1227 state: &State<H>,
1228 bounds: Rectangle,
1229 padding: Padding,
1230 cursor: mouse::Cursor,
1231 key_binding: Option<&dyn Fn(KeyPress) -> Option<Binding<Message>>>,
1232 ) -> Option<Self> {
1233 let binding = |binding| Some(Update::Binding(binding));
1234
1235 match event {
1236 Event::Mouse(event) => match event {
1237 mouse::Event::ButtonPressed(mouse::Button::Left) => {
1238 if let Some(cursor_position) = cursor.position_in(bounds) {
1239 let cursor_position = cursor_position
1240 - Vector::new(padding.top, padding.left);
1241
1242 let click = mouse::Click::new(
1243 cursor_position,
1244 mouse::Button::Left,
1245 state.last_click,
1246 );
1247
1248 Some(Update::Click(click))
1249 } else if state.focus.is_some() {
1250 binding(Binding::Unfocus)
1251 } else {
1252 None
1253 }
1254 }
1255 mouse::Event::ButtonReleased(mouse::Button::Left) => {
1256 Some(Update::Release)
1257 }
1258 mouse::Event::CursorMoved { .. } => match state.drag_click {
1259 Some(mouse::click::Kind::Single) => {
1260 let cursor_position = cursor.position_in(bounds)?
1261 - Vector::new(padding.top, padding.left);
1262
1263 Some(Update::Drag(cursor_position))
1264 }
1265 _ => None,
1266 },
1267 mouse::Event::WheelScrolled { delta }
1268 if cursor.is_over(bounds) =>
1269 {
1270 Some(Update::Scroll(match delta {
1271 mouse::ScrollDelta::Lines { y, .. } => {
1272 if y.abs() > 0.0 {
1273 y.signum() * -(y.abs() * 4.0).max(1.0)
1274 } else {
1275 0.0
1276 }
1277 }
1278 mouse::ScrollDelta::Pixels { y, .. } => -y / 4.0,
1279 }))
1280 }
1281 _ => None,
1282 },
1283 Event::InputMethod(event) => match event {
1284 input_method::Event::Opened | input_method::Event::Closed => {
1285 Some(Update::InputMethod(Ime::Toggle(matches!(
1286 event,
1287 input_method::Event::Opened
1288 ))))
1289 }
1290 input_method::Event::Preedit(content, selection)
1291 if state.focus.is_some() =>
1292 {
1293 Some(Update::InputMethod(Ime::Preedit {
1294 content: content.clone(),
1295 selection: selection.clone(),
1296 }))
1297 }
1298 input_method::Event::Commit(content)
1299 if state.focus.is_some() =>
1300 {
1301 Some(Update::InputMethod(Ime::Commit(content.clone())))
1302 }
1303 _ => None,
1304 },
1305 Event::Keyboard(keyboard::Event::KeyPressed {
1306 key,
1307 modifiers,
1308 text,
1309 ..
1310 }) => {
1311 let status = if state.focus.is_some() {
1312 Status::Focused {
1313 is_hovered: cursor.is_over(bounds),
1314 }
1315 } else {
1316 Status::Active
1317 };
1318
1319 let key_press = KeyPress {
1320 key: key.clone(),
1321 modifiers: *modifiers,
1322 text: text.clone(),
1323 status,
1324 };
1325
1326 if let Some(key_binding) = key_binding {
1327 key_binding(key_press)
1328 } else {
1329 Binding::from_key_press(key_press)
1330 }
1331 .map(Self::Binding)
1332 }
1333 _ => None,
1334 }
1335 }
1336}
1337
1338fn motion(key: key::Named) -> Option<Motion> {
1339 match key {
1340 key::Named::ArrowLeft => Some(Motion::Left),
1341 key::Named::ArrowRight => Some(Motion::Right),
1342 key::Named::ArrowUp => Some(Motion::Up),
1343 key::Named::ArrowDown => Some(Motion::Down),
1344 key::Named::Home => Some(Motion::Home),
1345 key::Named::End => Some(Motion::End),
1346 key::Named::PageUp => Some(Motion::PageUp),
1347 key::Named::PageDown => Some(Motion::PageDown),
1348 _ => None,
1349 }
1350}
1351
1352#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1354pub enum Status {
1355 Active,
1357 Hovered,
1359 Focused {
1361 is_hovered: bool,
1363 },
1364 Disabled,
1366}
1367
1368#[derive(Debug, Clone, Copy, PartialEq)]
1370pub struct Style {
1371 pub background: Background,
1373 pub border: Border,
1375 pub placeholder: Color,
1377 pub value: Color,
1379 pub selection: Color,
1381}
1382
1383pub trait Catalog {
1385 type Class<'a>;
1387
1388 fn default<'a>() -> Self::Class<'a>;
1390
1391 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
1393}
1394
1395pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
1397
1398impl Catalog for Theme {
1399 type Class<'a> = StyleFn<'a, Self>;
1400
1401 fn default<'a>() -> Self::Class<'a> {
1402 Box::new(default)
1403 }
1404
1405 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
1406 class(self, status)
1407 }
1408}
1409
1410pub fn default(theme: &Theme, status: Status) -> Style {
1412 let palette = theme.extended_palette();
1413
1414 let active = Style {
1415 background: Background::Color(palette.background.base.color),
1416 border: Border {
1417 radius: 2.0.into(),
1418 width: 1.0,
1419 color: palette.background.strong.color,
1420 },
1421 placeholder: palette.secondary.base.color,
1422 value: palette.background.base.text,
1423 selection: palette.primary.weak.color,
1424 };
1425
1426 match status {
1427 Status::Active => active,
1428 Status::Hovered => Style {
1429 border: Border {
1430 color: palette.background.base.text,
1431 ..active.border
1432 },
1433 ..active
1434 },
1435 Status::Focused { .. } => Style {
1436 border: Border {
1437 color: palette.primary.strong.color,
1438 ..active.border
1439 },
1440 ..active
1441 },
1442 Status::Disabled => Style {
1443 background: Background::Color(palette.background.weak.color),
1444 value: active.placeholder,
1445 placeholder: palette.background.strongest.color,
1446 ..active
1447 },
1448 }
1449}