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