1use std::{
4 any::Any,
5 borrow::Cow,
6 cell::RefCell,
7 fmt::Display,
8 rc::Rc,
9};
10
11use freya_engine::prelude::{
12 FontStyle,
13 Paint,
14 PaintStyle,
15 ParagraphBuilder,
16 ParagraphStyle,
17 RectHeightStyle,
18 RectWidthStyle,
19 SkParagraph,
20 SkRect,
21 TextStyle,
22};
23use rustc_hash::FxHashMap;
24use torin::prelude::Size2D;
25
26use crate::{
27 data::{
28 AccessibilityData,
29 CursorStyleData,
30 EffectData,
31 LayoutData,
32 StyleState,
33 TextStyleData,
34 TextStyleState,
35 },
36 diff_key::DiffKey,
37 element::{
38 Element,
39 ElementExt,
40 EventHandlerType,
41 LayoutContext,
42 RenderContext,
43 },
44 events::name::EventName,
45 layers::Layer,
46 prelude::{
47 AccessibilityExt,
48 Color,
49 ContainerExt,
50 EventHandlersExt,
51 KeyExt,
52 LayerExt,
53 LayoutExt,
54 MaybeExt,
55 TextAlign,
56 TextStyleExt,
57 },
58 style::cursor::CursorStyle,
59 text_cache::CachedParagraph,
60 tree::DiffModifies,
61};
62
63pub fn paragraph() -> Paragraph {
76 Paragraph {
77 key: DiffKey::None,
78 element: ParagraphElement::default(),
79 }
80}
81
82pub struct ParagraphHolderInner {
83 pub paragraph: Rc<SkParagraph>,
84 pub scale_factor: f64,
85}
86
87#[derive(Clone)]
88pub struct ParagraphHolder(pub Rc<RefCell<Option<ParagraphHolderInner>>>);
89
90impl PartialEq for ParagraphHolder {
91 fn eq(&self, other: &Self) -> bool {
92 Rc::ptr_eq(&self.0, &other.0)
93 }
94}
95
96impl Default for ParagraphHolder {
97 fn default() -> Self {
98 Self(Rc::new(RefCell::new(None)))
99 }
100}
101
102#[derive(PartialEq, Clone)]
103pub struct ParagraphElement {
104 pub layout: LayoutData,
105 pub spans: Vec<Span<'static>>,
106 pub accessibility: AccessibilityData,
107 pub text_style_data: TextStyleData,
108 pub cursor_style_data: CursorStyleData,
109 pub event_handlers: FxHashMap<EventName, EventHandlerType>,
110 pub sk_paragraph: ParagraphHolder,
111 pub cursor_index: Option<usize>,
112 pub highlights: Vec<(usize, usize)>,
113 pub max_lines: Option<usize>,
114 pub line_height: Option<f32>,
115 pub relative_layer: Layer,
116 pub cursor_style: CursorStyle,
117}
118
119impl Default for ParagraphElement {
120 fn default() -> Self {
121 let mut accessibility = AccessibilityData::default();
122 accessibility.builder.set_role(accesskit::Role::Paragraph);
123 Self {
124 layout: Default::default(),
125 spans: Default::default(),
126 accessibility,
127 text_style_data: Default::default(),
128 cursor_style_data: Default::default(),
129 event_handlers: Default::default(),
130 sk_paragraph: Default::default(),
131 cursor_index: Default::default(),
132 highlights: Default::default(),
133 max_lines: Default::default(),
134 line_height: Default::default(),
135 relative_layer: Default::default(),
136 cursor_style: CursorStyle::default(),
137 }
138 }
139}
140
141impl Display for ParagraphElement {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 f.write_str(
144 &self
145 .spans
146 .iter()
147 .map(|s| s.text.clone())
148 .collect::<Vec<_>>()
149 .join("\n"),
150 )
151 }
152}
153
154impl ElementExt for ParagraphElement {
155 fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
156 let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
157 else {
158 return false;
159 };
160 self != paragraph
161 }
162
163 fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
164 let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
165 else {
166 return DiffModifies::all();
167 };
168
169 let mut diff = DiffModifies::empty();
170
171 if self.spans != paragraph.spans {
172 diff.insert(DiffModifies::STYLE);
173 diff.insert(DiffModifies::LAYOUT);
174 }
175
176 if self.accessibility != paragraph.accessibility {
177 diff.insert(DiffModifies::ACCESSIBILITY);
178 }
179
180 if self.relative_layer != paragraph.relative_layer {
181 diff.insert(DiffModifies::LAYER);
182 }
183
184 if self.text_style_data != paragraph.text_style_data {
185 diff.insert(DiffModifies::STYLE);
186 }
187
188 if self.event_handlers != paragraph.event_handlers {
189 diff.insert(DiffModifies::EVENT_HANDLERS);
190 }
191
192 if self.cursor_index != paragraph.cursor_index || self.highlights != paragraph.highlights {
193 diff.insert(DiffModifies::STYLE);
194 }
195
196 if self.text_style_data != paragraph.text_style_data
197 || self.line_height != paragraph.line_height
198 || self.max_lines != paragraph.max_lines
199 {
200 diff.insert(DiffModifies::TEXT_STYLE);
201 diff.insert(DiffModifies::LAYOUT);
202 }
203
204 if self.layout != paragraph.layout {
205 diff.insert(DiffModifies::STYLE);
206 diff.insert(DiffModifies::LAYOUT);
207 }
208
209 diff
210 }
211
212 fn layout(&'_ self) -> Cow<'_, LayoutData> {
213 Cow::Borrowed(&self.layout)
214 }
215 fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
216 None
217 }
218
219 fn style(&'_ self) -> Cow<'_, StyleState> {
220 Cow::Owned(StyleState::default())
221 }
222
223 fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
224 Cow::Borrowed(&self.text_style_data)
225 }
226
227 fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
228 Cow::Borrowed(&self.accessibility)
229 }
230
231 fn layer(&self) -> Layer {
232 self.relative_layer
233 }
234
235 fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
236 let cached_paragraph = CachedParagraph {
237 text_style_state: context.text_style_state,
238 spans: &self.spans,
239 max_lines: self.max_lines,
240 line_height: self.line_height,
241 width: context.area_size.width,
242 };
243 let paragraph = context
244 .text_cache
245 .utilize(context.node_id, &cached_paragraph)
246 .unwrap_or_else(|| {
247 let mut paragraph_style = ParagraphStyle::default();
248 let mut text_style = TextStyle::default();
249
250 let mut font_families = context.text_style_state.font_families.clone();
251 font_families.extend_from_slice(context.fallback_fonts);
252
253 text_style.set_color(context.text_style_state.color);
254 text_style.set_font_size(
255 f32::from(context.text_style_state.font_size) * context.scale_factor as f32,
256 );
257 text_style.set_font_families(&font_families);
258 text_style.set_font_style(FontStyle::new(
259 context.text_style_state.font_weight.into(),
260 context.text_style_state.font_width.into(),
261 context.text_style_state.font_slant.into(),
262 ));
263
264 if context.text_style_state.text_height.needs_custom_height() {
265 text_style.set_height_override(true);
266 text_style.set_half_leading(true);
267 }
268
269 if let Some(line_height) = self.line_height {
270 text_style.set_height_override(true).set_height(line_height);
271 }
272
273 for text_shadow in context.text_style_state.text_shadows.iter() {
274 text_style.add_shadow((*text_shadow).into());
275 }
276
277 if let Some(ellipsis) = context.text_style_state.text_overflow.get_ellipsis() {
278 paragraph_style.set_ellipsis(ellipsis);
279 }
280
281 paragraph_style.set_text_style(&text_style);
282 paragraph_style.set_max_lines(self.max_lines);
283 paragraph_style.set_text_align(context.text_style_state.text_align.into());
284
285 let mut paragraph_builder =
286 ParagraphBuilder::new(¶graph_style, context.font_collection);
287
288 for span in &self.spans {
289 let text_style_state =
290 TextStyleState::from_data(context.text_style_state, &span.text_style_data);
291 let mut text_style = TextStyle::new();
292 let mut font_families = context.text_style_state.font_families.clone();
293 font_families.extend_from_slice(context.fallback_fonts);
294
295 for text_shadow in text_style_state.text_shadows.iter() {
296 text_style.add_shadow((*text_shadow).into());
297 }
298
299 text_style.set_color(text_style_state.color);
300 text_style.set_font_size(
301 f32::from(text_style_state.font_size) * context.scale_factor as f32,
302 );
303 text_style.set_font_families(&font_families);
304 paragraph_builder.push_style(&text_style);
305 paragraph_builder.add_text(&span.text);
306 }
307
308 let mut paragraph = paragraph_builder.build();
309 paragraph.layout(
310 if self.max_lines == Some(1)
311 && context.text_style_state.text_align == TextAlign::Start
312 && !paragraph_style.ellipsized()
313 {
314 f32::MAX
315 } else {
316 context.area_size.width + 1.0
317 },
318 );
319 context
320 .text_cache
321 .insert(context.node_id, &cached_paragraph, paragraph)
322 });
323
324 let size = Size2D::new(paragraph.longest_line(), paragraph.height());
325
326 self.sk_paragraph
327 .0
328 .borrow_mut()
329 .replace(ParagraphHolderInner {
330 paragraph,
331 scale_factor: context.scale_factor,
332 });
333
334 Some((size, Rc::new(())))
335 }
336
337 fn should_hook_measurement(&self) -> bool {
338 true
339 }
340
341 fn should_measure_inner_children(&self) -> bool {
342 false
343 }
344
345 fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
346 Some(Cow::Borrowed(&self.event_handlers))
347 }
348
349 fn render(&self, context: RenderContext) {
350 let paragraph = self.sk_paragraph.0.borrow();
351 let ParagraphHolderInner { paragraph, .. } = paragraph.as_ref().unwrap();
352 let area = context.layout_node.visible_area();
353
354 for (from, to) in self.highlights.iter() {
356 let (from, to) = { if from < to { (from, to) } else { (to, from) } };
357 let rects = paragraph.get_rects_for_range(
358 *from..*to,
359 RectHeightStyle::Tight,
360 RectWidthStyle::Tight,
361 );
362
363 let mut highlights_paint = Paint::default();
364 highlights_paint.set_anti_alias(true);
365 highlights_paint.set_style(PaintStyle::Fill);
366 highlights_paint.set_color(self.cursor_style_data.highlight_color);
367
368 for rect in rects {
371 let rect = SkRect::new(
372 area.min_x() + rect.rect.left,
373 area.min_y() + rect.rect.top,
374 area.min_x() + rect.rect.right,
375 area.min_y() + rect.rect.bottom,
376 );
377 context.canvas.draw_rect(rect, &highlights_paint);
378 }
379 }
380
381 if let Some(cursor_index) = self.cursor_index
383 && self.highlights.is_empty()
384 && self.cursor_style == CursorStyle::Block
385 && let Some(cursor_rect) = paragraph
386 .get_rects_for_range(
387 cursor_index..cursor_index + 1,
388 RectHeightStyle::Tight,
389 RectWidthStyle::Tight,
390 )
391 .first()
392 .map(|text| text.rect)
393 .or_else(|| {
394 let text_len = paragraph
396 .get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
397 .position as usize;
398 let last_rects = paragraph.get_rects_for_range(
399 (text_len - 1)..text_len,
400 RectHeightStyle::Tight,
401 RectWidthStyle::Tight,
402 );
403
404 if let Some(last_rect) = last_rects.first() {
405 let mut caret = last_rect.rect;
406 caret.left = caret.right;
407 Some(caret)
408 } else {
409 None
410 }
411 })
412 {
413 let width = (cursor_rect.right - cursor_rect.left).max(6.0);
414 let cursor_rect = SkRect::new(
415 area.min_x() + cursor_rect.left,
416 area.min_y() + cursor_rect.top,
417 area.min_x() + cursor_rect.left + width,
418 area.min_y() + cursor_rect.bottom,
419 );
420
421 let mut paint = Paint::default();
422 paint.set_anti_alias(true);
423 paint.set_style(PaintStyle::Fill);
424 paint.set_color(self.cursor_style_data.color);
425
426 context.canvas.draw_rect(cursor_rect, &paint);
427 }
428
429 paragraph.paint(context.canvas, area.origin.to_tuple());
431
432 if let Some(cursor_index) = self.cursor_index
434 && self.highlights.is_empty()
435 {
436 let cursor_rects = paragraph.get_rects_for_range(
437 cursor_index..cursor_index + 1,
438 RectHeightStyle::Tight,
439 RectWidthStyle::Tight,
440 );
441 if let Some(cursor_rect) = cursor_rects.first().map(|text| text.rect).or_else(|| {
442 let text_len = paragraph
444 .get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
445 .position as usize;
446 let last_rects = paragraph.get_rects_for_range(
447 (text_len - 1)..text_len,
448 RectHeightStyle::Tight,
449 RectWidthStyle::Tight,
450 );
451
452 if let Some(last_rect) = last_rects.first() {
453 let mut caret = last_rect.rect;
454 caret.left = caret.right;
455 Some(caret)
456 } else {
457 None
458 }
459 }) {
460 let paint_color = self.cursor_style_data.color;
461 match self.cursor_style {
462 CursorStyle::Underline => {
463 let thickness = 2.0_f32;
464 let underline_rect = SkRect::new(
465 area.min_x() + cursor_rect.left,
466 area.min_y() + cursor_rect.bottom - thickness,
467 area.min_x() + cursor_rect.right,
468 area.min_y() + cursor_rect.bottom,
469 );
470
471 let mut paint = Paint::default();
472 paint.set_anti_alias(true);
473 paint.set_style(PaintStyle::Fill);
474 paint.set_color(paint_color);
475
476 context.canvas.draw_rect(underline_rect, &paint);
477 }
478 CursorStyle::Line => {
479 let cursor_rect = SkRect::new(
480 area.min_x() + cursor_rect.left,
481 area.min_y() + cursor_rect.top,
482 area.min_x() + cursor_rect.left + 2.,
483 area.min_y() + cursor_rect.bottom,
484 );
485
486 let mut paint = Paint::default();
487 paint.set_anti_alias(true);
488 paint.set_style(PaintStyle::Fill);
489 paint.set_color(paint_color);
490
491 context.canvas.draw_rect(cursor_rect, &paint);
492 }
493 _ => {}
494 }
495 }
496 }
497 }
498}
499
500impl From<Paragraph> for Element {
501 fn from(value: Paragraph) -> Self {
502 Element::Element {
503 key: value.key,
504 element: Rc::new(value.element),
505 elements: vec![],
506 }
507 }
508}
509
510impl KeyExt for Paragraph {
511 fn write_key(&mut self) -> &mut DiffKey {
512 &mut self.key
513 }
514}
515
516impl EventHandlersExt for Paragraph {
517 fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
518 &mut self.element.event_handlers
519 }
520}
521
522impl MaybeExt for Paragraph {}
523
524impl LayerExt for Paragraph {
525 fn get_layer(&mut self) -> &mut Layer {
526 &mut self.element.relative_layer
527 }
528}
529
530pub struct Paragraph {
531 key: DiffKey,
532 element: ParagraphElement,
533}
534
535impl LayoutExt for Paragraph {
536 fn get_layout(&mut self) -> &mut LayoutData {
537 &mut self.element.layout
538 }
539}
540
541impl ContainerExt for Paragraph {}
542
543impl AccessibilityExt for Paragraph {
544 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
545 &mut self.element.accessibility
546 }
547}
548
549impl TextStyleExt for Paragraph {
550 fn get_text_style_data(&mut self) -> &mut TextStyleData {
551 &mut self.element.text_style_data
552 }
553}
554
555impl Paragraph {
556 pub fn try_downcast(element: &dyn ElementExt) -> Option<ParagraphElement> {
557 (element as &dyn Any)
558 .downcast_ref::<ParagraphElement>()
559 .cloned()
560 }
561
562 pub fn spans_iter(mut self, spans: impl Iterator<Item = Span<'static>>) -> Self {
563 let spans = spans.collect::<Vec<Span>>();
564 self.element.spans.extend(spans);
567 self
568 }
569
570 pub fn span(mut self, span: impl Into<Span<'static>>) -> Self {
571 let span = span.into();
572 self.element.spans.push(span);
575 self
576 }
577
578 pub fn cursor_color(mut self, cursor_color: impl Into<Color>) -> Self {
579 self.element.cursor_style_data.color = cursor_color.into();
580 self
581 }
582
583 pub fn highlight_color(mut self, highlight_color: impl Into<Color>) -> Self {
584 self.element.cursor_style_data.highlight_color = highlight_color.into();
585 self
586 }
587
588 pub fn cursor_style(mut self, cursor_style: impl Into<CursorStyle>) -> Self {
589 self.element.cursor_style = cursor_style.into();
590 self
591 }
592
593 pub fn holder(mut self, holder: ParagraphHolder) -> Self {
594 self.element.sk_paragraph = holder;
595 self
596 }
597
598 pub fn cursor_index(mut self, cursor_index: impl Into<Option<usize>>) -> Self {
599 self.element.cursor_index = cursor_index.into();
600 self
601 }
602
603 pub fn highlights(mut self, highlights: impl Into<Option<Vec<(usize, usize)>>>) -> Self {
604 if let Some(highlights) = highlights.into() {
605 self.element.highlights = highlights;
606 }
607 self
608 }
609
610 pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
611 self.element.max_lines = max_lines.into();
612 self
613 }
614
615 pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
616 self.element.line_height = line_height.into();
617 self
618 }
619}
620
621#[derive(Clone, PartialEq, Hash)]
622pub struct Span<'a> {
623 pub text_style_data: TextStyleData,
624 pub text: Cow<'a, str>,
625}
626
627impl From<&'static str> for Span<'static> {
628 fn from(text: &'static str) -> Self {
629 Span {
630 text_style_data: TextStyleData::default(),
631 text: text.into(),
632 }
633 }
634}
635
636impl From<String> for Span<'static> {
637 fn from(text: String) -> Self {
638 Span {
639 text_style_data: TextStyleData::default(),
640 text: text.into(),
641 }
642 }
643}
644
645impl<'a> Span<'a> {
646 pub fn new(text: impl Into<Cow<'a, str>>) -> Self {
647 Self {
648 text: text.into(),
649 text_style_data: TextStyleData::default(),
650 }
651 }
652}
653
654impl<'a> TextStyleExt for Span<'a> {
655 fn get_text_style_data(&mut self) -> &mut TextStyleData {
656 &mut self.text_style_data
657 }
658}