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 width(mut self, width: impl Into<Length>) -> Self {
104 self.format.width = width.into();
105 self
106 }
107
108 pub fn height(mut self, height: impl Into<Length>) -> Self {
110 self.format.height = height.into();
111 self
112 }
113
114 pub fn center(self) -> Self {
116 self.align_x(alignment::Horizontal::Center)
117 .align_y(alignment::Vertical::Center)
118 }
119
120 pub fn align_x(mut self, alignment: impl Into<text::Alignment>) -> Self {
122 self.format.align_x = alignment.into();
123 self
124 }
125
126 pub fn align_y(
128 mut self,
129 alignment: impl Into<alignment::Vertical>,
130 ) -> Self {
131 self.format.align_y = alignment.into();
132 self
133 }
134
135 pub fn shaping(mut self, shaping: Shaping) -> Self {
137 self.format.shaping = shaping;
138 self
139 }
140
141 pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
143 self.format.wrapping = wrapping;
144 self
145 }
146
147 #[must_use]
149 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
150 where
151 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
152 {
153 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
154 self
155 }
156
157 pub fn color(self, color: impl Into<Color>) -> Self
159 where
160 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
161 {
162 self.color_maybe(Some(color))
163 }
164
165 pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self
167 where
168 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
169 {
170 let color = color.map(Into::into);
171
172 self.style(move |_theme| Style { color })
173 }
174
175 #[cfg(feature = "advanced")]
177 #[must_use]
178 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
179 self.class = class.into();
180 self
181 }
182}
183
184pub type State<P> = paragraph::Plain<P>;
186
187impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
188 for Text<'_, Theme, Renderer>
189where
190 Theme: Catalog,
191 Renderer: text::Renderer,
192{
193 fn tag(&self) -> tree::Tag {
194 tree::Tag::of::<State<Renderer::Paragraph>>()
195 }
196
197 fn state(&self) -> tree::State {
198 tree::State::new(paragraph::Plain::<Renderer::Paragraph>::default())
199 }
200
201 fn size(&self) -> Size<Length> {
202 Size {
203 width: self.format.width,
204 height: self.format.height,
205 }
206 }
207
208 fn layout(
209 &mut self,
210 tree: &mut Tree,
211 renderer: &Renderer,
212 limits: &layout::Limits,
213 ) -> layout::Node {
214 layout(
215 tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
216 renderer,
217 limits,
218 &self.fragment,
219 self.format,
220 )
221 }
222
223 fn draw(
224 &self,
225 tree: &Tree,
226 renderer: &mut Renderer,
227 theme: &Theme,
228 defaults: &renderer::Style,
229 layout: Layout<'_>,
230 _cursor_position: mouse::Cursor,
231 viewport: &Rectangle,
232 ) {
233 let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
234 let style = theme.style(&self.class);
235
236 draw(
237 renderer,
238 defaults,
239 layout.bounds(),
240 state.raw(),
241 style,
242 viewport,
243 );
244 }
245
246 fn operate(
247 &mut self,
248 _state: &mut Tree,
249 layout: Layout<'_>,
250 _renderer: &Renderer,
251 operation: &mut dyn super::Operation,
252 ) {
253 operation.text(None, layout.bounds(), &self.fragment);
254 }
255}
256
257#[derive(Debug, Clone, Copy)]
262#[allow(missing_docs)]
263pub struct Format<Font> {
264 pub width: Length,
265 pub height: Length,
266 pub size: Option<Pixels>,
267 pub font: Option<Font>,
268 pub line_height: LineHeight,
269 pub align_x: text::Alignment,
270 pub align_y: alignment::Vertical,
271 pub shaping: Shaping,
272 pub wrapping: Wrapping,
273}
274
275impl<Font> Default for Format<Font> {
276 fn default() -> Self {
277 Self {
278 size: None,
279 line_height: LineHeight::default(),
280 font: None,
281 width: Length::Shrink,
282 height: Length::Shrink,
283 align_x: text::Alignment::Default,
284 align_y: alignment::Vertical::Top,
285 shaping: Shaping::default(),
286 wrapping: Wrapping::default(),
287 }
288 }
289}
290
291pub fn layout<Renderer>(
293 paragraph: &mut paragraph::Plain<Renderer::Paragraph>,
294 renderer: &Renderer,
295 limits: &layout::Limits,
296 content: &str,
297 format: Format<Renderer::Font>,
298) -> layout::Node
299where
300 Renderer: text::Renderer,
301{
302 layout::sized(limits, format.width, format.height, |limits| {
303 let bounds = limits.max();
304
305 let size = format.size.unwrap_or_else(|| renderer.default_size());
306 let font = format.font.unwrap_or_else(|| renderer.default_font());
307
308 let _ = paragraph.update(text::Text {
309 content,
310 bounds,
311 size,
312 line_height: format.line_height,
313 font,
314 align_x: format.align_x,
315 align_y: format.align_y,
316 shaping: format.shaping,
317 wrapping: format.wrapping,
318 });
319
320 paragraph.min_bounds()
321 })
322}
323
324pub fn draw<Renderer>(
326 renderer: &mut Renderer,
327 style: &renderer::Style,
328 bounds: Rectangle,
329 paragraph: &Renderer::Paragraph,
330 appearance: Style,
331 viewport: &Rectangle,
332) where
333 Renderer: text::Renderer,
334{
335 let anchor = bounds.anchor(
336 paragraph.min_bounds(),
337 paragraph.align_x(),
338 paragraph.align_y(),
339 );
340
341 renderer.fill_paragraph(
342 paragraph,
343 anchor,
344 appearance.color.unwrap_or(style.text_color),
345 *viewport,
346 );
347}
348
349impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>>
350 for Element<'a, Message, Theme, Renderer>
351where
352 Theme: Catalog + 'a,
353 Renderer: text::Renderer + 'a,
354{
355 fn from(
356 text: Text<'a, Theme, Renderer>,
357 ) -> 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>
373 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}