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, Layout, Length, Pixels, Rectangle, Size, Theme, Widget};
31
32pub use text::{Alignment, LineHeight, Shaping, Wrapping};
33
34pub struct Text<'a, Theme, Renderer>
57where
58 Theme: Catalog,
59 Renderer: text::Renderer,
60{
61 fragment: text::Fragment<'a>,
62 format: Format<Renderer::Font>,
63 class: Theme::Class<'a>,
64}
65
66impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
67where
68 Theme: Catalog,
69 Renderer: text::Renderer,
70{
71 pub fn new(fragment: impl text::IntoFragment<'a>) -> Self {
73 Text {
74 fragment: fragment.into_fragment(),
75 format: Format::default(),
76 class: Theme::default(),
77 }
78 }
79
80 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
82 self.format.size = Some(size.into());
83 self
84 }
85
86 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
88 self.format.line_height = line_height.into();
89 self
90 }
91
92 pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
96 self.format.font = Some(font.into());
97 self
98 }
99
100 pub fn font_maybe(mut self, font: Option<impl Into<Renderer::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 #[must_use]
152 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 #[must_use]
181 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
182 self.class = class.into();
183 self
184 }
185}
186
187pub type State<P> = paragraph::Plain<P>;
189
190impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Text<'_, Theme, Renderer>
191where
192 Theme: Catalog,
193 Renderer: text::Renderer,
194{
195 fn tag(&self) -> tree::Tag {
196 tree::Tag::of::<State<Renderer::Paragraph>>()
197 }
198
199 fn state(&self) -> tree::State {
200 tree::State::new(paragraph::Plain::<Renderer::Paragraph>::default())
201 }
202
203 fn size(&self) -> Size<Length> {
204 Size {
205 width: self.format.width,
206 height: self.format.height,
207 }
208 }
209
210 fn layout(
211 &mut self,
212 tree: &mut Tree,
213 renderer: &Renderer,
214 limits: &layout::Limits,
215 ) -> layout::Node {
216 layout(
217 tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
218 renderer,
219 limits,
220 &self.fragment,
221 self.format,
222 )
223 }
224
225 fn draw(
226 &self,
227 tree: &Tree,
228 renderer: &mut Renderer,
229 theme: &Theme,
230 defaults: &renderer::Style,
231 layout: Layout<'_>,
232 _cursor_position: mouse::Cursor,
233 viewport: &Rectangle,
234 ) {
235 let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
236 let style = theme.style(&self.class);
237
238 draw(
239 renderer,
240 defaults,
241 layout.bounds(),
242 state.raw(),
243 style,
244 viewport,
245 );
246 }
247
248 fn operate(
249 &mut self,
250 _tree: &mut Tree,
251 layout: Layout<'_>,
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<Font> {
266 pub width: Length,
267 pub height: Length,
268 pub size: Option<Pixels>,
269 pub font: Option<Font>,
270 pub line_height: LineHeight,
271 pub align_x: text::Alignment,
272 pub align_y: alignment::Vertical,
273 pub shaping: Shaping,
274 pub wrapping: Wrapping,
275}
276
277impl<Font> Default for Format<Font> {
278 fn default() -> Self {
279 Self {
280 size: None,
281 line_height: LineHeight::default(),
282 font: None,
283 width: Length::Shrink,
284 height: Length::Shrink,
285 align_x: text::Alignment::Default,
286 align_y: alignment::Vertical::Top,
287 shaping: Shaping::default(),
288 wrapping: Wrapping::default(),
289 }
290 }
291}
292
293pub fn layout<Renderer>(
295 paragraph: &mut paragraph::Plain<Renderer::Paragraph>,
296 renderer: &Renderer,
297 limits: &layout::Limits,
298 content: &str,
299 format: Format<Renderer::Font>,
300) -> layout::Node
301where
302 Renderer: text::Renderer,
303{
304 layout::sized(limits, format.width, format.height, |limits| {
305 let bounds = limits.max();
306
307 let size = format.size.unwrap_or_else(|| renderer.default_size());
308 let font = format.font.unwrap_or_else(|| renderer.default_font());
309
310 let _ = paragraph.update(text::Text {
311 content,
312 bounds,
313 size,
314 line_height: format.line_height,
315 font,
316 align_x: format.align_x,
317 align_y: format.align_y,
318 shaping: format.shaping,
319 wrapping: format.wrapping,
320 });
321
322 paragraph.min_bounds()
323 })
324}
325
326pub fn draw<Renderer>(
328 renderer: &mut Renderer,
329 style: &renderer::Style,
330 bounds: Rectangle,
331 paragraph: &Renderer::Paragraph,
332 appearance: Style,
333 viewport: &Rectangle,
334) where
335 Renderer: text::Renderer,
336{
337 let anchor = bounds.anchor(
338 paragraph.min_bounds(),
339 paragraph.align_x(),
340 paragraph.align_y(),
341 );
342
343 renderer.fill_paragraph(
344 paragraph,
345 anchor,
346 appearance.color.unwrap_or(style.text_color),
347 *viewport,
348 );
349}
350
351impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>>
352 for Element<'a, Message, Theme, Renderer>
353where
354 Theme: Catalog + 'a,
355 Renderer: text::Renderer + 'a,
356{
357 fn from(text: Text<'a, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> {
358 Element::new(text)
359 }
360}
361
362impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer>
363where
364 Theme: Catalog + 'a,
365 Renderer: text::Renderer,
366{
367 fn from(content: &'a str) -> Self {
368 Self::new(content)
369 }
370}
371
372impl<'a, Message, Theme, Renderer> From<&'a str> for Element<'a, Message, Theme, Renderer>
373where
374 Theme: Catalog + 'a,
375 Renderer: text::Renderer + 'a,
376{
377 fn from(content: &'a str) -> Self {
378 Text::from(content).into()
379 }
380}
381
382#[derive(Debug, Clone, Copy, PartialEq, Default)]
384pub struct Style {
385 pub color: Option<Color>,
389}
390
391pub trait Catalog: Sized {
393 type Class<'a>;
395
396 fn default<'a>() -> Self::Class<'a>;
398
399 fn style(&self, item: &Self::Class<'_>) -> Style;
401}
402
403pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
407
408impl Catalog for Theme {
409 type Class<'a> = StyleFn<'a, Self>;
410
411 fn default<'a>() -> Self::Class<'a> {
412 Box::new(|_theme| Style::default())
413 }
414
415 fn style(&self, class: &Self::Class<'_>) -> Style {
416 class(self)
417 }
418}
419
420pub fn default(_theme: &Theme) -> Style {
422 Style { color: None }
423}
424
425pub fn base(theme: &Theme) -> Style {
427 Style {
428 color: Some(theme.palette().text),
429 }
430}
431
432pub fn primary(theme: &Theme) -> Style {
434 Style {
435 color: Some(theme.palette().primary),
436 }
437}
438
439pub fn secondary(theme: &Theme) -> Style {
441 Style {
442 color: Some(theme.extended_palette().secondary.base.color),
443 }
444}
445
446pub fn success(theme: &Theme) -> Style {
448 Style {
449 color: Some(theme.palette().success),
450 }
451}
452
453pub fn warning(theme: &Theme) -> Style {
455 Style {
456 color: Some(theme.palette().warning),
457 }
458}
459
460pub fn danger(theme: &Theme) -> Style {
462 Style {
463 color: Some(theme.palette().danger),
464 }
465}