freya_core/render/utils/
paragraph.rs1use freya_engine::prelude::*;
2use freya_native_core::{
3 node::ElementNode,
4 prelude::NodeType,
5 real_dom::NodeImmutable,
6 tags::TagName,
7};
8use torin::prelude::{
9 Alignment,
10 Area,
11 Size2D,
12};
13
14use crate::{
15 dom::DioxusNode,
16 states::{
17 CursorState,
18 FontStyleState,
19 LayoutState,
20 },
21 values::HighlightMode,
22};
23
24pub struct ParagraphData {
25 pub paragraph: Paragraph,
26 pub size: Size2D,
27}
28
29pub fn create_paragraph(
31 node: &DioxusNode,
32 area_size: &Size2D,
33 font_collection: &FontCollection,
34 is_rendering: bool,
35 default_font_family: &[String],
36 scale_factor: f32,
37) -> ParagraphData {
38 let font_style = &*node.get::<FontStyleState>().unwrap();
39
40 let mut paragraph_style = ParagraphStyle::default();
41 paragraph_style.set_text_align(font_style.text_align);
42 paragraph_style.set_max_lines(font_style.max_lines);
43 paragraph_style.set_replace_tab_characters(true);
44 paragraph_style.set_text_height_behavior(font_style.text_height);
45
46 if let Some(ellipsis) = font_style.text_overflow.get_ellipsis() {
47 paragraph_style.set_ellipsis(ellipsis);
48 }
49
50 let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection);
51
52 let text_style =
53 font_style.text_style(default_font_family, scale_factor, font_style.text_height);
54 paragraph_builder.push_style(&text_style);
55
56 for text_span in node.children() {
57 if let NodeType::Element(ElementNode {
58 tag: TagName::Text, ..
59 }) = &*text_span.node_type()
60 {
61 let text_nodes = text_span.children();
62 let text_node = *text_nodes.first().unwrap();
63 let text_node_type = &*text_node.node_type();
64 let text_font_style = text_span.get::<FontStyleState>().unwrap();
65 let text_style = text_font_style.text_style(
66 default_font_family,
67 scale_factor,
68 font_style.text_height,
69 );
70 paragraph_builder.push_style(&text_style);
71
72 if let NodeType::Text(text) = text_node_type {
73 paragraph_builder.add_text(text);
74 }
75 }
76 }
77
78 if is_rendering {
79 paragraph_builder.add_text(" ");
81 }
82
83 let mut paragraph = paragraph_builder.build();
84 paragraph.layout(
85 if font_style.max_lines == Some(1) && font_style.text_align == TextAlign::default() {
86 f32::MAX
87 } else {
88 area_size.width + 1.0
89 },
90 );
91
92 let width = match font_style.text_align {
93 TextAlign::Start | TextAlign::Left => paragraph.longest_line(),
94 _ => paragraph.max_width(),
95 };
96
97 ParagraphData {
98 size: Size2D::new(width, paragraph.height()),
99 paragraph,
100 }
101}
102
103pub fn run_cursor_highlights(
104 area: Area,
105 paragraph: &Paragraph,
106 node_ref: &DioxusNode,
107 mut run: impl FnMut(Rect),
108) -> Option<()> {
109 let node_cursor_state = &*node_ref.get::<CursorState>().unwrap();
110
111 let highlights = node_cursor_state.highlights.as_ref()?;
112
113 for (from, to) in highlights.iter() {
114 let (from, to) = {
115 if from < to {
116 (from, to)
117 } else {
118 (to, from)
119 }
120 };
121 let cursor_rects = paragraph.get_rects_for_range(
122 *from..*to,
123 RectHeightStyle::Tight,
124 RectWidthStyle::Tight,
125 );
126
127 for cursor_rect in cursor_rects {
128 let rect = align_highlights_and_cursor_paragraph(
129 node_ref,
130 &area,
131 paragraph,
132 &cursor_rect,
133 None,
134 );
135
136 run(rect)
137 }
138 }
139
140 Some(())
141}
142
143pub fn draw_cursor(
144 area: &Area,
145 paragraph: &Paragraph,
146 canvas: &Canvas,
147 node_ref: &DioxusNode,
148) -> Option<()> {
149 let node_cursor_state = &*node_ref.get::<CursorState>().unwrap();
150
151 let cursor = node_cursor_state.position?;
152 let cursor_color = node_cursor_state.color;
153 let cursor_position = cursor as usize;
154
155 let cursor_rects = paragraph.get_rects_for_range(
156 cursor_position..cursor_position + 1,
157 RectHeightStyle::Tight,
158 RectWidthStyle::Tight,
159 );
160 let cursor_rect = cursor_rects.first()?;
161
162 let rect =
163 align_highlights_and_cursor_paragraph(node_ref, area, paragraph, cursor_rect, Some(1.0));
164
165 let mut paint = Paint::default();
166 paint.set_anti_alias(true);
167 paint.set_style(PaintStyle::Fill);
168 paint.set_color(cursor_color);
169
170 canvas.draw_rect(rect, &paint);
171
172 Some(())
173}
174
175pub fn align_highlights_and_cursor_paragraph(
177 node: &DioxusNode,
178 area: &Area,
179 paragraph: &Paragraph,
180 cursor_rect: &TextBox,
181 width: Option<f32>,
182) -> Rect {
183 let cursor_state = node.get::<CursorState>().unwrap();
184
185 let left = area.min_x() + cursor_rect.rect.left;
186 let right = left + width.unwrap_or(cursor_rect.rect.right - cursor_rect.rect.left);
187
188 match cursor_state.highlight_mode {
189 HighlightMode::Fit => {
190 let top = (area.min_y()
191 + align_main_align_paragraph(node, area, paragraph)
192 + cursor_rect.rect.top)
193 .clamp(area.min_y(), area.max_y());
194 let bottom = (top + (cursor_rect.rect.bottom - cursor_rect.rect.top))
195 .clamp(area.min_y(), area.max_y());
196
197 Rect::new(left, top, right, bottom)
198 }
199 HighlightMode::Expanded => {
200 let top = area.min_y();
201 let bottom = area.max_y();
202
203 Rect::new(left, top, right, bottom)
204 }
205 }
206}
207
208pub fn align_main_align_paragraph(node: &DioxusNode, area: &Area, paragraph: &Paragraph) -> f32 {
210 let layout = node.get::<LayoutState>().unwrap();
211
212 match layout.main_alignment {
213 Alignment::Start => 0.,
214 Alignment::Center => (area.height() / 2.0) - (paragraph.height() / 2.0),
215 Alignment::End => area.height() - paragraph.height(),
216 Alignment::SpaceBetween => 0.,
217 Alignment::SpaceEvenly => 0.,
218 Alignment::SpaceAround => 0.,
219 }
220}