iced_widget/
text_editor.rs

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