1use crate::alignment;
24use crate::layout;
25use crate::mouse;
26use crate::renderer;
27use crate::text;
28use crate::text::paragraph::{self, Paragraph};
29use crate::widget::tree::{self, Tree};
30use crate::{Color, Element, Font, Layout, Length, Pixels, Rectangle, Size, Theme, Widget};
31
32pub use text::{Alignment, Ellipsis, LineHeight, Position, Shaping, Wrapping};
33
34#[must_use]
57pub struct Text<'a, Theme>
58where
59 Theme: Catalog,
60{
61 fragment: text::Fragment<'a>,
62 format: Format,
63 class: Theme::Class<'a>,
64}
65
66impl<'a, Theme> Text<'a, Theme>
67where
68 Theme: Catalog,
69{
70 pub fn new(fragment: impl text::IntoFragment<'a>) -> Self {
72 Text {
73 fragment: fragment.into_fragment(),
74 format: Format::default(),
75 class: Theme::default(),
76 }
77 }
78
79 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
81 self.format.size = Some(size.into());
82 self
83 }
84
85 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
87 self.format.line_height = Some(line_height.into());
88 self
89 }
90
91 pub fn font(mut self, font: impl Into<Font>) -> Self {
93 self.format.font = Some(font.into());
94 self
95 }
96
97 pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
99 self.format.font = font.map(Into::into);
100 self
101 }
102
103 pub fn width(mut self, width: impl Into<Length>) -> Self {
105 self.format.width = width.into();
106 self
107 }
108
109 pub fn height(mut self, height: impl Into<Length>) -> Self {
111 self.format.height = height.into();
112 self
113 }
114
115 pub fn center(self) -> Self {
117 self.align_x(alignment::Horizontal::Center)
118 .align_y(alignment::Vertical::Center)
119 }
120
121 pub fn align_x(mut self, alignment: impl Into<text::Alignment>) -> Self {
123 self.format.align_x = alignment.into();
124 self
125 }
126
127 pub fn align_y(mut self, alignment: impl Into<alignment::Vertical>) -> Self {
129 self.format.align_y = alignment.into();
130 self
131 }
132
133 pub fn shaping(mut self, shaping: Shaping) -> Self {
135 self.format.shaping = shaping;
136 self
137 }
138
139 pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
141 self.format.wrapping = wrapping;
142 self
143 }
144
145 pub fn ellipsis(mut self, ellipsis: Ellipsis) -> Self {
147 self.format.ellipsis = ellipsis;
148 self
149 }
150
151 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
153 where
154 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
155 {
156 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
157 self
158 }
159
160 pub fn color(self, color: impl Into<Color>) -> Self
162 where
163 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
164 {
165 self.color_maybe(Some(color))
166 }
167
168 pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self
170 where
171 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
172 {
173 let color = color.map(Into::into);
174
175 self.style(move |_theme| Style { color })
176 }
177
178 #[cfg(feature = "advanced")]
180 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
181 self.class = class.into();
182 self
183 }
184}
185
186pub type State<P> = paragraph::Plain<P>;
188
189impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Text<'_, Theme>
190where
191 Theme: Catalog,
192 Renderer: text::Renderer,
193{
194 fn tag(&self) -> tree::Tag {
195 tree::Tag::of::<State<Renderer::Paragraph>>()
196 }
197
198 fn state(&self) -> tree::State {
199 tree::State::new(paragraph::Plain::<Renderer::Paragraph>::default())
200 }
201
202 fn size(&self) -> Size<Length> {
203 Size {
204 width: self.format.width,
205 height: self.format.height,
206 }
207 }
208
209 fn layout(
210 &mut self,
211 tree: &mut Tree,
212 renderer: &Renderer,
213 limits: &layout::Limits,
214 ) -> layout::Node {
215 layout(
216 tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
217 renderer,
218 limits,
219 &self.fragment,
220 self.format,
221 )
222 }
223
224 fn draw(
225 &self,
226 tree: &Tree,
227 renderer: &mut Renderer,
228 theme: &Theme,
229 defaults: &renderer::Style,
230 layout: Layout<'_>,
231 _cursor_position: mouse::Cursor,
232 viewport: &Rectangle,
233 ) {
234 let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
235 let style = theme.style(&self.class);
236
237 draw(
238 renderer,
239 defaults,
240 layout.bounds(),
241 state.raw(),
242 style,
243 viewport,
244 );
245 }
246
247 fn operate(
248 &mut self,
249 _tree: &mut Tree,
250 layout: Layout<'_>,
251 _viewport: &Rectangle,
252 _renderer: &Renderer,
253 operation: &mut dyn super::Operation,
254 ) {
255 operation.text(None, layout.bounds(), &self.fragment);
256 }
257}
258
259#[derive(Debug, Clone, Copy)]
264#[allow(missing_docs)]
265pub struct Format {
266 pub width: Length,
267 pub height: Length,
268 pub size: Option<Pixels>,
269 pub font: Option<Font>,
270 pub line_height: Option<LineHeight>,
271 pub align_x: text::Alignment,
272 pub align_y: alignment::Vertical,
273 pub shaping: Shaping,
274 pub wrapping: Wrapping,
275 pub ellipsis: Ellipsis,
276}
277
278impl Default for Format {
279 fn default() -> Self {
280 Self {
281 size: None,
282 line_height: None,
283 font: None,
284 width: Length::Shrink,
285 height: Length::Shrink,
286 align_x: text::Alignment::Default,
287 align_y: alignment::Vertical::Top,
288 shaping: Shaping::default(),
289 wrapping: Wrapping::default(),
290 ellipsis: Ellipsis::default(),
291 }
292 }
293}
294
295pub fn layout<Renderer>(
297 paragraph: &mut paragraph::Plain<Renderer::Paragraph>,
298 renderer: &Renderer,
299 limits: &layout::Limits,
300 content: &str,
301 format: Format,
302) -> layout::Node
303where
304 Renderer: text::Renderer,
305{
306 layout::sized(limits, format.width, format.height, |limits| {
307 let bounds = limits.max();
308
309 let size = format.size.unwrap_or_else(|| renderer.text_size());
310 let font = format.font.unwrap_or_else(|| renderer.font());
311 let line_height = format.line_height.unwrap_or_else(|| renderer.line_height());
312
313 let _ = paragraph.update(text::Text {
314 content,
315 bounds,
316 size,
317 line_height,
318 font,
319 align_x: format.align_x,
320 align_y: format.align_y,
321 shaping: format.shaping,
322 wrapping: format.wrapping,
323 ellipsis: format.ellipsis,
324 hint_factor: renderer.hint_factor(),
325 });
326
327 paragraph.min_bounds()
328 })
329}
330
331pub fn draw<Renderer>(
333 renderer: &mut Renderer,
334 style: &renderer::Style,
335 bounds: Rectangle,
336 paragraph: &Renderer::Paragraph,
337 appearance: Style,
338 viewport: &Rectangle,
339) where
340 Renderer: text::Renderer,
341{
342 let anchor = bounds.anchor(
343 paragraph.min_bounds(),
344 paragraph.align_x(),
345 paragraph.align_y(),
346 );
347
348 renderer.fill_paragraph(
349 paragraph,
350 anchor,
351 appearance.color.unwrap_or(style.text_color),
352 *viewport,
353 );
354}
355
356impl<'a, Message, Theme, Renderer> From<Text<'a, Theme>> for Element<'a, Message, Theme, Renderer>
357where
358 Theme: Catalog + 'a,
359 Renderer: text::Renderer + 'a,
360{
361 fn from(text: Text<'a, Theme>) -> Element<'a, Message, Theme, Renderer> {
362 Element::new(text)
363 }
364}
365
366impl<'a, Theme> From<&'a str> for Text<'a, Theme>
367where
368 Theme: Catalog + 'a,
369{
370 fn from(content: &'a str) -> Self {
371 Self::new(content)
372 }
373}
374
375impl<'a, Message, Theme, Renderer> From<&'a str> for Element<'a, Message, Theme, Renderer>
376where
377 Theme: Catalog + 'a,
378 Renderer: text::Renderer + 'a,
379{
380 fn from(content: &'a str) -> Self {
381 Text::from(content).into()
382 }
383}
384
385#[derive(Debug, Clone, Copy, PartialEq, Default)]
387pub struct Style {
388 pub color: Option<Color>,
392}
393
394pub trait Catalog: Sized {
396 type Class<'a>;
398
399 fn default<'a>() -> Self::Class<'a>;
401
402 fn style(&self, item: &Self::Class<'_>) -> Style;
404}
405
406pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
410
411impl Catalog for Theme {
412 type Class<'a> = StyleFn<'a, Self>;
413
414 fn default<'a>() -> Self::Class<'a> {
415 Box::new(|_theme| Style::default())
416 }
417
418 fn style(&self, class: &Self::Class<'_>) -> Style {
419 class(self)
420 }
421}
422
423pub fn default(_theme: &Theme) -> Style {
425 Style { color: None }
426}
427
428pub fn base(theme: &Theme) -> Style {
430 Style {
431 color: Some(theme.seed().text),
432 }
433}
434
435pub fn primary(theme: &Theme) -> Style {
437 Style {
438 color: Some(theme.seed().primary),
439 }
440}
441
442pub fn secondary(theme: &Theme) -> Style {
444 Style {
445 color: Some(theme.palette().secondary.base.color),
446 }
447}
448
449pub fn success(theme: &Theme) -> Style {
451 Style {
452 color: Some(theme.seed().success),
453 }
454}
455
456pub fn warning(theme: &Theme) -> Style {
458 Style {
459 color: Some(theme.seed().warning),
460 }
461}
462
463pub fn danger(theme: &Theme) -> Style {
465 Style {
466 color: Some(theme.seed().danger),
467 }
468}