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