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