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::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/// A multi-line text input.
64///
65/// # Example
66/// ```no_run
67/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
68/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
69/// #
70/// use iced::widget::text_editor;
71///
72/// struct State {
73///    content: text_editor::Content,
74/// }
75///
76/// #[derive(Debug, Clone)]
77/// enum Message {
78///     Edit(text_editor::Action)
79/// }
80///
81/// fn view(state: &State) -> Element<'_, Message> {
82///     text_editor(&state.content)
83///         .placeholder("Type something here...")
84///         .on_action(Message::Edit)
85///         .into()
86/// }
87///
88/// fn update(state: &mut State, message: Message) {
89///     match message {
90///         Message::Edit(action) => {
91///             state.content.perform(action);
92///         }
93///     }
94/// }
95/// ```
96#[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    /// Creates new [`TextEditor`] with the given [`Content`].
137    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    /// Sets the placeholder of the [`TextEditor`].
170    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    /// Sets the width of the [`TextEditor`].
179    pub fn width(mut self, width: impl Into<Pixels>) -> Self {
180        self.width = Length::from(width.into());
181        self
182    }
183
184    /// Sets the height of the [`TextEditor`].
185    pub fn height(mut self, height: impl Into<Length>) -> Self {
186        self.height = height.into();
187        self
188    }
189
190    /// Sets the minimum height of the [`TextEditor`].
191    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    /// Sets the maximum height of the [`TextEditor`].
197    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    /// Sets the message that should be produced when some action is performed in
203    /// the [`TextEditor`].
204    ///
205    /// If this method is not called, the [`TextEditor`] will be disabled.
206    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    /// Sets the [`Font`] of the [`TextEditor`].
215    ///
216    /// [`Font`]: text::Renderer::Font
217    pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
218        self.font = Some(font.into());
219        self
220    }
221
222    /// Sets the text size of the [`TextEditor`].
223    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
224        self.text_size = Some(size.into());
225        self
226    }
227
228    /// Sets the [`text::LineHeight`] of the [`TextEditor`].
229    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    /// Sets the [`Padding`] of the [`TextEditor`].
238    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
239        self.padding = padding.into();
240        self
241    }
242
243    /// Sets the [`Wrapping`] strategy of the [`TextEditor`].
244    pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
245        self.wrapping = wrapping;
246        self
247    }
248
249    /// Highlights the [`TextEditor`] using the given syntax and theme.
250    #[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    /// Highlights the [`TextEditor`] with the given [`Highlighter`] and
269    /// a strategy to turn its highlights into some text format.
270    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    /// Sets the closure to produce key bindings on key presses.
300    ///
301    /// See [`Binding`] for the list of available bindings.
302    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    /// Sets the style of the [`TextEditor`].
311    #[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    /// Sets the style class of the [`TextEditor`].
321    #[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
370/// The content of a [`TextEditor`].
371pub 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    /// Creates an empty [`Content`].
388    pub fn new() -> Self {
389        Self::with_text("")
390    }
391
392    /// Creates a [`Content`] with the given text.
393    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    /// Performs an [`Action`] on the [`Content`].
401    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    /// Returns the amount of lines of the [`Content`].
409    pub fn line_count(&self) -> usize {
410        self.0.borrow().editor.line_count()
411    }
412
413    /// Returns the text of the line at the given index, if it exists.
414    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    /// Returns an iterator of the text of the lines in the [`Content`].
425    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    /// Returns the text of the [`Content`].
433    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    /// Returns the kind of [`LineEnding`] used for separating lines in the [`Content`].
453    pub fn line_ending(&self) -> Option<LineEnding> {
454        Some(self.line(0)?.ending)
455    }
456
457    /// Returns the selected text of the [`Content`].
458    pub fn selection(&self) -> Option<String> {
459        self.0.borrow().editor.selection()
460    }
461
462    /// Returns the current cursor position of the [`Content`].
463    pub fn cursor_position(&self) -> (usize, usize) {
464        self.0.borrow().editor.cursor_position()
465    }
466}
467
468impl<Renderer> Default for Content<Renderer>
469where
470    Renderer: text::Renderer,
471{
472    fn default() -> Self {
473        Self::new()
474    }
475}
476
477impl<Renderer> fmt::Debug for Content<Renderer>
478where
479    Renderer: text::Renderer,
480    Renderer::Editor: fmt::Debug,
481{
482    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
483        let internal = self.0.borrow();
484
485        f.debug_struct("Content")
486            .field("editor", &internal.editor)
487            .field("is_dirty", &internal.is_dirty)
488            .finish()
489    }
490}
491
492/// The state of a [`TextEditor`].
493#[derive(Debug)]
494pub struct State<Highlighter: text::Highlighter> {
495    focus: Option<Focus>,
496    preedit: Option<input_method::Preedit>,
497    last_click: Option<mouse::Click>,
498    drag_click: Option<mouse::click::Kind>,
499    partial_scroll: f32,
500    highlighter: RefCell<Highlighter>,
501    highlighter_settings: Highlighter::Settings,
502    highlighter_format_address: usize,
503}
504
505#[derive(Debug, Clone)]
506struct Focus {
507    updated_at: Instant,
508    now: Instant,
509    is_window_focused: bool,
510}
511
512impl Focus {
513    const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
514
515    fn now() -> Self {
516        let now = Instant::now();
517
518        Self {
519            updated_at: now,
520            now,
521            is_window_focused: true,
522        }
523    }
524
525    fn is_cursor_visible(&self) -> bool {
526        self.is_window_focused
527            && ((self.now - self.updated_at).as_millis()
528                / Self::CURSOR_BLINK_INTERVAL_MILLIS)
529                % 2
530                == 0
531    }
532}
533
534impl<Highlighter: text::Highlighter> State<Highlighter> {
535    /// Returns whether the [`TextEditor`] is currently focused or not.
536    pub fn is_focused(&self) -> bool {
537        self.focus.is_some()
538    }
539}
540
541impl<Highlighter: text::Highlighter> operation::Focusable
542    for State<Highlighter>
543{
544    fn is_focused(&self) -> bool {
545        self.focus.is_some()
546    }
547
548    fn focus(&mut self) {
549        self.focus = Some(Focus::now());
550    }
551
552    fn unfocus(&mut self) {
553        self.focus = None;
554    }
555}
556
557impl<Highlighter, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
558    for TextEditor<'_, Highlighter, Message, Theme, Renderer>
559where
560    Highlighter: text::Highlighter,
561    Theme: Catalog,
562    Renderer: text::Renderer,
563{
564    fn tag(&self) -> widget::tree::Tag {
565        widget::tree::Tag::of::<State<Highlighter>>()
566    }
567
568    fn state(&self) -> widget::tree::State {
569        widget::tree::State::new(State {
570            focus: None,
571            preedit: None,
572            last_click: None,
573            drag_click: None,
574            partial_scroll: 0.0,
575            highlighter: RefCell::new(Highlighter::new(
576                &self.highlighter_settings,
577            )),
578            highlighter_settings: self.highlighter_settings.clone(),
579            highlighter_format_address: self.highlighter_format as usize,
580        })
581    }
582
583    fn size(&self) -> Size<Length> {
584        Size {
585            width: self.width,
586            height: self.height,
587        }
588    }
589
590    fn layout(
591        &self,
592        tree: &mut widget::Tree,
593        renderer: &Renderer,
594        limits: &layout::Limits,
595    ) -> iced_renderer::core::layout::Node {
596        let mut internal = self.content.0.borrow_mut();
597        let state = tree.state.downcast_mut::<State<Highlighter>>();
598
599        if state.highlighter_format_address != self.highlighter_format as usize
600        {
601            state.highlighter.borrow_mut().change_line(0);
602
603            state.highlighter_format_address = self.highlighter_format as usize;
604        }
605
606        if state.highlighter_settings != self.highlighter_settings {
607            state
608                .highlighter
609                .borrow_mut()
610                .update(&self.highlighter_settings);
611
612            state.highlighter_settings = self.highlighter_settings.clone();
613        }
614
615        let limits = limits
616            .width(self.width)
617            .height(self.height)
618            .min_height(self.min_height)
619            .max_height(self.max_height);
620
621        internal.editor.update(
622            limits.shrink(self.padding).max(),
623            self.font.unwrap_or_else(|| renderer.default_font()),
624            self.text_size.unwrap_or_else(|| renderer.default_size()),
625            self.line_height,
626            self.wrapping,
627            state.highlighter.borrow_mut().deref_mut(),
628        );
629
630        match self.height {
631            Length::Fill | Length::FillPortion(_) | Length::Fixed(_) => {
632                layout::Node::new(limits.max())
633            }
634            Length::Shrink => {
635                let min_bounds = internal.editor.min_bounds();
636
637                layout::Node::new(
638                    limits
639                        .height(min_bounds.height)
640                        .max()
641                        .expand(Size::new(0.0, self.padding.vertical())),
642                )
643            }
644        }
645    }
646
647    fn update(
648        &mut self,
649        tree: &mut widget::Tree,
650        event: &Event,
651        layout: Layout<'_>,
652        cursor: mouse::Cursor,
653        renderer: &Renderer,
654        clipboard: &mut dyn Clipboard,
655        shell: &mut Shell<'_, Message>,
656        _viewport: &Rectangle,
657    ) {
658        let Some(on_edit) = self.on_edit.as_ref() else {
659            return;
660        };
661
662        let state = tree.state.downcast_mut::<State<Highlighter>>();
663        let is_redraw = matches!(
664            event,
665            Event::Window(window::Event::RedrawRequested(_now)),
666        );
667
668        match event {
669            Event::Window(window::Event::Unfocused) => {
670                if let Some(focus) = &mut state.focus {
671                    focus.is_window_focused = false;
672                }
673            }
674            Event::Window(window::Event::Focused) => {
675                if let Some(focus) = &mut state.focus {
676                    focus.is_window_focused = true;
677                    focus.updated_at = Instant::now();
678
679                    shell.request_redraw();
680                }
681            }
682            Event::Window(window::Event::RedrawRequested(now)) => {
683                if let Some(focus) = &mut state.focus {
684                    if focus.is_window_focused {
685                        focus.now = *now;
686
687                        let millis_until_redraw =
688                            Focus::CURSOR_BLINK_INTERVAL_MILLIS
689                                - (focus.now - focus.updated_at).as_millis()
690                                    % Focus::CURSOR_BLINK_INTERVAL_MILLIS;
691
692                        shell.request_redraw_at(
693                            focus.now
694                                + Duration::from_millis(
695                                    millis_until_redraw as u64,
696                                ),
697                        );
698                    }
699                }
700            }
701            _ => {}
702        }
703
704        if let Some(update) = Update::from_event(
705            event,
706            state,
707            layout.bounds(),
708            self.padding,
709            cursor,
710            self.key_binding.as_deref(),
711        ) {
712            match update {
713                Update::Click(click) => {
714                    let action = match click.kind() {
715                        mouse::click::Kind::Single => {
716                            Action::Click(click.position())
717                        }
718                        mouse::click::Kind::Double => Action::SelectWord,
719                        mouse::click::Kind::Triple => Action::SelectLine,
720                    };
721
722                    state.focus = Some(Focus::now());
723                    state.last_click = Some(click);
724                    state.drag_click = Some(click.kind());
725
726                    shell.publish(on_edit(action));
727                    shell.capture_event();
728                }
729                Update::Drag(position) => {
730                    shell.publish(on_edit(Action::Drag(position)));
731                }
732                Update::Release => {
733                    state.drag_click = None;
734                }
735                Update::Scroll(lines) => {
736                    let bounds = self.content.0.borrow().editor.bounds();
737
738                    if bounds.height >= i32::MAX as f32 {
739                        return;
740                    }
741
742                    let lines = lines + state.partial_scroll;
743                    state.partial_scroll = lines.fract();
744
745                    shell.publish(on_edit(Action::Scroll {
746                        lines: lines as i32,
747                    }));
748                    shell.capture_event();
749                }
750                Update::InputMethod(update) => match update {
751                    Ime::Toggle(is_open) => {
752                        state.preedit =
753                            is_open.then(input_method::Preedit::new);
754
755                        shell.request_redraw();
756                    }
757                    Ime::Preedit { content, selection } => {
758                        state.preedit = Some(input_method::Preedit {
759                            content,
760                            selection,
761                            text_size: self.text_size,
762                        });
763
764                        shell.request_redraw();
765                    }
766                    Ime::Commit(text) => {
767                        shell.publish(on_edit(Action::Edit(Edit::Paste(
768                            Arc::new(text),
769                        ))));
770                    }
771                },
772                Update::Binding(binding) => {
773                    fn apply_binding<
774                        H: text::Highlighter,
775                        R: text::Renderer,
776                        Message,
777                    >(
778                        binding: Binding<Message>,
779                        content: &Content<R>,
780                        state: &mut State<H>,
781                        on_edit: &dyn Fn(Action) -> Message,
782                        clipboard: &mut dyn Clipboard,
783                        shell: &mut Shell<'_, Message>,
784                    ) {
785                        let mut publish =
786                            |action| shell.publish(on_edit(action));
787
788                        match binding {
789                            Binding::Unfocus => {
790                                state.focus = None;
791                                state.drag_click = None;
792                            }
793                            Binding::Copy => {
794                                if let Some(selection) = content.selection() {
795                                    clipboard.write(
796                                        clipboard::Kind::Standard,
797                                        selection,
798                                    );
799                                }
800                            }
801                            Binding::Cut => {
802                                if let Some(selection) = content.selection() {
803                                    clipboard.write(
804                                        clipboard::Kind::Standard,
805                                        selection,
806                                    );
807
808                                    publish(Action::Edit(Edit::Delete));
809                                }
810                            }
811                            Binding::Paste => {
812                                if let Some(contents) =
813                                    clipboard.read(clipboard::Kind::Standard)
814                                {
815                                    publish(Action::Edit(Edit::Paste(
816                                        Arc::new(contents),
817                                    )));
818                                }
819                            }
820                            Binding::Move(motion) => {
821                                publish(Action::Move(motion));
822                            }
823                            Binding::Select(motion) => {
824                                publish(Action::Select(motion));
825                            }
826                            Binding::SelectWord => {
827                                publish(Action::SelectWord);
828                            }
829                            Binding::SelectLine => {
830                                publish(Action::SelectLine);
831                            }
832                            Binding::SelectAll => {
833                                publish(Action::SelectAll);
834                            }
835                            Binding::Insert(c) => {
836                                publish(Action::Edit(Edit::Insert(c)));
837                            }
838                            Binding::Enter => {
839                                publish(Action::Edit(Edit::Enter));
840                            }
841                            Binding::Backspace => {
842                                publish(Action::Edit(Edit::Backspace));
843                            }
844                            Binding::Delete => {
845                                publish(Action::Edit(Edit::Delete));
846                            }
847                            Binding::Sequence(sequence) => {
848                                for binding in sequence {
849                                    apply_binding(
850                                        binding, content, state, on_edit,
851                                        clipboard, shell,
852                                    );
853                                }
854                            }
855                            Binding::Custom(message) => {
856                                shell.publish(message);
857                            }
858                        }
859                    }
860
861                    if !matches!(binding, Binding::Unfocus) {
862                        shell.capture_event();
863                    }
864
865                    apply_binding(
866                        binding,
867                        self.content,
868                        state,
869                        on_edit,
870                        clipboard,
871                        shell,
872                    );
873
874                    if let Some(focus) = &mut state.focus {
875                        focus.updated_at = Instant::now();
876                    }
877                }
878            }
879        }
880
881        let status = {
882            let is_disabled = self.on_edit.is_none();
883            let is_hovered = cursor.is_over(layout.bounds());
884
885            if is_disabled {
886                Status::Disabled
887            } else if state.focus.is_some() {
888                Status::Focused { is_hovered }
889            } else if is_hovered {
890                Status::Hovered
891            } else {
892                Status::Active
893            }
894        };
895
896        if is_redraw {
897            self.last_status = Some(status);
898
899            shell.request_input_method(
900                &self.input_method(state, renderer, layout),
901            );
902        } else if self
903            .last_status
904            .is_some_and(|last_status| status != last_status)
905        {
906            shell.request_redraw();
907        }
908    }
909
910    fn draw(
911        &self,
912        tree: &widget::Tree,
913        renderer: &mut Renderer,
914        theme: &Theme,
915        _defaults: &renderer::Style,
916        layout: Layout<'_>,
917        _cursor: mouse::Cursor,
918        _viewport: &Rectangle,
919    ) {
920        let bounds = layout.bounds();
921
922        let mut internal = self.content.0.borrow_mut();
923        let state = tree.state.downcast_ref::<State<Highlighter>>();
924
925        let font = self.font.unwrap_or_else(|| renderer.default_font());
926
927        internal.editor.highlight(
928            font,
929            state.highlighter.borrow_mut().deref_mut(),
930            |highlight| (self.highlighter_format)(highlight, theme),
931        );
932
933        let style = theme
934            .style(&self.class, self.last_status.unwrap_or(Status::Active));
935
936        renderer.fill_quad(
937            renderer::Quad {
938                bounds,
939                border: style.border,
940                ..renderer::Quad::default()
941            },
942            style.background,
943        );
944
945        let text_bounds = bounds.shrink(self.padding);
946
947        if internal.editor.is_empty() {
948            if let Some(placeholder) = self.placeholder.clone() {
949                renderer.fill_text(
950                    Text {
951                        content: placeholder.into_owned(),
952                        bounds: text_bounds.size(),
953                        size: self
954                            .text_size
955                            .unwrap_or_else(|| renderer.default_size()),
956                        line_height: self.line_height,
957                        font,
958                        align_x: text::Alignment::Default,
959                        align_y: alignment::Vertical::Top,
960                        shaping: text::Shaping::Advanced,
961                        wrapping: self.wrapping,
962                    },
963                    text_bounds.position(),
964                    style.placeholder,
965                    text_bounds,
966                );
967            }
968        } else {
969            renderer.fill_editor(
970                &internal.editor,
971                text_bounds.position(),
972                style.value,
973                text_bounds,
974            );
975        }
976
977        let translation = text_bounds.position() - Point::ORIGIN;
978
979        if let Some(focus) = state.focus.as_ref() {
980            match internal.editor.cursor() {
981                Cursor::Caret(position) if focus.is_cursor_visible() => {
982                    let cursor =
983                        Rectangle::new(
984                            position + translation,
985                            Size::new(
986                                1.0,
987                                self.line_height
988                                    .to_absolute(self.text_size.unwrap_or_else(
989                                        || renderer.default_size(),
990                                    ))
991                                    .into(),
992                            ),
993                        );
994
995                    if let Some(clipped_cursor) =
996                        text_bounds.intersection(&cursor)
997                    {
998                        renderer.fill_quad(
999                            renderer::Quad {
1000                                bounds: clipped_cursor,
1001                                ..renderer::Quad::default()
1002                            },
1003                            style.value,
1004                        );
1005                    }
1006                }
1007                Cursor::Selection(ranges) => {
1008                    for range in ranges.into_iter().filter_map(|range| {
1009                        text_bounds.intersection(&(range + translation))
1010                    }) {
1011                        renderer.fill_quad(
1012                            renderer::Quad {
1013                                bounds: range,
1014                                ..renderer::Quad::default()
1015                            },
1016                            style.selection,
1017                        );
1018                    }
1019                }
1020                Cursor::Caret(_) => {}
1021            }
1022        }
1023    }
1024
1025    fn mouse_interaction(
1026        &self,
1027        _state: &widget::Tree,
1028        layout: Layout<'_>,
1029        cursor: mouse::Cursor,
1030        _viewport: &Rectangle,
1031        _renderer: &Renderer,
1032    ) -> mouse::Interaction {
1033        let is_disabled = self.on_edit.is_none();
1034
1035        if cursor.is_over(layout.bounds()) {
1036            if is_disabled {
1037                mouse::Interaction::NotAllowed
1038            } else {
1039                mouse::Interaction::Text
1040            }
1041        } else {
1042            mouse::Interaction::default()
1043        }
1044    }
1045
1046    fn operate(
1047        &self,
1048        tree: &mut widget::Tree,
1049        layout: Layout<'_>,
1050        _renderer: &Renderer,
1051        operation: &mut dyn widget::Operation,
1052    ) {
1053        let state = tree.state.downcast_mut::<State<Highlighter>>();
1054
1055        operation.focusable(None, layout.bounds(), state);
1056    }
1057}
1058
1059impl<'a, Highlighter, Message, Theme, Renderer>
1060    From<TextEditor<'a, Highlighter, Message, Theme, Renderer>>
1061    for Element<'a, Message, Theme, Renderer>
1062where
1063    Highlighter: text::Highlighter,
1064    Message: 'a,
1065    Theme: Catalog + 'a,
1066    Renderer: text::Renderer,
1067{
1068    fn from(
1069        text_editor: TextEditor<'a, Highlighter, Message, Theme, Renderer>,
1070    ) -> Self {
1071        Self::new(text_editor)
1072    }
1073}
1074
1075/// A binding to an action in the [`TextEditor`].
1076#[derive(Debug, Clone, PartialEq)]
1077pub enum Binding<Message> {
1078    /// Unfocus the [`TextEditor`].
1079    Unfocus,
1080    /// Copy the selection of the [`TextEditor`].
1081    Copy,
1082    /// Cut the selection of the [`TextEditor`].
1083    Cut,
1084    /// Paste the clipboard contents in the [`TextEditor`].
1085    Paste,
1086    /// Apply a [`Motion`].
1087    Move(Motion),
1088    /// Select text with a given [`Motion`].
1089    Select(Motion),
1090    /// Select the word at the current cursor.
1091    SelectWord,
1092    /// Select the line at the current cursor.
1093    SelectLine,
1094    /// Select the entire buffer.
1095    SelectAll,
1096    /// Insert the given character.
1097    Insert(char),
1098    /// Break the current line.
1099    Enter,
1100    /// Delete the previous character.
1101    Backspace,
1102    /// Delete the next character.
1103    Delete,
1104    /// A sequence of bindings to execute.
1105    Sequence(Vec<Self>),
1106    /// Produce the given message.
1107    Custom(Message),
1108}
1109
1110/// A key press.
1111#[derive(Debug, Clone, PartialEq, Eq)]
1112pub struct KeyPress {
1113    /// The key pressed.
1114    pub key: keyboard::Key,
1115    /// The state of the keyboard modifiers.
1116    pub modifiers: keyboard::Modifiers,
1117    /// The text produced by the key press.
1118    pub text: Option<SmolStr>,
1119    /// The current [`Status`] of the [`TextEditor`].
1120    pub status: Status,
1121}
1122
1123impl<Message> Binding<Message> {
1124    /// Returns the default [`Binding`] for the given key press.
1125    pub fn from_key_press(event: KeyPress) -> Option<Self> {
1126        let KeyPress {
1127            key,
1128            modifiers,
1129            text,
1130            status,
1131        } = event;
1132
1133        if !matches!(status, Status::Focused { .. }) {
1134            return None;
1135        }
1136
1137        match key.as_ref() {
1138            keyboard::Key::Named(key::Named::Enter) => Some(Self::Enter),
1139            keyboard::Key::Named(key::Named::Backspace) => {
1140                Some(Self::Backspace)
1141            }
1142            keyboard::Key::Named(key::Named::Delete)
1143                if text.is_none() || text.as_deref() == Some("\u{7f}") =>
1144            {
1145                Some(Self::Delete)
1146            }
1147            keyboard::Key::Named(key::Named::Escape) => Some(Self::Unfocus),
1148            keyboard::Key::Character("c") if modifiers.command() => {
1149                Some(Self::Copy)
1150            }
1151            keyboard::Key::Character("x") if modifiers.command() => {
1152                Some(Self::Cut)
1153            }
1154            keyboard::Key::Character("v")
1155                if modifiers.command() && !modifiers.alt() =>
1156            {
1157                Some(Self::Paste)
1158            }
1159            keyboard::Key::Character("a") if modifiers.command() => {
1160                Some(Self::SelectAll)
1161            }
1162            _ => {
1163                if let Some(text) = text {
1164                    let c = text.chars().find(|c| !c.is_control())?;
1165
1166                    Some(Self::Insert(c))
1167                } else if let keyboard::Key::Named(named_key) = key.as_ref() {
1168                    let motion = motion(named_key)?;
1169
1170                    let motion = if modifiers.macos_command() {
1171                        match motion {
1172                            Motion::Left => Motion::Home,
1173                            Motion::Right => Motion::End,
1174                            _ => motion,
1175                        }
1176                    } else {
1177                        motion
1178                    };
1179
1180                    let motion = if modifiers.jump() {
1181                        motion.widen()
1182                    } else {
1183                        motion
1184                    };
1185
1186                    Some(if modifiers.shift() {
1187                        Self::Select(motion)
1188                    } else {
1189                        Self::Move(motion)
1190                    })
1191                } else {
1192                    None
1193                }
1194            }
1195        }
1196    }
1197}
1198
1199enum Update<Message> {
1200    Click(mouse::Click),
1201    Drag(Point),
1202    Release,
1203    Scroll(f32),
1204    InputMethod(Ime),
1205    Binding(Binding<Message>),
1206}
1207
1208enum Ime {
1209    Toggle(bool),
1210    Preedit {
1211        content: String,
1212        selection: Option<Range<usize>>,
1213    },
1214    Commit(String),
1215}
1216
1217impl<Message> Update<Message> {
1218    fn from_event<H: Highlighter>(
1219        event: &Event,
1220        state: &State<H>,
1221        bounds: Rectangle,
1222        padding: Padding,
1223        cursor: mouse::Cursor,
1224        key_binding: Option<&dyn Fn(KeyPress) -> Option<Binding<Message>>>,
1225    ) -> Option<Self> {
1226        let binding = |binding| Some(Update::Binding(binding));
1227
1228        match event {
1229            Event::Mouse(event) => match event {
1230                mouse::Event::ButtonPressed(mouse::Button::Left) => {
1231                    if let Some(cursor_position) = cursor.position_in(bounds) {
1232                        let cursor_position = cursor_position
1233                            - Vector::new(padding.top, padding.left);
1234
1235                        let click = mouse::Click::new(
1236                            cursor_position,
1237                            mouse::Button::Left,
1238                            state.last_click,
1239                        );
1240
1241                        Some(Update::Click(click))
1242                    } else if state.focus.is_some() {
1243                        binding(Binding::Unfocus)
1244                    } else {
1245                        None
1246                    }
1247                }
1248                mouse::Event::ButtonReleased(mouse::Button::Left) => {
1249                    Some(Update::Release)
1250                }
1251                mouse::Event::CursorMoved { .. } => match state.drag_click {
1252                    Some(mouse::click::Kind::Single) => {
1253                        let cursor_position = cursor.position_in(bounds)?
1254                            - Vector::new(padding.top, padding.left);
1255
1256                        Some(Update::Drag(cursor_position))
1257                    }
1258                    _ => None,
1259                },
1260                mouse::Event::WheelScrolled { delta }
1261                    if cursor.is_over(bounds) =>
1262                {
1263                    Some(Update::Scroll(match delta {
1264                        mouse::ScrollDelta::Lines { y, .. } => {
1265                            if y.abs() > 0.0 {
1266                                y.signum() * -(y.abs() * 4.0).max(1.0)
1267                            } else {
1268                                0.0
1269                            }
1270                        }
1271                        mouse::ScrollDelta::Pixels { y, .. } => -y / 4.0,
1272                    }))
1273                }
1274                _ => None,
1275            },
1276            Event::InputMethod(event) => match event {
1277                input_method::Event::Opened | input_method::Event::Closed => {
1278                    Some(Update::InputMethod(Ime::Toggle(matches!(
1279                        event,
1280                        input_method::Event::Opened
1281                    ))))
1282                }
1283                input_method::Event::Preedit(content, selection)
1284                    if state.focus.is_some() =>
1285                {
1286                    Some(Update::InputMethod(Ime::Preedit {
1287                        content: content.clone(),
1288                        selection: selection.clone(),
1289                    }))
1290                }
1291                input_method::Event::Commit(content)
1292                    if state.focus.is_some() =>
1293                {
1294                    Some(Update::InputMethod(Ime::Commit(content.clone())))
1295                }
1296                _ => None,
1297            },
1298            Event::Keyboard(keyboard::Event::KeyPressed {
1299                key,
1300                modifiers,
1301                text,
1302                ..
1303            }) => {
1304                let status = if state.focus.is_some() {
1305                    Status::Focused {
1306                        is_hovered: cursor.is_over(bounds),
1307                    }
1308                } else {
1309                    Status::Active
1310                };
1311
1312                let key_press = KeyPress {
1313                    key: key.clone(),
1314                    modifiers: *modifiers,
1315                    text: text.clone(),
1316                    status,
1317                };
1318
1319                if let Some(key_binding) = key_binding {
1320                    key_binding(key_press)
1321                } else {
1322                    Binding::from_key_press(key_press)
1323                }
1324                .map(Self::Binding)
1325            }
1326            _ => None,
1327        }
1328    }
1329}
1330
1331fn motion(key: key::Named) -> Option<Motion> {
1332    match key {
1333        key::Named::ArrowLeft => Some(Motion::Left),
1334        key::Named::ArrowRight => Some(Motion::Right),
1335        key::Named::ArrowUp => Some(Motion::Up),
1336        key::Named::ArrowDown => Some(Motion::Down),
1337        key::Named::Home => Some(Motion::Home),
1338        key::Named::End => Some(Motion::End),
1339        key::Named::PageUp => Some(Motion::PageUp),
1340        key::Named::PageDown => Some(Motion::PageDown),
1341        _ => None,
1342    }
1343}
1344
1345/// The possible status of a [`TextEditor`].
1346#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1347pub enum Status {
1348    /// The [`TextEditor`] can be interacted with.
1349    Active,
1350    /// The [`TextEditor`] is being hovered.
1351    Hovered,
1352    /// The [`TextEditor`] is focused.
1353    Focused {
1354        /// Whether the [`TextEditor`] is hovered, while focused.
1355        is_hovered: bool,
1356    },
1357    /// The [`TextEditor`] cannot be interacted with.
1358    Disabled,
1359}
1360
1361/// The appearance of a text input.
1362#[derive(Debug, Clone, Copy, PartialEq)]
1363pub struct Style {
1364    /// The [`Background`] of the text input.
1365    pub background: Background,
1366    /// The [`Border`] of the text input.
1367    pub border: Border,
1368    /// The [`Color`] of the icon of the text input.
1369    pub icon: Color,
1370    /// The [`Color`] of the placeholder of the text input.
1371    pub placeholder: Color,
1372    /// The [`Color`] of the value of the text input.
1373    pub value: Color,
1374    /// The [`Color`] of the selection of the text input.
1375    pub selection: Color,
1376}
1377
1378/// The theme catalog of a [`TextEditor`].
1379pub trait Catalog {
1380    /// The item class of the [`Catalog`].
1381    type Class<'a>;
1382
1383    /// The default class produced by the [`Catalog`].
1384    fn default<'a>() -> Self::Class<'a>;
1385
1386    /// The [`Style`] of a class with the given status.
1387    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
1388}
1389
1390/// A styling function for a [`TextEditor`].
1391pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
1392
1393impl Catalog for Theme {
1394    type Class<'a> = StyleFn<'a, Self>;
1395
1396    fn default<'a>() -> Self::Class<'a> {
1397        Box::new(default)
1398    }
1399
1400    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
1401        class(self, status)
1402    }
1403}
1404
1405/// The default style of a [`TextEditor`].
1406pub fn default(theme: &Theme, status: Status) -> Style {
1407    let palette = theme.extended_palette();
1408
1409    let active = Style {
1410        background: Background::Color(palette.background.base.color),
1411        border: Border {
1412            radius: 2.0.into(),
1413            width: 1.0,
1414            color: palette.background.strong.color,
1415        },
1416        icon: palette.background.weak.text,
1417        placeholder: palette.background.strong.color,
1418        value: palette.background.base.text,
1419        selection: palette.primary.weak.color,
1420    };
1421
1422    match status {
1423        Status::Active => active,
1424        Status::Hovered => Style {
1425            border: Border {
1426                color: palette.background.base.text,
1427                ..active.border
1428            },
1429            ..active
1430        },
1431        Status::Focused { .. } => Style {
1432            border: Border {
1433                color: palette.primary.strong.color,
1434                ..active.border
1435            },
1436            ..active
1437        },
1438        Status::Disabled => Style {
1439            background: Background::Color(palette.background.weak.color),
1440            value: active.placeholder,
1441            ..active
1442        },
1443    }
1444}