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