1use crate::alignment;
24use crate::layout;
25use crate::mouse;
26use crate::renderer;
27use crate::text::paragraph::{self, Paragraph};
28use crate::text::{self, Fragment};
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::{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: Fragment<'a>,
66 id: crate::widget::Id,
67 size: Option<Pixels>,
68 line_height: LineHeight,
69 width: Length,
70 height: Length,
71 horizontal_alignment: alignment::Horizontal,
72 vertical_alignment: alignment::Vertical,
73 font: Option<Renderer::Font>,
74 shaping: Shaping,
75 wrapping: Wrapping,
76 class: Theme::Class<'a>,
77}
78
79impl<'a, Theme, Renderer> Text<'a, Theme, Renderer>
80where
81 Theme: Catalog,
82 Renderer: text::Renderer,
83{
84 pub fn new(fragment: impl text::IntoFragment<'a>) -> Self {
86 Text {
87 fragment: fragment.into_fragment(),
88 id: crate::widget::Id::unique(),
89 size: None,
90 line_height: LineHeight::default(),
91 font: None,
92 width: Length::Shrink,
93 height: Length::Shrink,
94 horizontal_alignment: alignment::Horizontal::Left,
95 vertical_alignment: alignment::Vertical::Top,
96 shaping: Shaping::default(),
97 wrapping: Wrapping::default(),
98 class: Theme::default(),
99 }
100 }
101
102 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
104 self.size = Some(size.into());
105 self
106 }
107
108 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
110 self.line_height = line_height.into();
111 self
112 }
113
114 pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
118 self.font = Some(font.into());
119 self
120 }
121
122 pub fn width(mut self, width: impl Into<Length>) -> Self {
124 self.width = width.into();
125 self
126 }
127
128 pub fn height(mut self, height: impl Into<Length>) -> Self {
130 self.height = height.into();
131 self
132 }
133
134 pub fn center(self) -> Self {
136 self.align_x(alignment::Horizontal::Center)
137 .align_y(alignment::Vertical::Center)
138 }
139
140 pub fn align_x(
142 mut self,
143 alignment: impl Into<alignment::Horizontal>,
144 ) -> Self {
145 self.horizontal_alignment = alignment.into();
146 self
147 }
148
149 pub fn align_y(
151 mut self,
152 alignment: impl Into<alignment::Vertical>,
153 ) -> Self {
154 self.vertical_alignment = alignment.into();
155 self
156 }
157
158 pub fn shaping(mut self, shaping: Shaping) -> Self {
160 self.shaping = shaping;
161 self
162 }
163
164 pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
166 self.wrapping = wrapping;
167 self
168 }
169
170 #[must_use]
172 pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
173 where
174 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
175 {
176 self.class = (Box::new(style) as StyleFn<'a, Theme>).into();
177 self
178 }
179
180 pub fn color(self, color: impl Into<Color>) -> Self
182 where
183 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
184 {
185 self.color_maybe(Some(color))
186 }
187
188 pub fn color_maybe(self, color: Option<impl Into<Color>>) -> Self
190 where
191 Theme::Class<'a>: From<StyleFn<'a, Theme>>,
192 {
193 let color = color.map(Into::into);
194
195 self.style(move |_theme| Style { color })
196 }
197
198 #[cfg(feature = "advanced")]
200 #[must_use]
201 pub fn class(mut self, class: impl Into<Theme::Class<'a>>) -> Self {
202 self.class = class.into();
203 self
204 }
205}
206
207#[derive(Debug, Default)]
209pub struct State<P: Paragraph>(pub paragraph::Plain<P>);
210
211impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
212 for Text<'a, Theme, Renderer>
213where
214 Theme: Catalog,
215 Renderer: text::Renderer,
216{
217 fn tag(&self) -> tree::Tag {
218 tree::Tag::of::<State<Renderer::Paragraph>>()
219 }
220
221 fn state(&self) -> tree::State {
222 tree::State::new(State::<Renderer::Paragraph>(
223 paragraph::Plain::default(),
224 ))
225 }
226
227 fn size(&self) -> Size<Length> {
228 Size {
229 width: self.width,
230 height: self.height,
231 }
232 }
233
234 fn layout(
235 &self,
236 tree: &mut Tree,
237 renderer: &Renderer,
238 limits: &layout::Limits,
239 ) -> layout::Node {
240 layout(
241 tree.state.downcast_mut::<State<Renderer::Paragraph>>(),
242 renderer,
243 limits,
244 self.width,
245 self.height,
246 &self.fragment,
247 self.line_height,
248 self.size,
249 self.font,
250 self.horizontal_alignment,
251 self.vertical_alignment,
252 self.shaping,
253 self.wrapping,
254 )
255 }
256
257 fn draw(
258 &self,
259 tree: &Tree,
260 renderer: &mut Renderer,
261 theme: &Theme,
262 defaults: &renderer::Style,
263 layout: Layout<'_>,
264 _cursor_position: mouse::Cursor,
265 viewport: &Rectangle,
266 ) {
267 let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
268 let style = theme.style(&self.class);
269
270 draw(renderer, defaults, layout, state.0.raw(), style, viewport);
271 }
272
273 #[cfg(feature = "a11y")]
274 fn a11y_nodes(
275 &self,
276 layout: Layout<'_>,
277 _state: &Tree,
278 _: mouse::Cursor,
279 ) -> iced_accessibility::A11yTree {
280 use iced_accessibility::{
281 accesskit::{Live, NodeBuilder, Rect, Role},
282 A11yTree,
283 };
284
285 let Rectangle {
286 x,
287 y,
288 width,
289 height,
290 } = layout.bounds();
291 let bounds = Rect::new(
292 x as f64,
293 y as f64,
294 (x + width) as f64,
295 (y + height) as f64,
296 );
297
298 let mut node = NodeBuilder::new(Role::Paragraph);
299
300 node.set_name(self.fragment.to_string().into_boxed_str());
302 node.set_bounds(bounds);
303
304 node.set_live(Live::Polite);
306 A11yTree::leaf(node, self.id.clone())
307 }
308
309 fn id(&self) -> Option<crate::widget::Id> {
310 Some(self.id.clone())
311 }
312
313 fn set_id(&mut self, id: crate::widget::Id) {
314 self.id = id;
315 }
316}
317
318pub fn layout<Renderer>(
320 state: &mut State<Renderer::Paragraph>,
321 renderer: &Renderer,
322 limits: &layout::Limits,
323 width: Length,
324 height: Length,
325 content: &str,
326 line_height: LineHeight,
327 size: Option<Pixels>,
328 font: Option<Renderer::Font>,
329 horizontal_alignment: alignment::Horizontal,
330 vertical_alignment: alignment::Vertical,
331 shaping: Shaping,
332 wrapping: Wrapping,
333) -> layout::Node
334where
335 Renderer: text::Renderer,
336{
337 layout::sized(limits, width, height, |limits| {
338 let bounds = limits.max();
339
340 let size = size.unwrap_or_else(|| renderer.default_size());
341 let font = font.unwrap_or_else(|| renderer.default_font());
342
343 let State(ref mut paragraph) = state;
344
345 paragraph.update(text::Text {
346 content,
347 bounds,
348 size,
349 line_height,
350 font,
351 horizontal_alignment,
352 vertical_alignment,
353 shaping,
354 wrapping,
355 });
356
357 paragraph.min_bounds()
358 })
359}
360
361pub fn draw<Renderer>(
372 renderer: &mut Renderer,
373 style: &renderer::Style,
374 layout: Layout<'_>,
375 paragraph: &Renderer::Paragraph,
376 appearance: Style,
377 viewport: &Rectangle,
378) where
379 Renderer: text::Renderer,
380{
381 let bounds = layout.bounds();
382
383 let x = match paragraph.horizontal_alignment() {
384 alignment::Horizontal::Left => bounds.x,
385 alignment::Horizontal::Center => bounds.center_x(),
386 alignment::Horizontal::Right => bounds.x + bounds.width,
387 };
388
389 let y = match paragraph.vertical_alignment() {
390 alignment::Vertical::Top => bounds.y,
391 alignment::Vertical::Center => bounds.center_y(),
392 alignment::Vertical::Bottom => bounds.y + bounds.height,
393 };
394
395 renderer.fill_paragraph(
396 paragraph,
397 Point::new(x, y),
398 appearance.color.unwrap_or(style.text_color),
399 *viewport,
400 );
401}
402
403impl<'a, Message, Theme, Renderer> From<Text<'a, Theme, Renderer>>
404 for Element<'a, Message, Theme, Renderer>
405where
406 Theme: Catalog + 'a,
407 Renderer: text::Renderer + 'a,
408{
409 fn from(
410 text: Text<'a, Theme, Renderer>,
411 ) -> Element<'a, Message, Theme, Renderer> {
412 Element::new(text)
413 }
414}
415
416impl<'a, Theme, Renderer> From<&'a str> for Text<'a, Theme, Renderer>
440where
441 Theme: Catalog + 'a,
442 Renderer: text::Renderer,
443{
444 fn from(content: &'a str) -> Self {
445 Self::new(content)
446 }
447}
448
449impl<'a, Message, Theme, Renderer> From<&'a str>
450 for Element<'a, Message, Theme, Renderer>
451where
452 Theme: Catalog + 'a,
453 Renderer: text::Renderer + 'a,
454{
455 fn from(content: &'a str) -> Self {
456 Text::from(content).into()
457 }
458}
459
460#[derive(Debug, Clone, Copy, PartialEq, Default)]
462pub struct Style {
463 pub color: Option<Color>,
467}
468
469pub trait Catalog: Sized {
471 type Class<'a>;
473
474 fn default<'a>() -> Self::Class<'a>;
476
477 fn style(&self, item: &Self::Class<'_>) -> Style;
479}
480
481pub type StyleFn<'a, Theme> = Box<dyn Fn(&Theme) -> Style + 'a>;
485
486impl Catalog for Theme {
487 type Class<'a> = StyleFn<'a, Self>;
488
489 fn default<'a>() -> Self::Class<'a> {
490 Box::new(|_theme| Style::default())
491 }
492
493 fn style(&self, class: &Self::Class<'_>) -> Style {
494 class(self)
495 }
496}
497
498pub fn default(_theme: &Theme) -> Style {
500 Style { color: None }
501}
502
503pub fn base(theme: &Theme) -> Style {
505 Style {
506 color: Some(theme.palette().text),
507 }
508}
509
510pub fn primary(theme: &Theme) -> Style {
512 Style {
513 color: Some(theme.palette().primary),
514 }
515}
516
517pub fn secondary(theme: &Theme) -> Style {
519 Style {
520 color: Some(theme.extended_palette().secondary.strong.color),
521 }
522}
523
524pub fn success(theme: &Theme) -> Style {
526 Style {
527 color: Some(theme.palette().success),
528 }
529}
530
531pub fn danger(theme: &Theme) -> Style {
533 Style {
534 color: Some(theme.palette().danger),
535 }
536}