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