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    partial_scroll: f32,
483    highlighter: RefCell<Highlighter>,
484    highlighter_settings: Highlighter::Settings,
485    highlighter_format_address: usize,
486}
487
488#[derive(Debug, Clone, Copy)]
489struct Focus {
490    updated_at: Instant,
491    now: Instant,
492    is_window_focused: bool,
493}
494
495impl Focus {
496    const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
497
498    fn now() -> Self {
499        let now = Instant::now();
500
501        Self {
502            updated_at: now,
503            now,
504            is_window_focused: true,
505        }
506    }
507
508    fn is_cursor_visible(&self) -> bool {
509        self.is_window_focused
510            && ((self.now - self.updated_at).as_millis()
511                / Self::CURSOR_BLINK_INTERVAL_MILLIS)
512                % 2
513                == 0
514    }
515}
516
517impl<Highlighter: text::Highlighter> State<Highlighter> {
518    /// Returns whether the [`TextEditor`] is currently focused or not.
519    pub fn is_focused(&self) -> bool {
520        self.focus.is_some()
521    }
522}
523
524impl<Highlighter: text::Highlighter> operation::Focusable
525    for State<Highlighter>
526{
527    fn is_focused(&self) -> bool {
528        self.focus.is_some()
529    }
530
531    fn focus(&mut self) {
532        self.focus = Some(Focus::now());
533    }
534
535    fn unfocus(&mut self) {
536        self.focus = None;
537    }
538}
539
540impl<'a, Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
541    for TextEditor<'a, Highlighter, Message, Theme, Renderer>
542where
543    Highlighter: text::Highlighter,
544    Theme: Catalog,
545    Renderer: text::Renderer,
546{
547    fn tag(&self) -> widget::tree::Tag {
548        widget::tree::Tag::of::<State<Highlighter>>()
549    }
550
551    fn state(&self) -> widget::tree::State {
552        widget::tree::State::new(State {
553            focus: None,
554            last_click: None,
555            drag_click: None,
556            partial_scroll: 0.0,
557            highlighter: RefCell::new(Highlighter::new(
558                &self.highlighter_settings,
559            )),
560            highlighter_settings: self.highlighter_settings.clone(),
561            highlighter_format_address: self.highlighter_format as usize,
562        })
563    }
564
565    fn size(&self) -> Size<Length> {
566        Size {
567            width: self.width,
568            height: self.height,
569        }
570    }
571
572    fn layout(
573        &self,
574        tree: &mut widget::Tree,
575        renderer: &Renderer,
576        limits: &layout::Limits,
577    ) -> iced_renderer::core::layout::Node {
578        let mut internal = self.content.0.borrow_mut();
579        let state = tree.state.downcast_mut::<State<Highlighter>>();
580
581        if state.highlighter_format_address != self.highlighter_format as usize
582        {
583            state.highlighter.borrow_mut().change_line(0);
584
585            state.highlighter_format_address = self.highlighter_format as usize;
586        }
587
588        if state.highlighter_settings != self.highlighter_settings {
589            state
590                .highlighter
591                .borrow_mut()
592                .update(&self.highlighter_settings);
593
594            state.highlighter_settings = self.highlighter_settings.clone();
595        }
596
597        let limits = limits.width(self.width).height(self.height);
598
599        internal.editor.update(
600            limits.shrink(self.padding).max(),
601            self.font.unwrap_or_else(|| renderer.default_font()),
602            self.text_size.unwrap_or_else(|| renderer.default_size()),
603            self.line_height,
604            self.wrapping,
605            state.highlighter.borrow_mut().deref_mut(),
606        );
607
608        match self.height {
609            Length::Fill | Length::FillPortion(_) | Length::Fixed(_) => {
610                layout::Node::new(limits.max())
611            }
612            Length::Shrink => {
613                let min_bounds = internal.editor.min_bounds();
614
615                layout::Node::new(
616                    limits
617                        .height(min_bounds.height)
618                        .max()
619                        .expand(Size::new(0.0, self.padding.vertical())),
620                )
621            }
622        }
623    }
624
625    fn on_event(
626        &mut self,
627        tree: &mut widget::Tree,
628        event: Event,
629        layout: Layout<'_>,
630        cursor: mouse::Cursor,
631        _renderer: &Renderer,
632        clipboard: &mut dyn Clipboard,
633        shell: &mut Shell<'_, Message>,
634        _viewport: &Rectangle,
635    ) -> event::Status {
636        let Some(on_edit) = self.on_edit.as_ref() else {
637            return event::Status::Ignored;
638        };
639
640        let state = tree.state.downcast_mut::<State<Highlighter>>();
641
642        match event {
643            Event::Window(window::Event::Unfocused) => {
644                if let Some(focus) = &mut state.focus {
645                    focus.is_window_focused = false;
646                }
647            }
648            Event::Window(window::Event::Focused) => {
649                if let Some(focus) = &mut state.focus {
650                    focus.is_window_focused = true;
651                    focus.updated_at = Instant::now();
652
653                    shell.request_redraw(window::RedrawRequest::NextFrame);
654                }
655            }
656            Event::Window(window::Event::RedrawRequested(now)) => {
657                if let Some(focus) = &mut state.focus {
658                    if focus.is_window_focused {
659                        focus.now = now;
660
661                        let millis_until_redraw =
662                            Focus::CURSOR_BLINK_INTERVAL_MILLIS
663                                - (now - focus.updated_at).as_millis()
664                                    % Focus::CURSOR_BLINK_INTERVAL_MILLIS;
665
666                        shell.request_redraw(window::RedrawRequest::At(
667                            now + Duration::from_millis(
668                                millis_until_redraw as u64,
669                            ),
670                        ));
671                    }
672                }
673            }
674            _ => {}
675        }
676
677        let Some(update) = Update::from_event(
678            event,
679            state,
680            layout.bounds(),
681            self.padding,
682            cursor,
683            self.key_binding.as_deref(),
684        ) else {
685            return event::Status::Ignored;
686        };
687
688        match update {
689            Update::Click(click) => {
690                let action = match click.kind() {
691                    mouse::click::Kind::Single => {
692                        Action::Click(click.position())
693                    }
694                    mouse::click::Kind::Double => Action::SelectWord,
695                    mouse::click::Kind::Triple => Action::SelectLine,
696                };
697
698                state.focus = Some(Focus::now());
699                state.last_click = Some(click);
700                state.drag_click = Some(click.kind());
701
702                shell.publish(on_edit(action));
703            }
704            Update::Drag(position) => {
705                shell.publish(on_edit(Action::Drag(position)));
706            }
707            Update::Release => {
708                state.drag_click = None;
709            }
710            Update::Scroll(lines) => {
711                let bounds = self.content.0.borrow().editor.bounds();
712
713                if bounds.height >= i32::MAX as f32 {
714                    return event::Status::Ignored;
715                }
716
717                let lines = lines + state.partial_scroll;
718                state.partial_scroll = lines.fract();
719
720                shell.publish(on_edit(Action::Scroll {
721                    lines: lines as i32,
722                }));
723            }
724            Update::Binding(binding) => {
725                fn apply_binding<
726                    H: text::Highlighter,
727                    R: text::Renderer,
728                    Message,
729                >(
730                    binding: Binding<Message>,
731                    content: &Content<R>,
732                    state: &mut State<H>,
733                    on_edit: &dyn Fn(Action) -> Message,
734                    clipboard: &mut dyn Clipboard,
735                    shell: &mut Shell<'_, Message>,
736                ) {
737                    let mut publish = |action| shell.publish(on_edit(action));
738
739                    match binding {
740                        Binding::Unfocus => {
741                            state.focus = None;
742                            state.drag_click = None;
743                        }
744                        Binding::Copy => {
745                            if let Some(selection) = content.selection() {
746                                clipboard.write(
747                                    clipboard::Kind::Standard,
748                                    selection,
749                                );
750                            }
751                        }
752                        Binding::Cut => {
753                            if let Some(selection) = content.selection() {
754                                clipboard.write(
755                                    clipboard::Kind::Standard,
756                                    selection,
757                                );
758
759                                publish(Action::Edit(Edit::Delete));
760                            }
761                        }
762                        Binding::Paste => {
763                            if let Some(contents) =
764                                clipboard.read(clipboard::Kind::Standard)
765                            {
766                                publish(Action::Edit(Edit::Paste(Arc::new(
767                                    contents,
768                                ))));
769                            }
770                        }
771                        Binding::Move(motion) => {
772                            publish(Action::Move(motion));
773                        }
774                        Binding::Select(motion) => {
775                            publish(Action::Select(motion));
776                        }
777                        Binding::SelectWord => {
778                            publish(Action::SelectWord);
779                        }
780                        Binding::SelectLine => {
781                            publish(Action::SelectLine);
782                        }
783                        Binding::SelectAll => {
784                            publish(Action::SelectAll);
785                        }
786                        Binding::Insert(c) => {
787                            publish(Action::Edit(Edit::Insert(c)));
788                        }
789                        Binding::Enter => {
790                            publish(Action::Edit(Edit::Enter));
791                        }
792                        Binding::Backspace => {
793                            publish(Action::Edit(Edit::Backspace));
794                        }
795                        Binding::Delete => {
796                            publish(Action::Edit(Edit::Delete));
797                        }
798                        Binding::Sequence(sequence) => {
799                            for binding in sequence {
800                                apply_binding(
801                                    binding, content, state, on_edit,
802                                    clipboard, shell,
803                                );
804                            }
805                        }
806                        Binding::Custom(message) => {
807                            shell.publish(message);
808                        }
809                    }
810                }
811
812                apply_binding(
813                    binding,
814                    self.content,
815                    state,
816                    on_edit,
817                    clipboard,
818                    shell,
819                );
820
821                if let Some(focus) = &mut state.focus {
822                    focus.updated_at = Instant::now();
823                }
824            }
825        }
826
827        event::Status::Captured
828    }
829
830    fn draw(
831        &self,
832        tree: &widget::Tree,
833        renderer: &mut Renderer,
834        theme: &Theme,
835        _defaults: &renderer::Style,
836        layout: Layout<'_>,
837        cursor: mouse::Cursor,
838        _viewport: &Rectangle,
839    ) {
840        let bounds = layout.bounds();
841
842        let mut internal = self.content.0.borrow_mut();
843        let state = tree.state.downcast_ref::<State<Highlighter>>();
844
845        let font = self.font.unwrap_or_else(|| renderer.default_font());
846
847        internal.editor.highlight(
848            font,
849            state.highlighter.borrow_mut().deref_mut(),
850            |highlight| (self.highlighter_format)(highlight, theme),
851        );
852
853        let is_disabled = self.on_edit.is_none();
854        let is_mouse_over = cursor.is_over(bounds);
855
856        let status = if is_disabled {
857            Status::Disabled
858        } else if state.focus.is_some() {
859            Status::Focused
860        } else if is_mouse_over {
861            Status::Hovered
862        } else {
863            Status::Active
864        };
865
866        let style = theme.style(&self.class, status);
867
868        renderer.fill_quad(
869            renderer::Quad {
870                bounds,
871                border: style.border,
872                ..renderer::Quad::default()
873            },
874            style.background,
875        );
876
877        let text_bounds = bounds.shrink(self.padding);
878
879        if internal.editor.is_empty() {
880            if let Some(placeholder) = self.placeholder.clone() {
881                renderer.fill_text(
882                    Text {
883                        content: placeholder.into_owned(),
884                        bounds: text_bounds.size(),
885                        size: self
886                            .text_size
887                            .unwrap_or_else(|| renderer.default_size()),
888                        line_height: self.line_height,
889                        font,
890                        horizontal_alignment: alignment::Horizontal::Left,
891                        vertical_alignment: alignment::Vertical::Top,
892                        shaping: text::Shaping::Advanced,
893                        wrapping: self.wrapping,
894                    },
895                    text_bounds.position(),
896                    style.placeholder,
897                    text_bounds,
898                );
899            }
900        } else {
901            renderer.fill_editor(
902                &internal.editor,
903                text_bounds.position(),
904                style.value,
905                text_bounds,
906            );
907        }
908
909        let translation = text_bounds.position() - Point::ORIGIN;
910
911        if let Some(focus) = state.focus.as_ref() {
912            match internal.editor.cursor() {
913                Cursor::Caret(position) if focus.is_cursor_visible() => {
914                    let cursor =
915                        Rectangle::new(
916                            position + translation,
917                            Size::new(
918                                1.0,
919                                self.line_height
920                                    .to_absolute(self.text_size.unwrap_or_else(
921                                        || renderer.default_size(),
922                                    ))
923                                    .into(),
924                            ),
925                        );
926
927                    if let Some(clipped_cursor) =
928                        text_bounds.intersection(&cursor)
929                    {
930                        renderer.fill_quad(
931                            renderer::Quad {
932                                bounds: clipped_cursor,
933                                ..renderer::Quad::default()
934                            },
935                            style.value,
936                        );
937                    }
938                }
939                Cursor::Selection(ranges) => {
940                    for range in ranges.into_iter().filter_map(|range| {
941                        text_bounds.intersection(&(range + translation))
942                    }) {
943                        renderer.fill_quad(
944                            renderer::Quad {
945                                bounds: range,
946                                ..renderer::Quad::default()
947                            },
948                            style.selection,
949                        );
950                    }
951                }
952                Cursor::Caret(_) => {}
953            }
954        }
955    }
956
957    fn mouse_interaction(
958        &self,
959        _state: &widget::Tree,
960        layout: Layout<'_>,
961        cursor: mouse::Cursor,
962        _viewport: &Rectangle,
963        _renderer: &Renderer,
964    ) -> mouse::Interaction {
965        let is_disabled = self.on_edit.is_none();
966
967        if cursor.is_over(layout.bounds()) {
968            if is_disabled {
969                mouse::Interaction::NotAllowed
970            } else {
971                mouse::Interaction::Text
972            }
973        } else {
974            mouse::Interaction::default()
975        }
976    }
977
978    fn operate(
979        &self,
980        tree: &mut widget::Tree,
981        _layout: Layout<'_>,
982        _renderer: &Renderer,
983        operation: &mut dyn widget::Operation,
984    ) {
985        let state = tree.state.downcast_mut::<State<Highlighter>>();
986
987        operation.focusable(state, self.id.as_ref().map(|id| &id.0));
988    }
989
990    fn id(&self) -> Option<widget::Id> {
991        self.id.as_ref().map(|id| id.0.clone())
992    }
993    fn set_id(&mut self, id: widget::Id) {
994        self.id = Some(Id(id));
995    }
996}
997
998impl<'a, Highlighter, Message, Theme, Renderer>
999    From<TextEditor<'a, Highlighter, Message, Theme, Renderer>>
1000    for Element<'a, Message, Theme, Renderer>
1001where
1002    Highlighter: text::Highlighter,
1003    Message: 'a,
1004    Theme: Catalog + 'a,
1005    Renderer: text::Renderer,
1006{
1007    fn from(
1008        text_editor: TextEditor<'a, Highlighter, Message, Theme, Renderer>,
1009    ) -> Self {
1010        Self::new(text_editor)
1011    }
1012}
1013
1014/// A binding to an action in the [`TextEditor`].
1015#[derive(Debug, Clone, PartialEq)]
1016pub enum Binding<Message> {
1017    /// Unfocus the [`TextEditor`].
1018    Unfocus,
1019    /// Copy the selection of the [`TextEditor`].
1020    Copy,
1021    /// Cut the selection of the [`TextEditor`].
1022    Cut,
1023    /// Paste the clipboard contents in the [`TextEditor`].
1024    Paste,
1025    /// Apply a [`Motion`].
1026    Move(Motion),
1027    /// Select text with a given [`Motion`].
1028    Select(Motion),
1029    /// Select the word at the current cursor.
1030    SelectWord,
1031    /// Select the line at the current cursor.
1032    SelectLine,
1033    /// Select the entire buffer.
1034    SelectAll,
1035    /// Insert the given character.
1036    Insert(char),
1037    /// Break the current line.
1038    Enter,
1039    /// Delete the previous character.
1040    Backspace,
1041    /// Delete the next character.
1042    Delete,
1043    /// A sequence of bindings to execute.
1044    Sequence(Vec<Self>),
1045    /// Produce the given message.
1046    Custom(Message),
1047}
1048
1049/// A key press.
1050#[derive(Debug, Clone, PartialEq, Eq)]
1051pub struct KeyPress {
1052    /// The key pressed.
1053    pub key: keyboard::Key,
1054    /// The state of the keyboard modifiers.
1055    pub modifiers: keyboard::Modifiers,
1056    /// The text produced by the key press.
1057    pub text: Option<SmolStr>,
1058    /// The current [`Status`] of the [`TextEditor`].
1059    pub status: Status,
1060}
1061
1062impl<Message> Binding<Message> {
1063    /// Returns the default [`Binding`] for the given key press.
1064    pub fn from_key_press(event: KeyPress) -> Option<Self> {
1065        let KeyPress {
1066            key,
1067            modifiers,
1068            text,
1069            status,
1070        } = event;
1071
1072        if status != Status::Focused {
1073            return None;
1074        }
1075
1076        match key.as_ref() {
1077            keyboard::Key::Named(key::Named::Enter) => Some(Self::Enter),
1078            keyboard::Key::Named(key::Named::Backspace) => {
1079                Some(Self::Backspace)
1080            }
1081            keyboard::Key::Named(key::Named::Delete)
1082                if text.is_none() || text.as_deref() == Some("\u{7f}") =>
1083            {
1084                Some(Self::Delete)
1085            }
1086            keyboard::Key::Named(key::Named::Escape) => Some(Self::Unfocus),
1087            keyboard::Key::Character("c") if modifiers.command() => {
1088                Some(Self::Copy)
1089            }
1090            keyboard::Key::Character("x") if modifiers.command() => {
1091                Some(Self::Cut)
1092            }
1093            keyboard::Key::Character("v")
1094                if modifiers.command() && !modifiers.alt() =>
1095            {
1096                Some(Self::Paste)
1097            }
1098            keyboard::Key::Character("a") if modifiers.command() => {
1099                Some(Self::SelectAll)
1100            }
1101            _ => {
1102                if let Some(text) = text {
1103                    let c = text.chars().find(|c| !c.is_control())?;
1104
1105                    Some(Self::Insert(c))
1106                } else if let keyboard::Key::Named(named_key) = key.as_ref() {
1107                    let motion = motion(named_key)?;
1108
1109                    let motion = if modifiers.macos_command() {
1110                        match motion {
1111                            Motion::Left => Motion::Home,
1112                            Motion::Right => Motion::End,
1113                            _ => motion,
1114                        }
1115                    } else {
1116                        motion
1117                    };
1118
1119                    let motion = if modifiers.jump() {
1120                        motion.widen()
1121                    } else {
1122                        motion
1123                    };
1124
1125                    Some(if modifiers.shift() {
1126                        Self::Select(motion)
1127                    } else {
1128                        Self::Move(motion)
1129                    })
1130                } else {
1131                    None
1132                }
1133            }
1134        }
1135    }
1136}
1137
1138enum Update<Message> {
1139    Click(mouse::Click),
1140    Drag(Point),
1141    Release,
1142    Scroll(f32),
1143    Binding(Binding<Message>),
1144}
1145
1146impl<Message> Update<Message> {
1147    fn from_event<H: Highlighter>(
1148        event: Event,
1149        state: &State<H>,
1150        bounds: Rectangle,
1151        padding: Padding,
1152        cursor: mouse::Cursor,
1153        key_binding: Option<&dyn Fn(KeyPress) -> Option<Binding<Message>>>,
1154    ) -> Option<Self> {
1155        let binding = |binding| Some(Update::Binding(binding));
1156
1157        match event {
1158            Event::Mouse(event) => match event {
1159                mouse::Event::ButtonPressed(mouse::Button::Left) => {
1160                    if let Some(cursor_position) = cursor.position_in(bounds) {
1161                        let cursor_position = cursor_position
1162                            - Vector::new(padding.top, padding.left);
1163
1164                        let click = mouse::Click::new(
1165                            cursor_position,
1166                            mouse::Button::Left,
1167                            state.last_click,
1168                        );
1169
1170                        Some(Update::Click(click))
1171                    } else if state.focus.is_some() {
1172                        binding(Binding::Unfocus)
1173                    } else {
1174                        None
1175                    }
1176                }
1177                mouse::Event::ButtonReleased(mouse::Button::Left) => {
1178                    Some(Update::Release)
1179                }
1180                mouse::Event::CursorMoved { .. } => match state.drag_click {
1181                    Some(mouse::click::Kind::Single) => {
1182                        let cursor_position = cursor.position_in(bounds)?
1183                            - Vector::new(padding.top, padding.left);
1184
1185                        Some(Update::Drag(cursor_position))
1186                    }
1187                    _ => None,
1188                },
1189                mouse::Event::WheelScrolled { delta }
1190                    if cursor.is_over(bounds) =>
1191                {
1192                    Some(Update::Scroll(match delta {
1193                        mouse::ScrollDelta::Lines { y, .. } => {
1194                            if y.abs() > 0.0 {
1195                                y.signum() * -(y.abs() * 4.0).max(1.0)
1196                            } else {
1197                                0.0
1198                            }
1199                        }
1200                        mouse::ScrollDelta::Pixels { y, .. } => -y / 4.0,
1201                    }))
1202                }
1203                _ => None,
1204            },
1205            Event::Keyboard(keyboard::Event::KeyPressed {
1206                key,
1207                modifiers,
1208                text,
1209                ..
1210            }) => {
1211                let status = if state.focus.is_some() {
1212                    Status::Focused
1213                } else {
1214                    Status::Active
1215                };
1216
1217                let key_press = KeyPress {
1218                    key,
1219                    modifiers,
1220                    text,
1221                    status,
1222                };
1223
1224                if let Some(key_binding) = key_binding {
1225                    key_binding(key_press)
1226                } else {
1227                    Binding::from_key_press(key_press)
1228                }
1229                .map(Self::Binding)
1230            }
1231            _ => None,
1232        }
1233    }
1234}
1235
1236fn motion(key: key::Named) -> Option<Motion> {
1237    match key {
1238        key::Named::ArrowLeft => Some(Motion::Left),
1239        key::Named::ArrowRight => Some(Motion::Right),
1240        key::Named::ArrowUp => Some(Motion::Up),
1241        key::Named::ArrowDown => Some(Motion::Down),
1242        key::Named::Home => Some(Motion::Home),
1243        key::Named::End => Some(Motion::End),
1244        key::Named::PageUp => Some(Motion::PageUp),
1245        key::Named::PageDown => Some(Motion::PageDown),
1246        _ => None,
1247    }
1248}
1249
1250/// The possible status of a [`TextEditor`].
1251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1252pub enum Status {
1253    /// The [`TextEditor`] can be interacted with.
1254    Active,
1255    /// The [`TextEditor`] is being hovered.
1256    Hovered,
1257    /// The [`TextEditor`] is focused.
1258    Focused,
1259    /// The [`TextEditor`] cannot be interacted with.
1260    Disabled,
1261}
1262
1263/// The appearance of a text input.
1264#[derive(Debug, Clone, Copy, PartialEq)]
1265pub struct Style {
1266    /// The [`Background`] of the text input.
1267    pub background: Background,
1268    /// The [`Border`] of the text input.
1269    pub border: Border,
1270    /// The [`Color`] of the icon of the text input.
1271    pub icon: Color,
1272    /// The [`Color`] of the placeholder of the text input.
1273    pub placeholder: Color,
1274    /// The [`Color`] of the value of the text input.
1275    pub value: Color,
1276    /// The [`Color`] of the selection of the text input.
1277    pub selection: Color,
1278}
1279
1280/// The theme catalog of a [`TextEditor`].
1281pub trait Catalog {
1282    /// The item class of the [`Catalog`].
1283    type Class<'a>;
1284
1285    /// The default class produced by the [`Catalog`].
1286    fn default<'a>() -> Self::Class<'a>;
1287
1288    /// The [`Style`] of a class with the given status.
1289    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
1290}
1291
1292/// A styling function for a [`TextEditor`].
1293pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
1294
1295impl Catalog for Theme {
1296    type Class<'a> = StyleFn<'a, Self>;
1297
1298    fn default<'a>() -> Self::Class<'a> {
1299        Box::new(default)
1300    }
1301
1302    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
1303        class(self, status)
1304    }
1305}
1306
1307/// The default style of a [`TextEditor`].
1308pub fn default(theme: &Theme, status: Status) -> Style {
1309    let palette = theme.extended_palette();
1310
1311    let active = Style {
1312        background: Background::Color(palette.background.base.color),
1313        border: Border {
1314            radius: 2.0.into(),
1315            width: 1.0,
1316            color: palette.background.strong.color,
1317        },
1318        icon: palette.background.weak.text,
1319        placeholder: palette.background.strong.color,
1320        value: palette.background.base.text,
1321        selection: palette.primary.weak.color,
1322    };
1323
1324    match status {
1325        Status::Active => active,
1326        Status::Hovered => Style {
1327            border: Border {
1328                color: palette.background.base.text,
1329                ..active.border
1330            },
1331            ..active
1332        },
1333        Status::Focused => Style {
1334            border: Border {
1335                color: palette.primary.strong.color,
1336                ..active.border
1337            },
1338            ..active
1339        },
1340        Status::Disabled => Style {
1341            background: Background::Color(palette.background.weak.color),
1342            value: active.placeholder,
1343            ..active
1344        },
1345    }
1346}