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