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