Skip to main content

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