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