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