1use std::{
2 cell::{Cell, RefCell},
3 hash::{Hash, Hasher},
4 rc::Rc,
5};
6
7use cranpose_foundation::{
8 Constraints, DelegatableNode, DrawModifierNode, InvalidationKind, LayoutModifierNode,
9 Measurable, ModifierNode, ModifierNodeContext, ModifierNodeElement, NodeCapabilities,
10 NodeState, SemanticsConfiguration, SemanticsNode, Size,
11};
12
13use crate::{
14 density::Density,
15 text::{AnnotatedString, TextLayoutOptions, TextStyle},
16};
17
18#[derive(Debug)]
28pub struct TextModifierNode {
29 layout: Rc<TextPreparedLayoutOwner>,
30 density: Density,
31 state: NodeState,
32}
33
34const PREPARED_LAYOUT_CACHE_CAPACITY: usize = 4;
35
36#[derive(Clone, Debug)]
37struct TextPreparedLayoutCacheEntry {
38 widths: crate::text::measure::PreparedWidths,
39 text_generation: u64,
40 font_scale_fingerprint: u32,
41 layout: Rc<crate::text::PreparedTextLayout>,
42}
43
44#[derive(Debug)]
45struct TextPreparedLayoutOwner {
46 text: Rc<AnnotatedString>,
47 style: TextStyle,
48 options: TextLayoutOptions,
49 node_id: Cell<Option<cranpose_core::NodeId>>,
50 measured_max_width: Cell<Option<Option<f32>>>,
51 cache: RefCell<Vec<TextPreparedLayoutCacheEntry>>,
52}
53
54#[derive(Clone, Debug)]
55pub(crate) struct TextPreparedLayoutHandle {
56 owner: Rc<TextPreparedLayoutOwner>,
57}
58
59impl TextPreparedLayoutOwner {
60 fn new(
61 text: Rc<AnnotatedString>,
62 style: TextStyle,
63 options: TextLayoutOptions,
64 node_id: Option<cranpose_core::NodeId>,
65 measured_max_width: Option<Option<f32>>,
66 ) -> Self {
67 Self {
68 text,
69 style,
70 options: options.normalized(),
71 node_id: Cell::new(node_id),
72 measured_max_width: Cell::new(measured_max_width),
73 cache: RefCell::new(Vec::new()),
74 }
75 }
76
77 fn text(&self) -> &str {
78 self.text.text.as_str()
79 }
80
81 fn annotated_text(&self) -> Rc<AnnotatedString> {
82 self.text.clone()
83 }
84
85 fn annotated_string(&self) -> AnnotatedString {
86 (*self.text).clone()
87 }
88
89 fn style(&self) -> &TextStyle {
90 &self.style
91 }
92
93 fn options(&self) -> TextLayoutOptions {
94 self.options
95 }
96
97 fn node_id(&self) -> Option<cranpose_core::NodeId> {
98 self.node_id.get()
99 }
100
101 fn set_node_id(&self, node_id: Option<cranpose_core::NodeId>) {
102 if self.node_id.replace(node_id) != node_id {
103 self.cache.borrow_mut().clear();
104 }
105 }
106
107 fn prepare(&self, max_width: Option<f32>) -> Rc<crate::text::PreparedTextLayout> {
111 let normalized_max_width = max_width.filter(|width| width.is_finite() && *width > 0.0);
112 let text_generation = crate::text::measure::current_text_generation();
113 let font_scale_fingerprint = crate::current_font_scale_curve().fingerprint();
114
115 {
116 let mut cache = self.cache.borrow_mut();
117 if let Some(index) = cache.iter().position(|entry| {
118 entry.widths.hold(normalized_max_width)
119 && entry.text_generation == text_generation
120 && entry.font_scale_fingerprint == font_scale_fingerprint
121 }) {
122 let prepared = Rc::clone(&cache[index].layout);
123 cache[..=index].rotate_right(1);
124 return prepared;
125 }
126 }
127
128 let prepared = crate::text::prepare_text_layout_for_node(
129 self.node_id(),
130 self.text.as_ref(),
131 &self.style,
132 self.options,
133 normalized_max_width,
134 );
135
136 let mut cache = self.cache.borrow_mut();
137 cache.insert(
138 0,
139 TextPreparedLayoutCacheEntry {
140 widths: crate::text::measure::PreparedWidths::of(
141 self.text.as_ref(),
142 self.options,
143 normalized_max_width,
144 &prepared,
145 ),
146 text_generation,
147 font_scale_fingerprint,
148 layout: Rc::clone(&prepared),
149 },
150 );
151 cache.truncate(PREPARED_LAYOUT_CACHE_CAPACITY);
152 prepared
153 }
154
155 fn measure_text_content(&self, max_width: Option<f32>) -> Size {
156 let prepared = self.prepare(max_width);
157 Size {
158 width: prepared.metrics.width,
159 height: prepared.metrics.height,
160 }
161 }
162
163 fn measure_layout(&self, max_width: Option<f32>) -> Size {
164 self.measured_max_width.set(Some(max_width));
165 self.measure_text_content(max_width)
166 }
167
168 fn measured_layout(&self) -> Option<Rc<crate::text::PreparedTextLayout>> {
169 self.measured_max_width
170 .get()
171 .map(|max_width| self.prepare(max_width))
172 }
173}
174
175impl TextPreparedLayoutHandle {
176 fn new(owner: Rc<TextPreparedLayoutOwner>) -> Self {
177 Self { owner }
178 }
179
180 pub(crate) fn measured_layout(&self) -> Option<Rc<crate::text::PreparedTextLayout>> {
181 self.owner.measured_layout()
182 }
183}
184
185impl TextModifierNode {
186 pub fn new(
188 text: Rc<AnnotatedString>,
189 style: TextStyle,
190 options: TextLayoutOptions,
191 density: Density,
192 ) -> Self {
193 Self {
194 layout: Rc::new(TextPreparedLayoutOwner::new(
195 text, style, options, None, None,
196 )),
197 density,
198 state: NodeState::new(),
199 }
200 }
201
202 fn pixel_size(&self, size: Size) -> Size {
206 Size {
207 width: self.density.ceil(size.width),
208 height: self.density.ceil(size.height),
209 }
210 }
211
212 pub fn text(&self) -> &str {
213 self.layout.text()
214 }
215
216 pub fn annotated_text(&self) -> Rc<AnnotatedString> {
217 self.layout.annotated_text()
218 }
219
220 pub fn annotated_string(&self) -> AnnotatedString {
221 self.layout.annotated_string()
222 }
223
224 pub fn style(&self) -> &TextStyle {
225 self.layout.style()
226 }
227
228 pub fn options(&self) -> TextLayoutOptions {
229 self.layout.options()
230 }
231
232 fn measure_text_content(&self, max_width: Option<f32>) -> Size {
233 self.layout.measure_text_content(max_width)
234 }
235
236 pub(crate) fn prepared_layout_handle(&self) -> TextPreparedLayoutHandle {
237 TextPreparedLayoutHandle::new(self.layout.clone())
238 }
239}
240
241impl DelegatableNode for TextModifierNode {
242 fn node_state(&self) -> &NodeState {
243 &self.state
244 }
245}
246
247impl ModifierNode for TextModifierNode {
248 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
249 self.layout.set_node_id(context.node_id());
250 context.invalidate(InvalidationKind::Layout);
251 context.invalidate(InvalidationKind::Draw);
252 context.invalidate(InvalidationKind::Semantics);
253 }
254
255 fn on_detach(&mut self) {
256 self.layout.set_node_id(None);
257 }
258
259 fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
260 Some(self)
261 }
262
263 fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
264 Some(self)
265 }
266
267 fn as_semantics_node(&self) -> Option<&dyn SemanticsNode> {
268 Some(self)
269 }
270
271 fn as_semantics_node_mut(&mut self) -> Option<&mut dyn SemanticsNode> {
272 Some(self)
273 }
274
275 fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
276 Some(self)
277 }
278
279 fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
280 Some(self)
281 }
282}
283
284impl LayoutModifierNode for TextModifierNode {
285 fn measure(
286 &self,
287 _context: &mut dyn ModifierNodeContext,
288 _measurable: &dyn Measurable,
289 constraints: Constraints,
290 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
291 let max_width = constraints
292 .max_width
293 .is_finite()
294 .then_some(constraints.max_width);
295 let text_size = self.pixel_size(self.layout.measure_layout(max_width));
296
297 let width = text_size
298 .width
299 .clamp(constraints.min_width, constraints.max_width);
300 let height = text_size
301 .height
302 .clamp(constraints.min_height, constraints.max_height);
303
304 cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size { width, height })
305 }
306
307 fn min_intrinsic_width(
308 &self,
309 _measurable: &dyn Measurable,
310 _height: f32,
311 _density: f32,
312 ) -> f32 {
313 self.pixel_size(self.measure_text_content(None)).width
314 }
315
316 fn max_intrinsic_width(
317 &self,
318 _measurable: &dyn Measurable,
319 _height: f32,
320 _density: f32,
321 ) -> f32 {
322 self.pixel_size(self.measure_text_content(None)).width
323 }
324
325 fn min_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
326 self.pixel_size(
327 self.measure_text_content(Some(width).filter(|w| w.is_finite() && *w > 0.0)),
328 )
329 .height
330 }
331
332 fn max_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
333 self.pixel_size(
334 self.measure_text_content(Some(width).filter(|w| w.is_finite() && *w > 0.0)),
335 )
336 .height
337 }
338}
339
340impl DrawModifierNode for TextModifierNode {}
341
342impl SemanticsNode for TextModifierNode {
343 fn merge_semantics(&self, config: &mut SemanticsConfiguration) {
344 config.content_description = Some(self.text().to_string());
345 }
346
347 fn reach(&self) -> cranpose_foundation::SemanticsReach {
348 cranpose_foundation::SemanticsReach::default()
349 }
350}
351
352#[derive(Debug, Clone, PartialEq)]
361pub struct TextModifierElement {
362 text: Rc<AnnotatedString>,
363 style: TextStyle,
364 options: TextLayoutOptions,
365 density: Density,
366}
367
368impl TextModifierElement {
369 pub fn new(
372 text: Rc<AnnotatedString>,
373 style: TextStyle,
374 options: TextLayoutOptions,
375 density: Density,
376 ) -> Self {
377 Self {
378 text,
379 style,
380 options: options.normalized(),
381 density,
382 }
383 }
384}
385
386impl Hash for TextModifierElement {
387 fn hash<H: Hasher>(&self, state: &mut H) {
388 self.text.render_hash().hash(state);
389 self.style.render_hash().hash(state);
390 self.options.hash(state);
391 self.density.density().to_bits().hash(state);
392 }
393}
394
395impl ModifierNodeElement for TextModifierElement {
396 type Node = TextModifierNode;
397
398 fn create(&self) -> Self::Node {
399 TextModifierNode::new(
400 self.text.clone(),
401 self.style.clone(),
402 self.options,
403 self.density,
404 )
405 }
406
407 fn update(&self, node: &mut Self::Node) {
408 node.density = self.density;
409 let current = node.layout.as_ref();
410 if current.text != self.text
411 || current.style != self.style
412 || current.options != self.options
413 {
414 node.layout = Rc::new(TextPreparedLayoutOwner::new(
415 self.text.clone(),
416 self.style.clone(),
417 self.options,
418 current.node_id(),
419 current.measured_max_width.get(),
420 ));
421 }
422 }
423
424 fn capabilities(&self) -> NodeCapabilities {
425 NodeCapabilities::LAYOUT | NodeCapabilities::DRAW | NodeCapabilities::SEMANTICS
426 }
427}
428
429#[cfg(test)]
430#[path = "tests/text_modifier_node_tests.rs"]
431mod tests;