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