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 hint_factor: renderer.scale_factor(),
321 });
322
323 paragraph.min_bounds()
324 })
325}
326
327pub fn draw<Renderer>(
329 renderer: &mut Renderer,
330 style: &renderer::Style,
331 bounds: Rectangle,
332 paragraph: &Renderer::Paragraph,
333 appearance: Style,
334 viewport: &Rectangle,
335) where
336 Renderer: text::Renderer,
337{
338 let anchor = bounds.anchor(
339 paragraph.min_bounds(),
340 paragraph.align_x(),
341 paragraph.align_y(),
342 );
343
344 renderer.fill_paragraph(
345 paragraph,
346 anchor,
347 appearance.color.unwrap_or(style.text_color),
348 *viewport,
349 );
350}
351
352impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>>
353 for Element<'a, Message, Theme, Renderer>
354where
355 Theme: Catalog + 'a,
356 Renderer: text::Renderer + 'a,
357{
358 fn from(text: Text<'a, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> {
359 Element::new(text)
360 }
361}
362
363impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer>
364where
365 Theme: Catalog + 'a,
366 Renderer: text::Renderer,
367{
368 fn from(content: &'a str) -> Self {
369 Self::new(content)
370 }
371}
372
373impl<'a, Message, Theme, Renderer> From<&'a str> for Element<'a, Message, Theme, Renderer>
374where
375 Theme: Catalog + 'a,
376 Renderer: text::Renderer + 'a,
377{
378 fn from(content: &'a str) -> Self {
379 Text::from(content).into()
380 }
381}
382
383#[derive(Debug, Clone, Copy, PartialEq, Default)]
385pub struct Style {
386 pub color: Option<Color>,
390}
391
392pub trait Catalog: Sized {
394 type Class<'a>;
396
397 fn default<'a>() -> Self::Class<'a>;
399
400 fn style(&self, item: &Self::Class<'_>) -> Style;
402}
403
404pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
408
409impl Catalog for Theme {
410 type Class<'a> = StyleFn<'a, Self>;
411
412 fn default<'a>() -> Self::Class<'a> {
413 Box::new(|_theme| Style::default())
414 }
415
416 fn style(&self, class: &Self::Class<'_>) -> Style {
417 class(self)
418 }
419}
420
421pub fn default(_theme: &Theme) -> Style {
423 Style { color: None }
424}
425
426pub fn base(theme: &Theme) -> Style {
428 Style {
429 color: Some(theme.palette().text),
430 }
431}
432
433pub fn primary(theme: &Theme) -> Style {
435 Style {
436 color: Some(theme.palette().primary),
437 }
438}
439
440pub fn secondary(theme: &Theme) -> Style {
442 Style {
443 color: Some(theme.extended_palette().secondary.base.color),
444 }
445}
446
447pub fn success(theme: &Theme) -> Style {
449 Style {
450 color: Some(theme.palette().success),
451 }
452}
453
454pub fn warning(theme: &Theme) -> Style {
456 Style {
457 color: Some(theme.palette().warning),
458 }
459}
460
461pub fn danger(theme: &Theme) -> Style {
463 Style {
464 color: Some(theme.palette().danger),
465 }
466}