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