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 measure_text_content(&self, max_width: Option<f32>) -> Size {
157 self.with_prepared(max_width, |prepared| Size {
158 width: prepared.metrics.width,
159 height: prepared.metrics.height,
160 })
161 }
162
163 fn measure_layout(&self, max_width: Option<f32>) -> (Size, cranpose_ui_layout::AlignmentLines) {
164 self.measured_max_width.set(Some(max_width));
165 self.with_prepared(max_width, |prepared| {
166 (
167 Size::new(prepared.metrics.width, prepared.metrics.height),
168 prepared.alignment_lines,
169 )
170 })
171 }
172
173 fn measured_layout(&self) -> Option<Rc<crate::text::PreparedTextLayout>> {
174 self.measured_max_width
175 .get()
176 .map(|max_width| self.with_prepared(max_width, Rc::clone))
177 }
178}
179
180impl TextPreparedLayoutHandle {
181 fn new(owner: Rc<TextPreparedLayoutOwner>) -> Self {
182 Self { owner }
183 }
184
185 pub(crate) fn annotated_text(&self) -> &Rc<AnnotatedString> {
186 &self.owner.text
187 }
188
189 pub(crate) fn style(&self) -> &TextStyle {
190 self.owner.style()
191 }
192
193 pub(crate) fn options(&self) -> TextLayoutOptions {
194 self.owner.options()
195 }
196
197 pub(crate) fn measured_layout(&self) -> Option<Rc<crate::text::PreparedTextLayout>> {
198 self.owner.measured_layout()
199 }
200}
201
202impl TextModifierNode {
203 pub fn new(
205 text: Rc<AnnotatedString>,
206 style: TextStyle,
207 options: TextLayoutOptions,
208 density: Density,
209 ) -> Self {
210 Self {
211 layout: Rc::new(TextPreparedLayoutOwner::new(
212 text,
213 std::sync::Arc::new(style),
214 options,
215 None,
216 None,
217 )),
218 density,
219 state: NodeState::new(),
220 }
221 }
222
223 fn pixel_size(&self, size: Size) -> Size {
227 Size {
228 width: self.density.ceil(size.width),
229 height: self.density.ceil(size.height),
230 }
231 }
232
233 pub fn text(&self) -> &str {
234 self.layout.text()
235 }
236
237 pub fn annotated_text(&self) -> Rc<AnnotatedString> {
238 self.layout.annotated_text()
239 }
240
241 pub fn annotated_string(&self) -> AnnotatedString {
242 self.layout.annotated_string()
243 }
244
245 pub fn style(&self) -> &TextStyle {
246 self.layout.style()
247 }
248
249 pub fn options(&self) -> TextLayoutOptions {
250 self.layout.options()
251 }
252
253 fn measure_text_content(&self, max_width: Option<f32>) -> Size {
254 self.layout.measure_text_content(max_width)
255 }
256
257 pub(crate) fn prepared_layout_handle(&self) -> TextPreparedLayoutHandle {
258 TextPreparedLayoutHandle::new(self.layout.clone())
259 }
260}
261
262impl DelegatableNode for TextModifierNode {
263 fn node_state(&self) -> &NodeState {
264 &self.state
265 }
266}
267
268impl ModifierNode for TextModifierNode {
269 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
270 self.layout.set_node_id(context.node_id());
271 context.invalidate(InvalidationKind::Layout);
272 context.invalidate(InvalidationKind::Draw);
273 context.invalidate(InvalidationKind::Semantics);
274 }
275
276 fn on_detach(&mut self) {
277 self.layout.set_node_id(None);
278 }
279
280 fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
281 Some(self)
282 }
283
284 fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
285 Some(self)
286 }
287
288 fn as_semantics_node(&self) -> Option<&dyn SemanticsNode> {
289 Some(self)
290 }
291
292 fn as_semantics_node_mut(&mut self) -> Option<&mut dyn SemanticsNode> {
293 Some(self)
294 }
295
296 fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
297 Some(self)
298 }
299
300 fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
301 Some(self)
302 }
303}
304
305impl LayoutModifierNode for TextModifierNode {
306 fn measure(
307 &self,
308 _context: &mut dyn ModifierNodeContext,
309 _measurable: &dyn Measurable,
310 constraints: Constraints,
311 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
312 let max_width = constraints
313 .max_width
314 .is_finite()
315 .then_some(constraints.max_width);
316 let (text_size, alignment_lines) = self.layout.measure_layout(max_width);
317 let text_size = self.pixel_size(text_size);
318
319 let width = text_size
320 .width
321 .clamp(constraints.min_width, constraints.max_width);
322 let height = text_size
323 .height
324 .clamp(constraints.min_height, constraints.max_height);
325
326 cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size { width, height })
327 .with_alignment_lines(alignment_lines)
328 }
329
330 fn min_intrinsic_width(
331 &self,
332 _measurable: &dyn Measurable,
333 _height: f32,
334 _density: f32,
335 ) -> f32 {
336 self.pixel_size(self.measure_text_content(None)).width
337 }
338
339 fn max_intrinsic_width(
340 &self,
341 _measurable: &dyn Measurable,
342 _height: f32,
343 _density: f32,
344 ) -> f32 {
345 self.pixel_size(self.measure_text_content(None)).width
346 }
347
348 fn min_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
349 self.pixel_size(
350 self.measure_text_content(Some(width).filter(|w| w.is_finite() && *w > 0.0)),
351 )
352 .height
353 }
354
355 fn max_intrinsic_height(&self, _measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
356 self.pixel_size(
357 self.measure_text_content(Some(width).filter(|w| w.is_finite() && *w > 0.0)),
358 )
359 .height
360 }
361}
362
363impl DrawModifierNode for TextModifierNode {}
364
365impl SemanticsNode for TextModifierNode {
366 fn merge_semantics(&self, config: &mut SemanticsConfiguration) {
367 config
368 .content_description
369 .get_or_insert_with(|| self.text().to_string());
370 }
371
372 fn reach(&self) -> cranpose_foundation::SemanticsReach {
373 cranpose_foundation::SemanticsReach::default()
374 }
375}
376
377#[derive(Debug, Clone, PartialEq)]
386pub struct TextModifierElement {
387 text: Rc<AnnotatedString>,
388 style: TextStyle,
389 options: TextLayoutOptions,
390 density: Density,
391}
392
393impl TextModifierElement {
394 pub fn new(
397 text: Rc<AnnotatedString>,
398 style: TextStyle,
399 options: TextLayoutOptions,
400 density: Density,
401 ) -> Self {
402 Self {
403 text,
404 style,
405 options: options.normalized(),
406 density,
407 }
408 }
409}
410
411impl Hash for TextModifierElement {
412 fn hash<H: Hasher>(&self, state: &mut H) {
413 self.text.render_hash().hash(state);
414 self.style.render_hash().hash(state);
415 self.options.hash(state);
416 self.density.density().to_bits().hash(state);
417 }
418}
419
420impl ModifierNodeElement for TextModifierElement {
421 type Node = TextModifierNode;
422
423 fn create(&self) -> Self::Node {
424 TextModifierNode::new(
425 self.text.clone(),
426 self.style.clone(),
427 self.options,
428 self.density,
429 )
430 }
431
432 fn update(&self, node: &mut Self::Node) {
433 node.density = self.density;
434 let current = node.layout.as_ref();
435 let same_style = *current.style == self.style;
436 if current.text != self.text || !same_style || current.options != self.options {
437 let style = if same_style {
440 std::sync::Arc::clone(¤t.style)
441 } else {
442 std::sync::Arc::new(self.style.clone())
443 };
444 let owner = TextPreparedLayoutOwner::new(
445 self.text.clone(),
446 style,
447 self.options,
448 current.node_id(),
449 current.measured_max_width.get(),
450 );
451 match Rc::get_mut(&mut node.layout) {
453 Some(layout) => *layout = owner,
454 None => node.layout = Rc::new(owner),
455 }
456 }
457 }
458
459 fn capabilities(&self) -> NodeCapabilities {
460 NodeCapabilities::LAYOUT | NodeCapabilities::DRAW | NodeCapabilities::SEMANTICS
461 }
462}
463
464#[cfg(test)]
465#[path = "tests/text_modifier_node_tests.rs"]
466mod tests;