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::{
31 Color, Element, Layout, Length, Pixels, Rectangle, Size, Theme, Widget,
32};
33
34pub use text::{Alignment, LineHeight, Shaping, Wrapping};
35
36#[allow(missing_debug_implementations)]
59pub struct Text<'a, Theme, Renderer>
60where
61 Theme: Catalog,
62 Renderer: text::Renderer,
63{
64 fragment: text::Fragment<'a>,
65 format: Format<Renderer::Font>,
66 class: Theme::Class<'a>,
67}
68
69impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
70where
71 Theme: Catalog,
72 Renderer: text::Renderer,
73{
74 pub fn new(fragment: impl text::IntoFragment<'a>) -> Self {
76 Text {
77 fragment: fragment.into_fragment(),
78 format: Format::default(),
79 class: Theme::default(),
80 }
81 }
82
83 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
85 self.format.size = Some(size.into());
86 self
87 }
88
89 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
91 self.format.line_height = line_height.into();
92 self
93 }
94
95 pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
99 self.format.font = Some(font.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(
129 mut self,
130 alignment: impl Into<alignment::Vertical>,
131 ) -> Self {
132 self.format.align_y = alignment.into();
133 self
134 }
135
136 pub fn shaping(mut self, shaping: Shaping) -> Self {
138 self.format.shaping = shaping;
139 self
140 }
141
142 pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
144 self.format.wrapping = wrapping;
145 self
146 }
147
148 #[must_use]
150 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
151 where
152 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
153 {
154 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
155 self
156 }
157
158 pub fn color(self, color: impl Into<Color>) -> Self
160 where
161 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
162 {
163 self.color_maybe(Some(color))
164 }
165
166 pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self
168 where
169 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
170 {
171 let color = color.map(Into::into);
172
173 self.style(move |_theme| Style { color })
174 }
175
176 #[cfg(feature = "advanced")]
178 #[must_use]
179 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
180 self.class = class.into();
181 self
182 }
183}
184
185pub type State<P> = paragraph::Plain<P>;
187
188impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
189 for Text<'_, Theme, Renderer>
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 &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 &self,
249 _state: &mut Tree,
250 layout: Layout<'_>,
251 _renderer: &Renderer,
252 operation: &mut dyn super::Operation,
253 ) {
254 operation.text(None, layout.bounds(), &self.fragment);
255 }
256}
257
258#[derive(Debug, Clone, Copy)]
263#[allow(missing_docs)]
264pub struct Format<Font> {
265 pub width: Length,
266 pub height: Length,
267 pub size: Option<Pixels>,
268 pub font: Option<Font>,
269 pub line_height: LineHeight,
270 pub align_x: text::Alignment,
271 pub align_y: alignment::Vertical,
272 pub shaping: Shaping,
273 pub wrapping: Wrapping,
274}
275
276impl<Font> Default for Format<Font> {
277 fn default() -> Self {
278 Self {
279 size: None,
280 line_height: LineHeight::default(),
281 font: None,
282 width: Length::Shrink,
283 height: Length::Shrink,
284 align_x: text::Alignment::Default,
285 align_y: alignment::Vertical::Top,
286 shaping: Shaping::default(),
287 wrapping: Wrapping::default(),
288 }
289 }
290}
291
292pub fn layout<Renderer>(
294 paragraph: &mut paragraph::Plain<Renderer::Paragraph>,
295 renderer: &Renderer,
296 limits: &layout::Limits,
297 content: &str,
298 format: Format<Renderer::Font>,
299) -> layout::Node
300where
301 Renderer: text::Renderer,
302{
303 layout::sized(limits, format.width, format.height, |limits| {
304 let bounds = limits.max();
305
306 let size = format.size.unwrap_or_else(|| renderer.default_size());
307 let font = format.font.unwrap_or_else(|| renderer.default_font());
308
309 let _ = paragraph.update(text::Text {
310 content,
311 bounds,
312 size,
313 line_height: format.line_height,
314 font,
315 align_x: format.align_x,
316 align_y: format.align_y,
317 shaping: format.shaping,
318 wrapping: format.wrapping,
319 });
320
321 paragraph.min_bounds()
322 })
323}
324
325pub fn draw<Renderer>(
327 renderer: &mut Renderer,
328 style: &renderer::Style,
329 bounds: Rectangle,
330 paragraph: &Renderer::Paragraph,
331 appearance: Style,
332 viewport: &Rectangle,
333) where
334 Renderer: text::Renderer,
335{
336 let anchor = bounds.anchor(
337 paragraph.min_bounds(),
338 paragraph.align_x(),
339 paragraph.align_y(),
340 );
341
342 renderer.fill_paragraph(
343 paragraph,
344 anchor,
345 appearance.color.unwrap_or(style.text_color),
346 *viewport,
347 );
348}
349
350impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>>
351 for Element<'a, Message, Theme, Renderer>
352where
353 Theme: Catalog + 'a,
354 Renderer: text::Renderer + 'a,
355{
356 fn from(
357 text: Text<'a, Theme, Renderer>,
358 ) -> 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>
374 for Element<'a, Message, Theme, Renderer>
375where
376 Theme: Catalog + 'a,
377 Renderer: text::Renderer + 'a,
378{
379 fn from(content: &'a str) -> Self {
380 Text::from(content).into()
381 }
382}
383
384#[derive(Debug, Clone, Copy, PartialEq, Default)]
386pub struct Style {
387 pub color: Option<Color>,
391}
392
393pub trait Catalog: Sized {
395 type Class<'a>;
397
398 fn default<'a>() -> Self::Class<'a>;
400
401 fn style(&self, item: &Self::Class<'_>) -> Style;
403}
404
405pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
409
410impl Catalog for Theme {
411 type Class<'a> = StyleFn<'a, Self>;
412
413 fn default<'a>() -> Self::Class<'a> {
414 Box::new(|_theme| Style::default())
415 }
416
417 fn style(&self, class: &Self::Class<'_>) -> Style {
418 class(self)
419 }
420}
421
422pub fn default(_theme: &Theme) -> Style {
424 Style { color: None }
425}
426
427pub fn base(theme: &Theme) -> Style {
429 Style {
430 color: Some(theme.palette().text),
431 }
432}
433
434pub fn primary(theme: &Theme) -> Style {
436 Style {
437 color: Some(theme.palette().primary),
438 }
439}
440
441pub fn secondary(theme: &Theme) -> Style {
443 Style {
444 color: Some(theme.extended_palette().secondary.strong.color),
445 }
446}
447
448pub fn success(theme: &Theme) -> Style {
450 Style {
451 color: Some(theme.palette().success),
452 }
453}
454
455pub fn danger(theme: &Theme) -> Style {
457 Style {
458 color: Some(theme.palette().danger),
459 }
460}