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