1use std::ops::Mul;
2
3use freya_engine::prelude::*;
4use freya_native_core::{
5 prelude::{
6 ElementNode,
7 NodeType,
8 },
9 real_dom::NodeImmutable,
10 tags::TagName,
11};
12use torin::{
13 geometry::Area,
14 prelude::{
15 AreaModel,
16 CursorPoint,
17 LayoutNode,
18 Length,
19 Size2D,
20 },
21};
22
23use super::utils::ElementUtils;
24use crate::{
25 custom_attributes::CursorLayoutResponse,
26 dom::{
27 DioxusNode,
28 ImagesCache,
29 },
30 event_loop_messages::TextGroupMeasurement,
31 render::{
32 align_main_align_paragraph,
33 create_paragraph,
34 draw_cursor,
35 run_cursor_highlights,
36 ParagraphData,
37 },
38 states::{
39 CursorState,
40 FontStyleState,
41 StyleState,
42 },
43};
44
45pub struct CachedParagraph(pub Paragraph);
46
47unsafe impl Send for CachedParagraph {}
53unsafe impl Sync for CachedParagraph {}
54
55pub struct ParagraphElement;
56
57impl ParagraphElement {
58 pub fn measure_paragraph(
60 node: &DioxusNode,
61 layout_node: &LayoutNode,
62 text_measurement: &TextGroupMeasurement,
63 scale_factor: f64,
64 ) {
65 let paragraph = &layout_node
66 .data
67 .as_ref()
68 .unwrap()
69 .get::<CachedParagraph>()
70 .unwrap()
71 .0;
72
73 let cursor_state = node.get::<CursorState>().unwrap();
74
75 if cursor_state.cursor_id != Some(text_measurement.cursor_id) {
76 return;
77 }
78
79 let y = align_main_align_paragraph(node, &layout_node.area, paragraph);
80
81 if let Some(cursor_reference) = &cursor_state.cursor_ref {
82 if let Some(cursor_position) = text_measurement.cursor_position {
83 let position = CursorPoint::new(cursor_position.x, cursor_position.y - y as f64);
84
85 let char_position = paragraph.get_glyph_position_at_coordinate(
87 position.mul(scale_factor).to_i32().to_tuple(),
88 );
89
90 cursor_reference
92 .cursor_sender
93 .send(CursorLayoutResponse::CursorPosition {
94 position: char_position.position as usize,
95 id: text_measurement.cursor_id,
96 })
97 .ok();
98 }
99
100 if let Some((origin, dist)) = text_measurement.cursor_selection {
101 let origin_position = CursorPoint::new(origin.x, origin.y - y as f64);
102 let dist_position = CursorPoint::new(dist.x, dist.y - y as f64);
103
104 let origin_char = paragraph.get_glyph_position_at_coordinate(
106 origin_position.mul(scale_factor).to_i32().to_tuple(),
107 );
108 let dist_char = paragraph.get_glyph_position_at_coordinate(
110 dist_position.mul(scale_factor).to_i32().to_tuple(),
111 );
112
113 cursor_reference
114 .cursor_sender
115 .send(CursorLayoutResponse::TextSelection {
116 from: origin_char.position as usize,
117 to: dist_char.position as usize,
118 id: text_measurement.cursor_id,
119 })
120 .ok();
121 }
122 }
123 }
124}
125
126impl ElementUtils for ParagraphElement {
127 fn render(
128 self,
129 layout_node: &torin::prelude::LayoutNode,
130 node_ref: &DioxusNode,
131 canvas: &Canvas,
132 font_collection: &mut FontCollection,
133 _font_manager: &FontMgr,
134 default_fonts: &[String],
135 _images_cache: &mut ImagesCache,
136 scale_factor: f32,
137 ) {
138 let area = layout_node.visible_area();
139 let node_cursor_state = &*node_ref.get::<CursorState>().unwrap();
140
141 let paint = |paragraph: &Paragraph| {
142 let x = area.min_x();
143 let y = area.min_y() + align_main_align_paragraph(node_ref, &area, paragraph);
144
145 let mut highlights_paint = Paint::default();
146 highlights_paint.set_anti_alias(true);
147 highlights_paint.set_style(PaintStyle::Fill);
148 highlights_paint.set_color(node_cursor_state.highlight_color);
149
150 run_cursor_highlights(area, paragraph, node_ref, |rect| {
152 canvas.draw_rect(rect, &highlights_paint);
153 });
154
155 draw_cursor(&area, paragraph, canvas, node_ref);
157
158 paragraph.paint(canvas, (x, y));
159 };
160
161 if node_cursor_state.position.is_some() {
162 let ParagraphData { paragraph, .. } = create_paragraph(
163 node_ref,
164 &area.size,
165 font_collection,
166 true,
167 default_fonts,
168 scale_factor,
169 );
170 paint(¶graph);
171 } else {
172 let paragraph = &layout_node
173 .data
174 .as_ref()
175 .unwrap()
176 .get::<CachedParagraph>()
177 .unwrap()
178 .0;
179 paint(paragraph);
180 };
181 }
182
183 fn clip(
184 &self,
185 layout_node: &LayoutNode,
186 _node_ref: &DioxusNode,
187 canvas: &Canvas,
188 _scale_factor: f32,
189 ) {
190 canvas.clip_rect(
191 Rect::new(
192 layout_node.area.min_x(),
193 layout_node.area.min_y(),
194 layout_node.area.max_x(),
195 layout_node.area.max_y(),
196 ),
197 ClipOp::Intersect,
198 true,
199 );
200 }
201
202 fn element_needs_cached_area(&self, node_ref: &DioxusNode, _style_state: &StyleState) -> bool {
203 let node_cursor_state = &*node_ref.get::<CursorState>().unwrap();
204
205 if node_cursor_state.highlights.is_some() {
206 return true;
207 }
208
209 for text_span in node_ref.children() {
210 if let NodeType::Element(ElementNode {
211 tag: TagName::Text, ..
212 }) = &*text_span.node_type()
213 {
214 let font_style = text_span.get::<FontStyleState>().unwrap();
215
216 if !font_style.text_shadows.is_empty() {
217 return true;
218 }
219 }
220 }
221
222 false
223 }
224
225 fn element_drawing_area(
226 &self,
227 layout_node: &LayoutNode,
228 node_ref: &DioxusNode,
229 scale_factor: f32,
230 _node_style: &StyleState,
231 ) -> Area {
232 let mut area = layout_node.visible_area();
233
234 for text_span in node_ref.children() {
244 if let NodeType::Element(ElementNode {
245 tag: TagName::Text, ..
246 }) = &*text_span.node_type()
247 {
248 let font_style = text_span.get::<FontStyleState>().unwrap();
249
250 let mut text_shadow_area = area;
251
252 for text_shadow in font_style.text_shadows.iter() {
253 if text_shadow.color != Color::TRANSPARENT {
254 text_shadow_area.move_with_offsets(
255 &Length::new(text_shadow.offset.x),
256 &Length::new(text_shadow.offset.y),
257 );
258
259 let expanded_size = text_shadow.blur_sigma.ceil() as f32 * scale_factor;
260
261 text_shadow_area.expand(&Size2D::new(expanded_size, expanded_size))
262 }
263 }
264
265 area = area.union(&text_shadow_area);
266 }
267 }
268
269 let paragraph = &layout_node
270 .data
271 .as_ref()
272 .unwrap()
273 .get::<CachedParagraph>()
274 .unwrap()
275 .0;
276
277 run_cursor_highlights(area, paragraph, node_ref, |rect| {
278 area = area.union(&Area::new(
279 (rect.left, rect.top).into(),
280 (rect.width(), rect.height()).into(),
281 ))
282 });
283
284 area
285 }
286}