1use crate::alignment;
24use crate::layout;
25use crate::mouse;
26use crate::renderer;
27use crate::text::paragraph::{self, Paragraph};
28use crate::text::{self, Target};
29use crate::widget;
30use crate::widget::operation;
31use crate::widget::tree::{self, Tree};
32use crate::window;
33use crate::{Color, Event, Font, Layout, Length, Pixels, Point, Rectangle, Size, Theme, Widget};
34
35pub use text::{Alignment, Ellipsis, LineHeight, Position, Shaping, Wrapping};
36
37#[must_use]
60pub struct Text<'a, Theme>
61where
62 Theme: Catalog,
63{
64 fragment: text::Fragment<'a>,
65 format: Format,
66 selectable: bool,
67 class: Theme::Class<'a>,
68}
69
70impl<'a, Theme> Text<'a, Theme>
71where
72 Theme: Catalog,
73{
74 pub fn new(fragment: impl text::IntoFragment<'a>) -> Self {
76 Text {
77 fragment: fragment.into_fragment(),
78 format: Format::default(),
79 selectable: false,
80 class: Theme::default(),
81 }
82 }
83
84 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
86 self.format.size = Some(size.into());
87 self
88 }
89
90 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
92 self.format.line_height = Some(line_height.into());
93 self
94 }
95
96 pub fn font(mut self, font: impl Into<Font>) -> Self {
98 self.format.font = Some(font.into());
99 self
100 }
101
102 pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
104 self.format.font = font.map(Into::into);
105 self
106 }
107
108 pub fn width(mut self, width: impl Into<Length>) -> Self {
110 self.format.width = width.into();
111 self
112 }
113
114 pub fn height(mut self, height: impl Into<Length>) -> Self {
116 self.format.height = height.into();
117 self
118 }
119
120 pub fn center(self) -> Self {
122 self.align_x(alignment::Horizontal::Center)
123 .align_y(alignment::Vertical::Center)
124 }
125
126 pub fn align_x(mut self, alignment: impl Into<text::Alignment>) -> Self {
128 self.format.align_x = alignment.into();
129 self
130 }
131
132 pub fn align_y(mut self, alignment: impl Into<alignment::Vertical>) -> Self {
134 self.format.align_y = alignment.into();
135 self
136 }
137
138 pub fn shaping(mut self, shaping: Shaping) -> Self {
140 self.format.shaping = shaping;
141 self
142 }
143
144 pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
146 self.format.wrapping = wrapping;
147 self
148 }
149
150 pub fn ellipsis(mut self, ellipsis: Ellipsis) -> Self {
152 self.format.ellipsis = ellipsis;
153 self
154 }
155
156 pub fn selectable(mut self, selectable: bool) -> Self {
160 self.selectable = selectable;
161 self
162 }
163
164 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
166 where
167 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
168 {
169 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
170 self
171 }
172
173 pub fn color(self, color: impl Into<Color>) -> Self
175 where
176 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
177 {
178 self.color_maybe(Some(color))
179 }
180
181 pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self
183 where
184 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
185 {
186 let color = color.map(Into::into);
187
188 self.style(move |_theme| Style {
189 color,
190 selection: None,
191 })
192 }
193
194 #[cfg(feature = "advanced")]
196 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
197 self.class = class.into();
198 self
199 }
200}
201
202impl<Theme> widget::Meta for Text<'_, Theme> where Theme: Catalog {}
203
204impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Text<'_, Theme>
205where
206 Theme: Catalog,
207 Renderer: text::Renderer,
208{
209 fn size(&self) -> Size<Length> {
210 Size {
211 width: self.format.width,
212 height: self.format.height,
213 }
214 }
215
216 fn tag(&self) -> tree::Tag {
217 tree::Tag::of::<State<Renderer::Paragraph>>()
218 }
219
220 fn state(&self) -> tree::State {
221 tree::State::new(State::<Renderer::Paragraph>::default())
222 }
223
224 fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) {
225 let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
226
227 tree.size = layout(
228 &mut state.paragraph,
229 renderer,
230 limits,
231 &self.fragment,
232 self.format,
233 );
234 }
235
236 fn update(
237 &mut self,
238 tree: &mut Tree,
239 event: &crate::Event,
240 layout: Layout,
241 cursor: mouse::Cursor,
242 _renderer: &Renderer,
243 _shell: &mut crate::Shell<'_, Message>,
244 _viewport: &Rectangle,
245 ) {
246 update::<Renderer::Paragraph>(tree, event, layout, cursor);
247 }
248
249 fn mouse_interaction(
250 &self,
251 tree: &Tree,
252 _layout: Layout,
253 _cursor: mouse::Cursor,
254 _viewport: &Rectangle,
255 _renderer: &Renderer,
256 ) -> mouse::Interaction {
257 if self.selectable {
258 let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
259
260 if state.is_hovered {
261 return mouse::Interaction::Text;
262 }
263 }
264
265 mouse::Interaction::None
266 }
267
268 fn draw(
269 &self,
270 tree: &Tree,
271 renderer: &mut Renderer,
272 theme: &Theme,
273 defaults: &renderer::Style,
274 layout: Layout,
275 _cursor_position: mouse::Cursor,
276 viewport: &Rectangle,
277 ) {
278 let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
279 let style = theme.style(&self.class);
280
281 draw(
282 renderer,
283 defaults,
284 layout.bounds(),
285 state.paragraph.raw(),
286 style,
287 theme.selection(),
288 viewport,
289 );
290 }
291
292 fn operate(
293 &mut self,
294 tree: &mut Tree,
295 layout: Layout,
296 _viewport: &Rectangle,
297 _renderer: &Renderer,
298 operation: &mut dyn super::Operation,
299 ) {
300 let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
301 operation.text(
302 None,
303 layout.bounds(),
304 &mut Operand {
305 paragraph: &mut state.paragraph,
306 layout,
307 selectable: self.selectable,
308 },
309 );
310 }
311}
312
313impl widget::Meta for &str {}
314
315impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for &str
316where
317 Theme: Catalog,
318 Renderer: text::Renderer,
319{
320 fn size(&self) -> Size<Length> {
321 Size {
322 width: Length::Fit,
323 height: Length::Fit,
324 }
325 }
326
327 fn tag(&self) -> tree::Tag {
328 tree::Tag::of::<State<Renderer::Paragraph>>()
329 }
330
331 fn state(&self) -> tree::State {
332 tree::State::new(State::<Renderer::Paragraph>::default())
333 }
334
335 fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) {
336 let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
337
338 tree.size = layout(
339 &mut state.paragraph,
340 renderer,
341 limits,
342 self,
343 Format::default(),
344 );
345 }
346
347 fn draw(
348 &self,
349 tree: &Tree,
350 renderer: &mut Renderer,
351 theme: &Theme,
352 defaults: &renderer::Style,
353 layout: Layout,
354 _cursor_position: mouse::Cursor,
355 viewport: &Rectangle,
356 ) {
357 let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
358 let style = theme.style(&Theme::default());
359
360 draw(
361 renderer,
362 defaults,
363 layout.bounds(),
364 state.paragraph.raw(),
365 style,
366 theme.selection(),
367 viewport,
368 );
369 }
370
371 fn operate(
372 &mut self,
373 tree: &mut Tree,
374 layout: Layout,
375 _viewport: &Rectangle,
376 _renderer: &Renderer,
377 operation: &mut dyn super::Operation,
378 ) {
379 let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>();
380 operation.text(
381 None,
382 layout.bounds(),
383 &mut Operand {
384 paragraph: &mut state.paragraph,
385 layout,
386 selectable: false,
387 },
388 );
389 }
390}
391
392#[derive(Default)]
393struct State<P: Paragraph> {
394 paragraph: paragraph::Plain<P>,
395 is_hovered: bool,
396}
397
398pub struct Operand<'a, P: Paragraph> {
400 pub paragraph: &'a mut paragraph::Plain<P>,
402
403 pub layout: Layout,
405
406 pub selectable: bool,
408}
409
410impl<P: Paragraph> operation::Text for Operand<'_, P> {
411 fn text(&self) -> text::Fragment<'_> {
412 self.paragraph.content().into()
413 }
414
415 fn select(&mut self, start: Point, end: Point, target: Target) {
416 if !self.selectable {
417 return;
418 }
419
420 let anchor = self.layout.bounds().anchor(
421 self.paragraph.min_bounds(),
422 self.paragraph.align_x(),
423 self.paragraph.align_y(),
424 );
425
426 let translation = anchor - Point::ORIGIN;
427
428 self.paragraph
429 .raw_mut()
430 .select(start - translation, end - translation, target);
431 }
432
433 fn select_all(&mut self) {
434 if !self.selectable {
435 return;
436 }
437
438 self.paragraph.raw_mut().select_all();
439 }
440
441 fn deselect(&mut self) {
442 self.paragraph.raw_mut().deselect();
443 }
444
445 fn copy(&mut self) -> Option<String> {
446 self.paragraph.raw_mut().copy()
447 }
448}
449
450#[derive(Debug, Clone, Copy)]
455#[allow(missing_docs)]
456pub struct Format {
457 pub width: Length,
458 pub height: Length,
459 pub size: Option<Pixels>,
460 pub font: Option<Font>,
461 pub line_height: Option<LineHeight>,
462 pub align_x: text::Alignment,
463 pub align_y: alignment::Vertical,
464 pub shaping: Shaping,
465 pub wrapping: Wrapping,
466 pub ellipsis: Ellipsis,
467}
468
469impl Default for Format {
470 fn default() -> Self {
471 Self {
472 size: None,
473 line_height: None,
474 font: None,
475 width: Length::Fit,
476 height: Length::Fit,
477 align_x: text::Alignment::Default,
478 align_y: alignment::Vertical::Top,
479 shaping: Shaping::default(),
480 wrapping: Wrapping::default(),
481 ellipsis: Ellipsis::default(),
482 }
483 }
484}
485
486fn update<P: Paragraph + 'static>(
487 tree: &mut Tree,
488 event: &crate::Event,
489 layout: Layout,
490 cursor: mouse::Cursor,
491) {
492 match event {
493 Event::Mouse(mouse::Event::CursorMoved { .. })
494 | Event::Window(window::Event::RedrawRequested(_)) => {
495 let state = tree.state.downcast_mut::<State<P>>();
496
497 let Some(position) = cursor.position_in(layout.bounds()) else {
498 state.is_hovered = false;
499 return;
500 };
501
502 let anchor = Rectangle::with_size(layout.size()).anchor(
503 state.paragraph.min_bounds(),
504 state.paragraph.align_x(),
505 state.paragraph.align_y(),
506 );
507
508 let translation = anchor - Point::ORIGIN;
509
510 state.is_hovered = state.paragraph.raw().hit_glyph(position - translation);
511 }
512 _ => {}
513 }
514}
515
516pub fn layout<Renderer>(
518 paragraph: &mut paragraph::Plain<Renderer::Paragraph>,
519 renderer: &Renderer,
520 limits: &layout::Limits,
521 content: &str,
522 format: Format,
523) -> Size
524where
525 Renderer: text::Renderer,
526{
527 layout::sized(limits, format.width, format.height, |limits| {
528 let bounds = limits.bounds();
529
530 let size = format.size.unwrap_or_else(|| renderer.text_size());
531 let font = format.font.unwrap_or_else(|| renderer.font());
532 let line_height = format.line_height.unwrap_or_else(|| renderer.line_height());
533
534 let _ = paragraph.update(text::Text {
535 content,
536 bounds,
537 size,
538 line_height,
539 font,
540 align_x: format.align_x,
541 align_y: format.align_y,
542 shaping: format.shaping,
543 wrapping: format.wrapping,
544 ellipsis: format.ellipsis,
545 hint_factor: renderer.hint_factor(),
546 });
547
548 paragraph.min_bounds()
549 })
550}
551
552pub fn draw<Renderer>(
554 renderer: &mut Renderer,
555 style: &renderer::Style,
556 bounds: Rectangle,
557 paragraph: &Renderer::Paragraph,
558 appearance: Style,
559 selection_color: Color,
560 viewport: &Rectangle,
561) where
562 Renderer: text::Renderer,
563{
564 let anchor = bounds.anchor(
565 paragraph.min_bounds(),
566 paragraph.align_x(),
567 paragraph.align_y(),
568 );
569
570 renderer.fill_paragraph(
571 paragraph,
572 anchor,
573 appearance.color.unwrap_or(style.text_color),
574 *viewport,
575 );
576
577 let selection = paragraph.selection();
578
579 if !selection.is_empty() {
580 let translation = anchor - Point::ORIGIN;
581
582 for region in selection {
583 renderer.fill_quad(
584 renderer::Quad {
585 bounds: *region + translation,
586 ..renderer::Quad::default()
587 },
588 appearance.selection.unwrap_or(selection_color),
589 );
590 }
591 }
592}
593
594impl<'a, Theme> From<&'a str> for Text<'a, Theme>
595where
596 Theme: Catalog + 'a,
597{
598 fn from(content: &'a str) -> Self {
599 Self::new(content)
600 }
601}
602
603#[derive(Debug, Clone, Copy, PartialEq, Default)]
605pub struct Style {
606 pub color: Option<Color>,
610
611 pub selection: Option<Color>,
615}
616
617pub trait Catalog: Sized {
619 type Class<'a>;
621
622 fn default<'a>() -> Self::Class<'a>;
624
625 fn style(&self, item: &Self::Class<'_>) -> Style;
627
628 fn selection(&self) -> Color;
630}
631
632pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
636
637impl Catalog for Theme {
638 type Class<'a> = StyleFn<'a, Self>;
639
640 fn default<'a>() -> Self::Class<'a> {
641 Box::new(|_theme| Style::default())
642 }
643
644 fn selection(&self) -> Color {
645 self.palette().background.strongest.color
646 }
647
648 fn style(&self, class: &Self::Class<'_>) -> Style {
649 class(self)
650 }
651}
652
653pub fn default(_theme: &Theme) -> Style {
655 Style {
656 color: None,
657 selection: None,
658 }
659}
660
661pub fn base(theme: &Theme) -> Style {
663 Style {
664 color: Some(theme.seed().text),
665 selection: None,
666 }
667}
668
669pub fn primary(theme: &Theme) -> Style {
671 Style {
672 color: Some(theme.seed().primary),
673 selection: None,
674 }
675}
676
677pub fn secondary(theme: &Theme) -> Style {
679 Style {
680 color: Some(theme.palette().secondary.base.color),
681 selection: None,
682 }
683}
684
685pub fn success(theme: &Theme) -> Style {
687 Style {
688 color: Some(theme.seed().success),
689 selection: None,
690 }
691}
692
693pub fn warning(theme: &Theme) -> Style {
695 Style {
696 color: Some(theme.seed().warning),
697 selection: None,
698 }
699}
700
701pub fn danger(theme: &Theme) -> Style {
703 Style {
704 color: Some(theme.seed().danger),
705 selection: None,
706 }
707}