iced_widget/
text_editor.rs

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