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