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};
12use smallvec::SmallVec;
13
14use crate::{
15 density::Density,
16 text::{AnnotatedString, TextLayoutOptions, TextStyle},
17};
18
19#[derive(Debug)]
29pub struct TextModifierNode {
30 layout: Rc<TextPreparedLayoutOwner>,
31 density: Density,
32 state: NodeState,
33}
34
35const PREPARED_LAYOUT_CACHE_CAPACITY: usize = 4;
36
37#[derive(Clone, Debug)]
38struct TextPreparedLayoutCacheEntry {
39 widths: crate::text::measure::PreparedWidths,
40 text_generation: u64,
41 font_scale_fingerprint: u32,
42 layout: Rc<crate::text::PreparedTextLayout>,
43}
44
45#[derive(Debug)]
46struct TextPreparedLayoutOwner {
47 text: Rc<AnnotatedString>,
48 style: std::sync::Arc<TextStyle>,
49 style_hash: u64,
50 options: TextLayoutOptions,
51 node_id: Cell<Option<cranpose_core::NodeId>>,
52 measured_max_width: Cell<Option<Option<f32>>>,
53 cache: RefCell<SmallVec<[TextPreparedLayoutCacheEntry; 1]>>,
54 shares_layouts: bool,
55}
56
57#[derive(Clone, Debug)]
58pub(crate) struct TextPreparedLayoutHandle {
59 owner: Rc<TextPreparedLayoutOwner>,
60}
61
62impl TextPreparedLayoutOwner {
63 fn new(
64 text: Rc<AnnotatedString>,
65 (style, style_hash): (std::sync::Arc<TextStyle>, u64),
66 options: TextLayoutOptions,
67 node_id: Option<cranpose_core::NodeId>,
68 measured_max_width: Option<Option<f32>>,
69 shares_layouts: bool,
70 ) -> Self {
71 Self {
72 text,
73 style,
74 style_hash,
75 options: options.normalized(),
76 node_id: Cell::new(node_id),
77 measured_max_width: Cell::new(measured_max_width),
78 cache: RefCell::new(SmallVec::new()),
79 shares_layouts,
80 }
81 }
82
83 fn text(&self) -> &str {
84 self.text.text.as_str()
85 }
86
87 fn annotated_text(&self) -> Rc<AnnotatedString> {
88 self.text.clone()
89 }
90
91 fn annotated_string(&self) -> AnnotatedString {
92 (*self.text).clone()
93 }
94
95 fn style(&self) -> &TextStyle {
96 &self.style
97 }
98
99 fn options(&self) -> TextLayoutOptions {
100 self.options
101 }
102
103 fn node_id(&self) -> Option<cranpose_core::NodeId> {
104 self.node_id.get()
105 }
106
107 fn set_node_id(&self, node_id: Option<cranpose_core::NodeId>) {
108 if self.node_id.replace(node_id) != node_id {
109 self.cache.borrow_mut().clear();
110 }
111 }
112
113 fn with_prepared<R>(
114 &self,
115 max_width: Option<f32>,
116 read: impl FnOnce(&Rc<crate::text::PreparedTextLayout>) -> R,
117 ) -> R {
118 let normalized_max_width = max_width.filter(|width| width.is_finite() && *width > 0.0);
119 let (text_generation, font_scale_fingerprint) = crate::render_state::text_layout_stamp();
120
121 {
122 let mut cache = self.cache.borrow_mut();
123 if let Some(index) = cache.iter().position(|entry| {
124 entry.widths.hold(normalized_max_width)
125 && entry.text_generation == text_generation
126 && entry.font_scale_fingerprint == font_scale_fingerprint
127 }) {
128 cache[..=index].rotate_right(1);
129 return read(&cache[0].layout);
130 }
131 }
132
133 let prepare = if self.shares_layouts {
134 crate::text::prepare_text_layout_for_node
135 } else {
136 crate::text::measure::prepare_unshared_text_layout_for_node
137 };
138 let prepared = prepare(
139 self.node_id(),
140 &self.text,
141 &self.style,
142 self.options,
143 normalized_max_width,
144 );
145 if std::sync::Arc::ptr_eq(&prepared.visual_style, &self.style) {
146 let _ = prepared.visual_style_hash.set(self.style_hash);
147 }
148 let widths = crate::text::measure::PreparedWidths::of(
149 self.text.as_ref(),
150 self.options,
151 normalized_max_width,
152 &prepared,
153 );
154
155 let mut cache = self.cache.borrow_mut();
156 cache.insert(
157 0,
158 TextPreparedLayoutCacheEntry {
159 widths,
160 text_generation,
161 font_scale_fingerprint,
162 layout: prepared,
163 },
164 );
165 cache.truncate(PREPARED_LAYOUT_CACHE_CAPACITY);
166 read(&cache[0].layout)
167 }
168
169 fn cached_widths(
172 &self,
173 max_width: Option<f32>,
174 ) -> Option<(Size, crate::text::measure::PreparedWidths)> {
175 let normalized_max_width = max_width.filter(|width| width.is_finite() && *width > 0.0);
176 let (text_generation, font_scale_fingerprint) = crate::render_state::text_layout_stamp();
177 self.cache
178 .borrow()
179 .iter()
180 .find(|entry| {
181 entry.widths.hold(normalized_max_width)
182 && entry.text_generation == text_generation
183 && entry.font_scale_fingerprint == font_scale_fingerprint
184 })
185 .map(|entry| {
186 (
187 Size::new(entry.layout.metrics.width, entry.layout.metrics.height),
188 entry.widths,
189 )
190 })
191 }
192
193 fn measure_text_content(&self, max_width: Option<f32>) -> Size {
194 self.with_prepared(max_width, |prepared| Size {
195 width: prepared.metrics.width,
196 height: prepared.metrics.height,
197 })
198 }
199
200 fn measure_layout(&self, max_width: Option<f32>) -> (Size, cranpose_ui_layout::AlignmentLines) {
201 self.measured_max_width.set(Some(max_width));
202 self.with_prepared(max_width, |prepared| {
203 (
204 Size::new(prepared.metrics.width, prepared.metrics.height),
205 prepared.alignment_lines,
206 )
207 })
208 }
209
210 fn measured_layout(&self) -> Option<Rc<crate::text::PreparedTextLayout>> {
211 self.measured_max_width
212 .get()
213 .map(|max_width| self.with_prepared(max_width, Rc::clone))
214 }
215}
216
217impl TextPreparedLayoutHandle {
218 fn new(owner: Rc<TextPreparedLayoutOwner>) -> Self {
219 Self { owner }
220 }
221
222 pub(crate) fn annotated_text(&self) -> &Rc<AnnotatedString> {
223 &self.owner.text
224 }
225
226 pub(crate) fn style(&self) -> &TextStyle {
227 self.owner.style()
228 }
229
230 pub(crate) fn options(&self) -> TextLayoutOptions {
231 self.owner.options()
232 }
233
234 pub(crate) fn measured_layout(&self) -> Option<Rc<crate::text::PreparedTextLayout>> {
235 self.owner.measured_layout()
236 }
237}
238
239impl TextModifierNode {
240 pub fn new(
242 text: Rc<AnnotatedString>,
243 style: TextStyle,
244 options: TextLayoutOptions,
245 density: Density,
246 ) -> Self {
247 let style_hash = style.render_hash();
248 Self {
249 layout: Rc::new(TextPreparedLayoutOwner::new(
250 text,
251 (std::sync::Arc::new(style), style_hash),
252 options,
253 None,
254 None,
255 true,
256 )),
257 density,
258 state: NodeState::new(),
259 }
260 }
261
262 fn pixel_size(&self, size: Size) -> Size {
266 Size {
267 width: self.density.ceil(size.width),
268 height: self.density.ceil(size.height),
269 }
270 }
271
272 pub fn text(&self) -> &str {
273 self.layout.text()
274 }
275
276 pub fn annotated_text(&self) -> Rc<AnnotatedString> {
277 self.layout.annotated_text()
278 }
279
280 pub fn annotated_string(&self) -> AnnotatedString {
281 self.layout.annotated_string()
282 }
283
284 pub fn style(&self) -> &TextStyle {
285 self.layout.style()
286 }
287
288 pub fn options(&self) -> TextLayoutOptions {
289 self.layout.options()
290 }
291
292 fn measure_text_content(&self, max_width: Option<f32>) -> Size {
293 self.layout.measure_text_content(max_width)
294 }
295
296 pub(crate) fn prepared_layout_handle(&self) -> TextPreparedLayoutHandle {
297 TextPreparedLayoutHandle::new(self.layout.clone())
298 }
299}
300
301impl DelegatableNode for TextModifierNode {
302 fn node_state(&self) -> &NodeState {
303 &self.state
304 }
305}
306
307impl ModifierNode for TextModifierNode {
308 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
309 self.layout.set_node_id(context.node_id());
310 context.invalidate(InvalidationKind::Layout);
311 context.invalidate(InvalidationKind::Draw);
312 context.invalidate(InvalidationKind::Semantics);
313 }
314
315 fn on_detach(&mut self) {
316 self.layout.set_node_id(None);
317 }
318
319 fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
320 Some(self)
321 }
322
323 fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
324 Some(self)
325 }
326
327 fn as_semantics_node(&self) -> Option<&dyn SemanticsNode> {
328 Some(self)
329 }
330
331 fn as_semantics_node_mut(&mut self) -> Option<&mut dyn SemanticsNode> {
332 Some(self)
333 }
334
335 fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
336 Some(self)
337 }
338
339 fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
340 Some(self)
341 }
342}
343
344impl LayoutModifierNode for TextModifierNode {
345 fn measure_hold(
348 &self,
349 _density: f32,
350 constraints: Constraints,
351 size: Size,
352 _wrapped: cranpose_ui_layout::WrappedHold,
353 ) -> Option<cranpose_ui_layout::ConstraintsHold> {
354 let max_width = constraints
355 .max_width
356 .is_finite()
357 .then_some(constraints.max_width);
358 let (text_size, widths) = self.layout.cached_widths(max_width)?;
359 let text_size = self.pixel_size(text_size);
360 if size != text_size {
361 return None;
362 }
363 Some(cranpose_ui_layout::ConstraintsHold {
364 width: cranpose_ui_layout::AxisHold {
365 min: cranpose_ui_layout::BoundRange::up_to(text_size.width),
366 max: cranpose_ui_layout::BoundRange::from(text_size.width)
367 .intersect(widths.max_width_range())?,
368 },
369 height: cranpose_ui_layout::AxisHold::sized(text_size.height),
370 })
371 }
372
373 fn measure(
374 &self,
375 _context: &mut dyn ModifierNodeContext,
376 _measurable: &dyn Measurable,
377 constraints: Constraints,
378 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
379 let max_width = constraints
380 .max_width
381 .is_finite()
382 .then_some(constraints.max_width);
383 let (text_size, alignment_lines) = self.layout.measure_layout(max_width);
384 let text_size = self.pixel_size(text_size);
385
386 let width = text_size
387 .width
388 .clamp(constraints.min_width, constraints.max_width);
389 let height = text_size
390 .height
391 .clamp(constraints.min_height, constraints.max_height);
392
393 cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size { width, height })
394 .with_alignment_lines(alignment_lines)
395 }
396
397 fn min_intrinsic_width(
398 &self,
399 _measurable: &dyn Measurable,
400 _height: f32,
401 _density: f32,
402 ) -> f32 {
403 self.pixel_size(self.measure_text_content(None)).width
404 }
405
406 fn max_intrinsic_width(
407 &self,
408 _measurable: &dyn Measurable,
409 _height: f32,
410 _density: f32,
411 ) -> f32 {
412 self.pixel_size(self.measure_text_content(None)).width
413 }
414
415 fn min_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
416 self.pixel_size(
417 self.measure_text_content(Some(width).filter(|w| w.is_finite() && *w > 0.0)),
418 )
419 .height
420 }
421
422 fn max_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
423 self.pixel_size(
424 self.measure_text_content(Some(width).filter(|w| w.is_finite() && *w > 0.0)),
425 )
426 .height
427 }
428}
429
430impl DrawModifierNode for TextModifierNode {}
431
432impl SemanticsNode for TextModifierNode {
433 fn merge_semantics(&self, config: &mut SemanticsConfiguration) {
434 config
435 .content_description
436 .get_or_insert_with(|| self.text().to_string());
437 }
438
439 fn reach(&self) -> cranpose_foundation::SemanticsReach {
440 cranpose_foundation::SemanticsReach::default()
441 }
442}
443
444#[derive(Debug, Clone, PartialEq)]
453pub struct TextModifierElement {
454 text: Rc<AnnotatedString>,
455 style: TextStyle,
456 options: TextLayoutOptions,
457 density: Density,
458}
459
460impl TextModifierElement {
461 pub fn new(
464 text: Rc<AnnotatedString>,
465 style: TextStyle,
466 options: TextLayoutOptions,
467 density: Density,
468 ) -> Self {
469 Self {
470 text,
471 style,
472 options: options.normalized(),
473 density,
474 }
475 }
476}
477
478impl Hash for TextModifierElement {
479 fn hash<H: Hasher>(&self, state: &mut H) {
480 self.text.render_hash().hash(state);
481 self.options.hash(state);
482 self.density.density().to_bits().hash(state);
483 }
484}
485
486impl ModifierNodeElement for TextModifierElement {
487 type Node = TextModifierNode;
488
489 fn create(&self) -> Self::Node {
490 TextModifierNode::new(
491 self.text.clone(),
492 self.style.clone(),
493 self.options,
494 self.density,
495 )
496 }
497
498 fn update(&self, node: &mut Self::Node) {
499 node.density = self.density;
500 let current = node.layout.as_ref();
501 let same_style = *current.style == self.style;
502 if current.text != self.text || !same_style || current.options != self.options {
503 let style = if same_style {
506 (std::sync::Arc::clone(¤t.style), current.style_hash)
507 } else {
508 (
509 std::sync::Arc::new(self.style.clone()),
510 self.style.render_hash(),
511 )
512 };
513 let owner = TextPreparedLayoutOwner::new(
514 self.text.clone(),
515 style,
516 self.options,
517 current.node_id(),
518 current.measured_max_width.get(),
519 false,
520 );
521 match Rc::get_mut(&mut node.layout) {
523 Some(layout) => *layout = owner,
524 None => node.layout = Rc::new(owner),
525 }
526 }
527 }
528
529 fn capabilities(&self) -> NodeCapabilities {
530 NodeCapabilities::LAYOUT | NodeCapabilities::DRAW | NodeCapabilities::SEMANTICS
531 }
532}
533
534#[cfg(test)]
535#[path = "tests/text_modifier_node_tests.rs"]
536mod tests;