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