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::layout::{self, Layout};
37use crate::core::length;
38use crate::core::mouse;
39use crate::core::renderer;
40use crate::core::text::editor::{self, Editor as _};
41use crate::core::text::highlighter;
42use crate::core::text::parser;
43use crate::core::text::{self, LineHeight, Text, Wrapping};
44use crate::core::theme;
45use crate::core::widget::{self, Widget};
46use crate::core::window;
47use crate::core::{
48    Background, Border, Color, Element, Event, Font, Length, Padding, Pixels, Rectangle, Shell,
49    Size, Theme,
50};
51
52use std::borrow::Cow;
53use std::cell::RefCell;
54use std::fmt;
55use std::ops::DerefMut;
56
57pub use text::Highlighter;
58pub use text::editor::{
59    Action, Binding, Cursor, Edit, KeyPress, Line, LineEnding, Motion, Selection,
60};
61
62/// A multi-line text input.
63///
64/// # Example
65/// ```no_run
66/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
67/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
68/// #
69/// use iced::widget::text_editor;
70///
71/// struct State {
72///    content: text_editor::Content,
73/// }
74///
75/// #[derive(Debug, Clone)]
76/// enum Message {
77///     Edit(text_editor::Action)
78/// }
79///
80/// fn view(state: &State) -> Element<'_, Message> {
81///     text_editor(&state.content)
82///         .placeholder("Type something here...")
83///         .on_action(Message::Edit)
84///         .into()
85/// }
86///
87/// fn update(state: &mut State, message: Message) {
88///     match message {
89///         Message::Edit(action) => {
90///             state.content.perform(action);
91///         }
92///     }
93/// }
94/// ```
95pub struct TextEditor<'a, Parser, Message, Theme = crate::Theme, Renderer = crate::Renderer>
96where
97    Parser: text::Parser,
98    Theme: Catalog,
99    Renderer: text::Renderer,
100{
101    id: Option<widget::Id>,
102    content: &'a Content<Renderer>,
103    placeholder: Option<text::Fragment<'a>>,
104    font: Option<Font>,
105    text_size: Option<Pixels>,
106    line_height: Option<LineHeight>,
107    width: Length,
108    height: Length,
109    padding: Padding,
110    wrapping: Wrapping,
111    class: Theme::Class<'a>,
112    key_binding: Option<Box<dyn Fn(KeyPress) -> Option<Binding<Message>> + 'a>>,
113    on_edit: Option<Box<dyn Fn(Action) -> Message + 'a>>,
114    parser_settings: Parser::Settings,
115    highlighter: Option<Box<dyn text::Highlighter<Parser::Output, Theme> + 'a>>,
116    last_status: Option<Status>,
117}
118
119impl<'a, Message, Theme, Renderer> TextEditor<'a, parser::PlainText, Message, Theme, Renderer>
120where
121    Theme: Catalog,
122    Renderer: text::Renderer,
123{
124    /// Creates new [`TextEditor`] with the given [`Content`].
125    pub fn new(content: &'a Content<Renderer>) -> Self {
126        Self {
127            id: None,
128            content,
129            placeholder: None,
130            font: None,
131            text_size: None,
132            line_height: None,
133            width: Length::Fill,
134            height: Length::Fit,
135            padding: Padding::new(5.0),
136            wrapping: Wrapping::default(),
137            class: <Theme as Catalog>::default(),
138            key_binding: None,
139            on_edit: None,
140            parser_settings: (),
141            highlighter: None,
142            last_status: None,
143        }
144    }
145}
146
147impl<'a, Message, Renderer> TextEditor<'a, parser::PlainText, Message, crate::Theme, Renderer>
148where
149    Renderer: text::Renderer,
150{
151    /// Highlights the [`TextEditor`] using the given syntax.
152    ///
153    /// ```no_run
154    /// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
155    /// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
156    /// #
157    /// use iced::color;
158    /// use iced::widget::text;
159    /// use iced::widget::text_editor;
160    /// use iced::Theme;
161    ///
162    /// struct State {
163    ///    content: text_editor::Content,
164    /// }
165    ///
166    /// fn view(state: &State) -> Element<'_, ()> {
167    ///     text_editor(&state.content)
168    ///         .highlight("rust")
169    ///         .into()
170    /// }
171    /// ```
172    #[cfg(feature = "highlighter")]
173    pub fn highlight(
174        self,
175        syntax: &str,
176    ) -> TextEditor<'a, iced_highlighter::Parser, Message, crate::Theme, Renderer>
177    where
178        Renderer: text::Renderer,
179    {
180        self.highlight_with(
181            iced_highlighter::Settings {
182                token: syntax.to_owned(),
183            },
184            crate::core::Code::highlight,
185        )
186    }
187}
188
189impl<'a, Parser, Message, Theme, Renderer> TextEditor<'a, Parser, Message, Theme, Renderer>
190where
191    Parser: text::Parser,
192    Theme: Catalog,
193    Renderer: text::Renderer,
194{
195    /// Sets the [`Id`](widget::Id) of the [`TextEditor`].
196    pub fn id(mut self, id: impl Into<widget::Id>) -> Self {
197        self.id = Some(id.into());
198        self
199    }
200
201    /// Sets the placeholder of the [`TextEditor`].
202    pub fn placeholder(mut self, placeholder: impl text::IntoFragment<'a>) -> Self {
203        self.placeholder = Some(placeholder.into_fragment());
204        self
205    }
206
207    /// Sets the width of the [`TextEditor`].
208    pub fn width(mut self, width: impl Into<Pixels>) -> Self {
209        self.width = Length::from(width.into());
210        self
211    }
212
213    /// Sets the height of the [`TextEditor`].
214    pub fn height(mut self, height: impl Into<Length>) -> Self {
215        self.height = height.into();
216        self
217    }
218
219    /// Sets the message that should be produced when some action is performed in
220    /// the [`TextEditor`].
221    ///
222    /// If this method is not called, the [`TextEditor`] will be disabled.
223    pub fn on_action(mut self, on_edit: impl Fn(Action) -> Message + 'a) -> Self {
224        self.on_edit = Some(Box::new(on_edit));
225        self
226    }
227
228    /// Sets the [`Font`] of the [`TextEditor`].
229    ///
230    /// [`Font`]: crate::core::Font
231    pub fn font(mut self, font: impl Into<Font>) -> Self {
232        self.font = Some(font.into());
233        self
234    }
235
236    /// Sets the text size of the [`TextEditor`].
237    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
238        self.text_size = Some(size.into());
239        self
240    }
241
242    /// Sets the [`text::LineHeight`] of the [`TextEditor`].
243    pub fn line_height(mut self, line_height: impl Into<text::LineHeight>) -> Self {
244        self.line_height = Some(line_height.into());
245        self
246    }
247
248    /// Sets the [`Padding`] of the [`TextEditor`].
249    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
250        self.padding = padding.into();
251        self
252    }
253
254    /// Sets the [`Wrapping`] strategy of the [`TextEditor`].
255    pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
256        self.wrapping = wrapping;
257        self
258    }
259
260    /// Highlights the [`TextEditor`] with the given [`text::Parser`] and
261    /// [`text::Highlighter`].
262    pub fn highlight_with<P: text::Parser>(
263        self,
264        settings: P::Settings,
265        highlighter: impl text::Highlighter<P::Output, Theme> + 'a,
266    ) -> TextEditor<'a, P, 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            padding: self.padding,
277            wrapping: self.wrapping,
278            class: self.class,
279            key_binding: self.key_binding,
280            on_edit: self.on_edit,
281            parser_settings: settings,
282            highlighter: Some(Box::new(highlighter)),
283            last_status: self.last_status,
284        }
285    }
286
287    /// Sets the closure to produce key bindings on key presses.
288    ///
289    /// See [`Binding`] for the list of available bindings.
290    pub fn key_binding(
291        mut self,
292        key_binding: impl Fn(KeyPress) -> Option<Binding<Message>> + 'a,
293    ) -> Self {
294        self.key_binding = Some(Box::new(key_binding));
295        self
296    }
297
298    /// Sets the style of the [`TextEditor`].
299    #[must_use]
300    pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
301    where
302        Theme::Class<'a>: From<StyleFn<'a, Theme>>,
303    {
304        self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
305        self
306    }
307
308    /// Sets the style class of the [`TextEditor`].
309    #[cfg(feature = "advanced")]
310    #[must_use]
311    pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
312        self.class = class.into();
313        self
314    }
315}
316
317struct State<Parser: text::Parser> {
318    editor: editor::State,
319    parser: RefCell<Parser>,
320    parser_settings: Parser::Settings,
321    last_theme: RefCell<Option<String>>,
322}
323
324impl<Parser, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
325    for TextEditor<'_, Parser, Message, Theme, Renderer>
326where
327    Parser: text::Parser,
328    Theme: Catalog,
329    Renderer: text::Renderer,
330{
331    fn tag(&self) -> widget::tree::Tag {
332        widget::tree::Tag::of::<State<Parser>>()
333    }
334
335    fn state(&self) -> widget::tree::State {
336        widget::tree::State::new(State {
337            editor: editor::State::new(),
338            parser: RefCell::new(Parser::new(&self.parser_settings)),
339            parser_settings: self.parser_settings.clone(),
340            last_theme: RefCell::new(None),
341        })
342    }
343
344    fn size(&self) -> Size<Length> {
345        Size {
346            width: self.width,
347            height: self.height,
348        }
349    }
350
351    fn layout(
352        &mut self,
353        tree: &mut widget::Tree,
354        renderer: &Renderer,
355        limits: &layout::Limits,
356    ) -> iced_renderer::core::layout::Node {
357        let mut internal = self.content.0.borrow_mut();
358        let state = tree.state.downcast_mut::<State<Parser>>();
359
360        if state.parser_settings != self.parser_settings {
361            state.parser.borrow_mut().update(&self.parser_settings);
362
363            state.parser_settings = self.parser_settings.clone();
364        }
365
366        let limits = limits.width(self.width).height(self.height);
367
368        internal.editor.update(
369            limits.shrink(self.padding).max(),
370            self.font.unwrap_or_else(|| renderer.font()),
371            self.text_size.unwrap_or_else(|| renderer.text_size()),
372            self.line_height.unwrap_or_else(|| renderer.line_height()),
373            self.wrapping,
374            text::Alignment::Default,
375            renderer.hint_factor(),
376            state.parser.borrow_mut().deref_mut(),
377        );
378
379        match self.height {
380            Length::Shrink
381            | Length::Fit
382            | Length::Bounded {
383                sizing: length::Sizing::Fit | length::Sizing::Shrink,
384                ..
385            } => {
386                let min_bounds = internal.editor.min_bounds();
387
388                layout::Node::new(
389                    limits
390                        .height(min_bounds.height)
391                        .max()
392                        .expand(Size::new(0.0, self.padding.y())),
393                )
394            }
395            Length::Fill
396            | Length::FillPortion(_)
397            | Length::Fixed(_)
398            | Length::Bounded { .. }
399            | Length::Fluid(_) => layout::Node::new(limits.max()),
400        }
401    }
402
403    fn update(
404        &mut self,
405        tree: &mut widget::Tree,
406        event: &Event,
407        layout: Layout<'_>,
408        cursor: mouse::Cursor,
409        _renderer: &Renderer,
410        shell: &mut Shell<'_, Message>,
411        _viewport: &Rectangle,
412    ) {
413        let Some(on_edit) = self.on_edit.as_ref() else {
414            return;
415        };
416
417        let state = tree.state.downcast_mut::<State<Parser>>();
418        let is_redraw = matches!(event, Event::Window(window::Event::RedrawRequested(_now)),);
419
420        let editor = &self.content.0.borrow().editor;
421
422        fn apply_update<Message>(
423            update: editor::Update<Message>,
424            shell: &mut Shell<'_, Message>,
425            on_edit: &impl Fn(editor::Action) -> Message,
426        ) {
427            match update {
428                editor::Update::Action(action) => {
429                    shell.publish(on_edit(action));
430                }
431                editor::Update::Release => {}
432                editor::Update::Custom(message) => {
433                    shell.publish(message);
434                }
435                editor::Update::Sequence(updates) => {
436                    for update in updates {
437                        apply_update(update, shell, on_edit);
438                    }
439                }
440                editor::Update::Copy(content) => {
441                    shell.write_clipboard(clipboard::Content::Text(content));
442                }
443                editor::Update::Paste => {
444                    shell.read_clipboard(clipboard::Kind::Text);
445                }
446                editor::Update::RedrawAt(at) => {
447                    shell.request_redraw_at(at);
448                }
449                editor::Update::Focus | editor::Update::Unfocus | editor::Update::InputMethod => {
450                    shell.request_redraw();
451                }
452            }
453        }
454
455        if let Some(update) = state.editor.update(
456            &self.content.0.borrow().editor,
457            event,
458            layout.bounds(),
459            self.padding,
460            cursor,
461            self.key_binding
462                .as_deref()
463                .unwrap_or(&Binding::from_key_press as _),
464        ) {
465            apply_update(update, shell, on_edit);
466        }
467
468        let status = {
469            let is_disabled = self.on_edit.is_none();
470            let is_hovered = cursor.is_over(layout.bounds());
471
472            if is_disabled {
473                Status::Disabled
474            } else if state.editor.is_focused() {
475                Status::Focused { is_hovered }
476            } else if is_hovered {
477                Status::Hovered
478            } else {
479                Status::Active
480            }
481        };
482
483        if is_redraw {
484            self.last_status = Some(status);
485
486            shell.request_input_method(
487                &state
488                    .editor
489                    .input_method(editor, layout.bounds().shrink(self.padding).position()),
490            );
491        } else if self
492            .last_status
493            .is_some_and(|last_status| status != last_status)
494        {
495            shell.request_redraw();
496        }
497    }
498
499    fn draw(
500        &self,
501        tree: &widget::Tree,
502        renderer: &mut Renderer,
503        theme: &Theme,
504        _defaults: &renderer::Style,
505        layout: Layout<'_>,
506        _cursor: mouse::Cursor,
507        viewport: &Rectangle,
508    ) {
509        let bounds = layout.bounds();
510
511        let mut internal = self.content.0.borrow_mut();
512        let state = tree.state.downcast_ref::<State<Parser>>();
513
514        let font = self.font.unwrap_or_else(|| renderer.font());
515
516        let theme_name = theme.name();
517
518        if state
519            .last_theme
520            .borrow()
521            .as_ref()
522            .is_none_or(|last_theme| last_theme != theme_name)
523        {
524            state.parser.borrow_mut().change_line(0);
525            let _ = state.last_theme.borrow_mut().replace(theme_name.to_owned());
526        }
527
528        internal
529            .editor
530            .highlight(font, state.parser.borrow_mut().deref_mut(), |output| {
531                let Some(highlighter) = &self.highlighter else {
532                    return highlighter::Style::default();
533                };
534
535                highlighter.highlight(output, theme)
536            });
537
538        let style = theme.style(&self.class, self.last_status.unwrap_or(Status::Active));
539
540        renderer.fill_quad(
541            renderer::Quad {
542                bounds,
543                border: style.border,
544                ..renderer::Quad::default()
545            },
546            style.background,
547        );
548
549        let text_bounds = bounds.shrink(self.padding);
550
551        if internal.editor.is_empty()
552            && let Some(placeholder) = &self.placeholder
553        {
554            renderer.fill_text(
555                Text {
556                    content: placeholder.clone().into_owned(),
557                    bounds: text_bounds.size(),
558                    size: self.text_size.unwrap_or_else(|| renderer.text_size()),
559                    line_height: self.line_height.unwrap_or_else(|| renderer.line_height()),
560                    font,
561                    align_x: text::Alignment::Default,
562                    align_y: alignment::Vertical::Top,
563                    shaping: text::Shaping::Advanced,
564                    wrapping: self.wrapping,
565                    ellipsis: text::Ellipsis::None,
566                    hint_factor: renderer.hint_factor(),
567                },
568                text_bounds.position(),
569                style.placeholder,
570                text_bounds,
571            );
572        }
573
574        state.editor.draw(
575            &internal.editor,
576            renderer,
577            text_bounds.position(),
578            *viewport,
579            editor::Style {
580                value: style.value,
581                selection: style.selection,
582            },
583        );
584    }
585
586    fn mouse_interaction(
587        &self,
588        _tree: &widget::Tree,
589        layout: Layout<'_>,
590        cursor: mouse::Cursor,
591        _viewport: &Rectangle,
592        _renderer: &Renderer,
593    ) -> mouse::Interaction {
594        let is_disabled = self.on_edit.is_none();
595
596        if cursor.is_over(layout.bounds()) {
597            if is_disabled {
598                mouse::Interaction::NotAllowed
599            } else {
600                mouse::Interaction::Text
601            }
602        } else {
603            mouse::Interaction::default()
604        }
605    }
606
607    fn operate(
608        &mut self,
609        tree: &mut widget::Tree,
610        layout: Layout<'_>,
611        _renderer: &Renderer,
612        operation: &mut dyn widget::Operation,
613    ) {
614        let state = tree.state.downcast_mut::<State<Parser>>();
615
616        operation.focusable(self.id.as_ref(), layout.bounds(), &mut state.editor);
617        operation.text_input(
618            self.id.as_ref(),
619            layout.bounds(),
620            &mut self.content.0.borrow_mut().editor,
621        );
622    }
623}
624
625impl<'a, Parser, Message, Theme, Renderer> From<TextEditor<'a, Parser, Message, Theme, Renderer>>
626    for Element<'a, Message, Theme, Renderer>
627where
628    Parser: text::Parser,
629    Message: 'a,
630    Theme: Catalog + 'a,
631    Renderer: text::Renderer,
632{
633    fn from(text_editor: TextEditor<'a, Parser, Message, Theme, Renderer>) -> Self {
634        Self::new(text_editor)
635    }
636}
637
638/// The content of a [`TextEditor`].
639pub struct Content<R = crate::Renderer>(RefCell<Internal<R>>)
640where
641    R: text::Renderer;
642
643struct Internal<R>
644where
645    R: text::Renderer,
646{
647    editor: R::Editor,
648}
649
650impl<R> Content<R>
651where
652    R: text::Renderer,
653{
654    /// Creates an empty [`Content`].
655    pub fn new() -> Self {
656        Self::with_text("")
657    }
658
659    /// Creates a [`Content`] with the given text.
660    pub fn with_text(text: &str) -> Self {
661        Self(RefCell::new(Internal {
662            editor: R::Editor::with_text(text),
663        }))
664    }
665
666    /// Performs an [`Action`] on the [`Content`].
667    pub fn perform(&mut self, action: Action) {
668        let internal = self.0.get_mut();
669
670        internal.editor.perform(action);
671    }
672
673    /// Moves the current cursor to reflect the given one.
674    pub fn move_to(&mut self, cursor: Cursor) {
675        let internal = self.0.get_mut();
676
677        internal.editor.move_to(cursor);
678    }
679
680    /// Returns the current cursor position of the [`Content`].
681    pub fn cursor(&self) -> Cursor {
682        self.0.borrow().editor.cursor()
683    }
684
685    /// Returns the amount of lines of the [`Content`].
686    pub fn line_count(&self) -> usize {
687        self.0.borrow().editor.line_count()
688    }
689
690    /// Returns the text of the line at the given index, if it exists.
691    pub fn line(&self, index: usize) -> Option<Line<'_>> {
692        let internal = self.0.borrow();
693        let line = internal.editor.line(index)?;
694
695        Some(Line {
696            text: Cow::Owned(line.text.into_owned()),
697            ending: line.ending,
698        })
699    }
700
701    /// Returns an iterator of the text of the lines in the [`Content`].
702    pub fn lines(&self) -> impl Iterator<Item = Line<'_>> {
703        (0..)
704            .map(|i| self.line(i))
705            .take_while(Option::is_some)
706            .flatten()
707    }
708
709    /// Returns the text of the [`Content`].
710    pub fn text(&self) -> String {
711        self.0.borrow().editor.text()
712    }
713
714    /// Returns the selected text of the [`Content`].
715    pub fn selection(&self) -> Option<String> {
716        self.0.borrow().editor.copy()
717    }
718
719    /// Returns the kind of [`LineEnding`] used for separating lines in the [`Content`].
720    pub fn line_ending(&self) -> Option<LineEnding> {
721        Some(self.line(0)?.ending)
722    }
723
724    /// Returns whether or not the the [`Content`] is empty.
725    pub fn is_empty(&self) -> bool {
726        self.0.borrow().editor.is_empty()
727    }
728}
729
730impl<Renderer> Clone for Content<Renderer>
731where
732    Renderer: text::Renderer,
733{
734    fn clone(&self) -> Self {
735        Self::with_text(&self.text())
736    }
737}
738
739impl<Renderer> Default for Content<Renderer>
740where
741    Renderer: text::Renderer,
742{
743    fn default() -> Self {
744        Self::new()
745    }
746}
747
748impl<Renderer> fmt::Debug for Content<Renderer>
749where
750    Renderer: text::Renderer,
751    Renderer::Editor: fmt::Debug,
752{
753    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
754        let internal = self.0.borrow();
755
756        f.debug_struct("Content")
757            .field("editor", &internal.editor)
758            .finish()
759    }
760}
761
762/// The possible status of a [`TextEditor`].
763#[derive(Debug, Clone, Copy, PartialEq, Eq)]
764pub enum Status {
765    /// The [`TextEditor`] can be interacted with.
766    Active,
767    /// The [`TextEditor`] is being hovered.
768    Hovered,
769    /// The [`TextEditor`] is focused.
770    Focused {
771        /// Whether the [`TextEditor`] is hovered, while focused.
772        is_hovered: bool,
773    },
774    /// The [`TextEditor`] cannot be interacted with.
775    Disabled,
776}
777
778/// The appearance of a text input.
779#[derive(Debug, Clone, Copy, PartialEq)]
780pub struct Style {
781    /// The [`Background`] of the text input.
782    pub background: Background,
783    /// The [`Border`] of the text input.
784    pub border: Border,
785    /// The [`Color`] of the placeholder of the text input.
786    pub placeholder: Color,
787    /// The [`Color`] of the value of the text input.
788    pub value: Color,
789    /// The [`Color`] of the selection of the text input.
790    pub selection: Color,
791}
792
793/// The theme catalog of a [`TextEditor`].
794pub trait Catalog: theme::Base {
795    /// The item class of the [`Catalog`].
796    type Class<'a>;
797
798    /// The default class produced by the [`Catalog`].
799    fn default<'a>() -> Self::Class<'a>;
800
801    /// The [`Style`] of a class with the given status.
802    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
803}
804
805/// A styling function for a [`TextEditor`].
806pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme, Status) -> Style + 'a>;
807
808impl Catalog for Theme {
809    type Class<'a> = StyleFn<'a, Self>;
810
811    fn default<'a>() -> Self::Class<'a> {
812        Box::new(default)
813    }
814
815    fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
816        class(self, status)
817    }
818}
819
820/// The default style of a [`TextEditor`].
821pub fn default(theme: &Theme, status: Status) -> Style {
822    let palette = theme.palette();
823
824    let active = Style {
825        background: Background::Color(palette.background.base.color),
826        border: Border {
827            radius: 2.0.into(),
828            width: 1.0,
829            color: palette.background.strong.color,
830        },
831        placeholder: palette.secondary.base.color,
832        value: palette.background.base.text,
833        selection: palette.primary.weak.color,
834    };
835
836    match status {
837        Status::Active => active,
838        Status::Hovered => Style {
839            border: Border {
840                color: palette.background.base.text,
841                ..active.border
842            },
843            ..active
844        },
845        Status::Focused { .. } => Style {
846            border: Border {
847                color: palette.primary.strong.color,
848                ..active.border
849            },
850            ..active
851        },
852        Status::Disabled => Style {
853            background: Background::Color(palette.background.weak.color),
854            value: active.placeholder,
855            placeholder: palette.background.strongest.color,
856            ..active
857        },
858    }
859}