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