1use 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
62pub 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 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 #[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 pub fn id(mut self, id: impl Into<widget::Id>) -> Self {
197 self.id = Some(id.into());
198 self
199 }
200
201 pub fn placeholder(mut self, placeholder: impl text::IntoFragment<'a>) -> Self {
203 self.placeholder = Some(placeholder.into_fragment());
204 self
205 }
206
207 pub fn width(mut self, width: impl Into<Pixels>) -> Self {
209 self.width = Length::from(width.into());
210 self
211 }
212
213 pub fn height(mut self, height: impl Into<Length>) -> Self {
215 self.height = height.into();
216 self
217 }
218
219 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 pub fn font(mut self, font: impl Into<Font>) -> Self {
232 self.font = Some(font.into());
233 self
234 }
235
236 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
238 self.text_size = Some(size.into());
239 self
240 }
241
242 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 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
250 self.padding = padding.into();
251 self
252 }
253
254 pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
256 self.wrapping = wrapping;
257 self
258 }
259
260 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 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 #[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 #[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
638pub 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 pub fn new() -> Self {
656 Self::with_text("")
657 }
658
659 pub fn with_text(text: &str) -> Self {
661 Self(RefCell::new(Internal {
662 editor: R::Editor::with_text(text),
663 }))
664 }
665
666 pub fn perform(&mut self, action: Action) {
668 let internal = self.0.get_mut();
669
670 internal.editor.perform(action);
671 }
672
673 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 pub fn cursor(&self) -> Cursor {
682 self.0.borrow().editor.cursor()
683 }
684
685 pub fn line_count(&self) -> usize {
687 self.0.borrow().editor.line_count()
688 }
689
690 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 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 pub fn text(&self) -> String {
711 self.0.borrow().editor.text()
712 }
713
714 pub fn selection(&self) -> Option<String> {
716 self.0.borrow().editor.copy()
717 }
718
719 pub fn line_ending(&self) -> Option<LineEnding> {
721 Some(self.line(0)?.ending)
722 }
723
724 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
764pub enum Status {
765 Active,
767 Hovered,
769 Focused {
771 is_hovered: bool,
773 },
774 Disabled,
776}
777
778#[derive(Debug, Clone, Copy, PartialEq)]
780pub struct Style {
781 pub background: Background,
783 pub border: Border,
785 pub placeholder: Color,
787 pub value: Color,
789 pub selection: Color,
791}
792
793pub trait Catalog: theme::Base {
795 type Class<'a>;
797
798 fn default<'a>() -> Self::Class<'a>;
800
801 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
803}
804
805pub 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
820pub 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}