1use rapidhash::fast::RapidHasher;
4use repose_core::{
5 Brush, Color, Dp, Modifier, Px, Sp, TextOverflow, View, ViewKind,
6 animation::{AnimationSpec, Easing},
7 scroll::ScrollBinding,
8 text::{Shadow, SpanStyle, TextStyle},
9};
10use std::hash::{Hash, Hasher};
11
12#[allow(dead_code)]
13fn hash_view_content_inner(view: &View, hasher: &mut impl Hasher) {
14 view.id.hash(hasher);
15 hash_view_kind(&view.kind, hasher);
16 hash_modifier(&view.modifier, hasher);
17 view.scope_key.hash(hasher);
18 if let Some(semantics) = &view.semantics {
19 1u8.hash(hasher);
20 std::mem::discriminant(&semantics.role).hash(hasher);
21 semantics.label.hash(hasher);
22 semantics.focused.hash(hasher);
23 semantics.enabled.hash(hasher);
24 semantics.selectable_group.hash(hasher);
25 semantics.checked.hash(hasher);
26 semantics.selected.hash(hasher);
27 semantics.value.hash(hasher);
28 } else {
29 0u8.hash(hasher);
30 }
31 match view.modifier.key {
32 Some(key) => {
33 1u8.hash(hasher);
34 key.hash(hasher);
35 }
36 None => 0u8.hash(hasher),
37 }
38}
39
40pub fn hash_view_content(view: &View) -> u64 {
41 hash_view_facets(view).combined()
42}
43
44#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
45pub struct ViewHashes {
46 pub layout: u64,
47 pub measurement: u64,
48 pub paint: u64,
49 pub semantics: u64,
50}
51
52impl ViewHashes {
53 pub fn combined(self) -> u64 {
54 let mut hasher = RapidHasher::default();
55 0x5245_504f_5345_2d48u64.hash(&mut hasher);
56 self.layout.hash(&mut hasher);
57 self.measurement.hash(&mut hasher);
58 self.paint.hash(&mut hasher);
59 self.semantics.hash(&mut hasher);
60 hasher.finish()
61 }
62}
63
64#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
65pub struct SubtreeHashes {
66 pub layout: u64,
67 pub measurement: u64,
68 pub paint: u64,
69 pub semantics: u64,
70}
71
72impl SubtreeHashes {
73 pub fn combined(self) -> u64 {
74 let mut hasher = RapidHasher::default();
75 0x5245_504f_5345_2d53u64.hash(&mut hasher);
76 self.layout.hash(&mut hasher);
77 self.measurement.hash(&mut hasher);
78 self.paint.hash(&mut hasher);
79 self.semantics.hash(&mut hasher);
80 hasher.finish()
81 }
82}
83
84pub(crate) struct SubtreeHashBuilder {
85 layout: RapidHasher<'static>,
86 measurement: RapidHasher<'static>,
87 paint: RapidHasher<'static>,
88 semantics: RapidHasher<'static>,
89}
90
91impl SubtreeHashBuilder {
92 pub fn new(child_count: usize) -> Self {
93 let mut builder = Self {
94 layout: RapidHasher::default(),
95 measurement: RapidHasher::default(),
96 paint: RapidHasher::default(),
97 semantics: RapidHasher::default(),
98 };
99 builder.layout.write_u64(0x5355_4254_5245_45);
100 builder.measurement.write_u64(0x534d_4541_5355_5245);
101 builder.paint.write_u64(0x5041_494e_545f_5355);
102 builder.semantics.write_u64(0x5345_4d41_4e54_4943);
103 builder.layout.write_usize(child_count);
104 builder.measurement.write_usize(child_count);
105 builder.paint.write_usize(child_count);
106 builder.semantics.write_usize(child_count);
107 builder
108 }
109
110 pub fn push(&mut self, child: SubtreeHashes) {
111 self.layout.write_u64(child.layout);
112 self.measurement.write_u64(child.measurement);
113 self.paint.write_u64(child.paint);
114 self.semantics.write_u64(child.semantics);
115 }
116
117 pub fn finish(mut self, content: ViewHashes) -> SubtreeHashes {
118 self.layout.write_u64(content.layout);
119 self.measurement.write_u64(content.measurement);
120 self.paint.write_u64(content.paint);
121 self.semantics.write_u64(content.semantics);
122 SubtreeHashes {
123 layout: self.layout.finish(),
124 measurement: self.measurement.finish(),
125 paint: self.paint.finish(),
126 semantics: self.semantics.finish(),
127 }
128 }
129}
130
131const FACET_LAYOUT: u8 = 1;
132const FACET_MEASUREMENT: u8 = 2;
133const FACET_PAINT: u8 = 4;
134const FACET_SEMANTICS: u8 = 8;
135const FACET_LAYOUT_MEASUREMENT: u8 = FACET_LAYOUT | FACET_MEASUREMENT;
136const FACET_LAYOUT_PAINT: u8 = FACET_LAYOUT | FACET_PAINT;
137const FACET_LAYOUT_MEASUREMENT_PAINT: u8 = FACET_LAYOUT | FACET_MEASUREMENT | FACET_PAINT;
138const FACET_ALL: u8 = FACET_LAYOUT | FACET_MEASUREMENT | FACET_PAINT | FACET_SEMANTICS;
139
140type FacetHasher = RapidHasher<'static>;
141
142struct FacetHashers {
143 hashers: [FacetHasher; 4],
144}
145
146impl Default for FacetHashers {
147 fn default() -> Self {
148 Self {
149 hashers: [
150 FacetHasher::default(),
151 FacetHasher::default(),
152 FacetHasher::default(),
153 FacetHasher::default(),
154 ],
155 }
156 }
157}
158
159impl FacetHashers {
160 fn hash(&mut self, mask: u8, write: impl Fn(&mut FacetHasher)) {
161 for (index, hasher) in self.hashers.iter_mut().enumerate() {
162 if mask & (1 << index) != 0 {
163 write(hasher);
164 }
165 }
166 }
167
168 fn finish(self) -> ViewHashes {
169 ViewHashes {
170 layout: self.hashers[0].finish(),
171 measurement: self.hashers[1].finish(),
172 paint: self.hashers[2].finish(),
173 semantics: self.hashers[3].finish(),
174 }
175 }
176}
177
178pub fn hash_view_facets(view: &View) -> ViewHashes {
179 let mut hashers = FacetHashers::default();
180 hashers.hash(FACET_ALL, |hasher| {
181 0x5245_504f_5345_2d56u64.hash(hasher);
182 });
183 facet_hash_view_kind(&view.kind, &mut hashers);
184 facet_hash_modifier(&view.modifier, &mut hashers);
185 hashers.hash(FACET_LAYOUT_PAINT, |hasher| view.id.hash(hasher));
186 hashers.hash(FACET_ALL ^ FACET_SEMANTICS, |hasher| {
187 view.scope_key.hash(hasher);
188 });
189 facet_hash_semantics(view.semantics.as_ref(), &mut hashers, FACET_SEMANTICS);
190 hashers.finish()
191}
192
193pub fn hash_view_layout(view: &View) -> u64 {
194 hash_view_facets(view).layout
195}
196
197pub fn hash_view_measurement(view: &View) -> u64 {
198 hash_view_facets(view).measurement
199}
200
201pub fn hash_view_paint(view: &View) -> u64 {
202 hash_view_facets(view).paint
203}
204
205pub fn hash_view_semantics(view: &View) -> u64 {
206 hash_view_facets(view).semantics
207}
208
209pub fn hash_subtree_facets(content: ViewHashes, children: &[SubtreeHashes]) -> SubtreeHashes {
210 let mut builder = SubtreeHashBuilder::new(children.len());
211 for child in children {
212 builder.push(*child);
213 }
214 builder.finish(content)
215}
216
217fn facet_hash_view_kind(kind: &ViewKind, hashers: &mut FacetHashers) {
218 hashers.hash(FACET_ALL, |hasher| {
219 std::mem::discriminant(kind).hash(hasher);
220 });
221 match kind {
222 ViewKind::Text {
223 text,
224 color,
225 font_size,
226 soft_wrap,
227 max_lines,
228 overflow,
229 font_family,
230 annotations,
231 text_align,
232 font_weight,
233 font_style,
234 text_decoration,
235 letter_spacing,
236 line_height,
237 url,
238 font_variation_settings,
239 draw_style,
240 } => {
241 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| text.hash(h));
242 hashers.hash(FACET_PAINT, |h| hash_color(color, h));
243 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| hash_sp(*font_size, h));
244 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| soft_wrap.hash(h));
245 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| max_lines.hash(h));
246 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| {
247 hash_text_overflow(overflow, h)
248 });
249 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| font_family.hash(h));
250 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| text_align.hash(h));
251 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| font_weight.0.hash(h));
252 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| font_style.hash(h));
253 hashers.hash(FACET_PAINT, |h| hash_text_decoration(text_decoration, h));
254 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| hash_sp(*letter_spacing, h));
255 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| hash_sp(*line_height, h));
256 hashers.hash(FACET_PAINT, |h| url.hash(h));
257 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| {
258 font_variation_settings.hash(h)
259 });
260 hashers.hash(FACET_LAYOUT_MEASUREMENT_PAINT, |h| {
261 hash_draw_style(draw_style, h)
262 });
263 if let Some(annotations) = annotations {
264 hashers.hash(FACET_LAYOUT_MEASUREMENT_PAINT, |h| {
265 1u8.hash(h);
266 annotations.len().hash(h);
267 for span in annotations.iter() {
268 span.start.hash(h);
269 span.end.hash(h);
270 }
271 });
272 for span in annotations.iter() {
273 facet_hash_span_style(&span.style, hashers, FACET_ALL);
274 if let Some(url) = &span.url {
275 hashers.hash(FACET_PAINT, |h| url.hash(h));
276 } else {
277 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
278 }
279 }
280 } else {
281 hashers.hash(FACET_LAYOUT_MEASUREMENT_PAINT, |h| 0u8.hash(h));
282 }
283 }
284 ViewKind::Image {
285 handle,
286 tint,
287 fit,
288 filter,
289 source_rect,
290 alignment,
291 } => {
292 hashers.hash(FACET_LAYOUT_MEASUREMENT_PAINT, |h| handle.hash(h));
293 hashers.hash(FACET_MEASUREMENT, |h| {
297 repose_core::image_intrinsic_size(*handle).hash(h)
298 });
299 hashers.hash(FACET_PAINT, |h| hash_color(tint, h));
300 hashers.hash(FACET_PAINT, |h| std::mem::discriminant(fit).hash(h));
301 hashers.hash(FACET_PAINT, |h| std::mem::discriminant(filter).hash(h));
302 hashers.hash(FACET_PAINT, |h| source_rect.hash(h));
303 hashers.hash(FACET_PAINT, |h| std::mem::discriminant(alignment).hash(h));
304 }
305 ViewKind::Box
306 | ViewKind::Row
307 | ViewKind::Column
308 | ViewKind::ZStack
309 | ViewKind::OverlayHost
310 | ViewKind::SubcomposeLayout { .. } => {}
311 _ => {}
312 }
313}
314
315fn facet_hash_modifier(modifier: &Modifier, hashers: &mut FacetHashers) {
316 macro_rules! hash_value {
317 ($mask:expr, $value:expr) => {
318 hashers.hash($mask, |hasher| $value.hash(hasher));
319 };
320 }
321 macro_rules! hash_f32_value {
322 ($mask:expr, $value:expr) => {
323 hashers.hash($mask, |hasher| hash_f32($value, hasher));
324 };
325 }
326 macro_rules! hash_opt_f32_value {
327 ($mask:expr, $value:expr) => {
328 hashers.hash($mask, |hasher| hash_opt_f32($value, hasher));
329 };
330 }
331 macro_rules! hash_opt_dp_value {
332 ($mask:expr, $value:expr) => {
333 hashers.hash($mask, |hasher| hash_opt_dp($value, hasher));
334 };
335 }
336
337 hash_value!(FACET_LAYOUT_PAINT, modifier.key);
338 match modifier.size {
339 Some(size) => {
340 hashers.hash(FACET_LAYOUT, |h| {
341 hash_dp(size.width, h);
342 hash_dp(size.height, h);
343 });
344 }
345 None => hashers.hash(FACET_LAYOUT, |h| 0u8.hash(h)),
346 }
347 hash_opt_dp_value!(FACET_LAYOUT, modifier.width);
348 hash_opt_dp_value!(FACET_LAYOUT, modifier.height);
349 match modifier.required_size {
350 Some(size) => hashers.hash(FACET_LAYOUT, |h| {
351 hash_dp(size.width, h);
352 hash_dp(size.height, h);
353 }),
354 None => hashers.hash(FACET_LAYOUT, |h| 0u8.hash(h)),
355 }
356 hash_opt_dp_value!(FACET_LAYOUT, modifier.required_min_width);
357 hash_opt_dp_value!(FACET_LAYOUT, modifier.required_max_width);
358 hash_opt_dp_value!(FACET_LAYOUT, modifier.required_min_height);
359 hash_opt_dp_value!(FACET_LAYOUT, modifier.required_max_height);
360 hash_opt_dp_value!(FACET_LAYOUT, modifier.default_min_width);
361 hash_opt_dp_value!(FACET_LAYOUT, modifier.default_min_height);
362 hash_opt_f32_value!(FACET_LAYOUT, modifier.fill_max);
363 hash_opt_f32_value!(FACET_LAYOUT, modifier.fill_max_w);
364 hash_opt_f32_value!(FACET_LAYOUT, modifier.fill_max_h);
365 hashers.hash(FACET_PAINT, |h| modifier.repaint_boundary.hash(h));
366 hash_opt_dp_value!(FACET_LAYOUT, modifier.padding);
367 match modifier.padding_values {
368 Some(padding) => hashers.hash(FACET_LAYOUT, |h| {
369 hash_dp(padding.left, h);
370 hash_dp(padding.right, h);
371 hash_dp(padding.top, h);
372 hash_dp(padding.bottom, h);
373 }),
374 None => hashers.hash(FACET_LAYOUT, |h| 0u8.hash(h)),
375 }
376 hash_opt_dp_value!(FACET_LAYOUT, modifier.min_width);
377 hash_opt_dp_value!(FACET_LAYOUT, modifier.min_height);
378 hash_opt_dp_value!(FACET_LAYOUT, modifier.max_width);
379 hash_opt_dp_value!(FACET_LAYOUT, modifier.max_height);
380 if let Some(background) = &modifier.background {
381 hashers.hash(FACET_PAINT, |h| hash_brush(background, h));
382 } else {
383 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
384 }
385 if let Some(border) = &modifier.border {
386 hashers.hash(FACET_PAINT, |h| {
387 hash_dp(border.width, h);
388 hash_brush(&border.brush, h);
389 for radius in border.radius {
390 hash_dp(radius, h);
391 }
392 });
393 } else {
394 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
395 }
396 hash_opt_f32_value!(FACET_LAYOUT, modifier.flex_grow);
397 hash_opt_f32_value!(FACET_LAYOUT, modifier.flex_shrink);
398 hash_opt_dp_value!(FACET_LAYOUT, modifier.flex_basis);
399 hashers.hash(FACET_LAYOUT, |h| {
400 modifier
401 .flex_wrap
402 .map(|value| std::mem::discriminant(&value))
403 .hash(h);
404 modifier.flex_basis_content.hash(h);
405 modifier.flex_line_count.hash(h);
406 modifier
407 .flex_dir
408 .map(|value| std::mem::discriminant(&value))
409 .hash(h);
410 });
411 hashers.hash(FACET_LAYOUT, |h| {
412 modifier
413 .align_self
414 .map(|value| (value.keyword as u8, value.safety as u8))
415 .hash(h);
416 modifier
417 .justify_content
418 .map(|value| (value.keyword as u8, value.safety as u8))
419 .hash(h);
420 modifier
421 .align_items_container
422 .map(|value| (value.keyword as u8, value.safety as u8))
423 .hash(h);
424 modifier
425 .align_content
426 .map(|value| (value.keyword as u8, value.safety as u8))
427 .hash(h);
428 modifier.baseline_align.hash(h);
429 });
430 if let Some(radius) = modifier.clip_rounded {
431 hashers.hash(FACET_PAINT, |h| {
432 for value in radius {
433 hash_dp(value, h);
434 }
435 });
436 } else {
437 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
438 }
439 if let Some(transform) = &modifier.transform {
440 hashers.hash(FACET_PAINT, |h| {
441 hash_f32(transform.translate_x, h);
442 hash_f32(transform.translate_y, h);
443 hash_f32(transform.scale_x, h);
444 hash_f32(transform.scale_y, h);
445 hash_f32(transform.rotate, h);
446 hash_f32(transform.shear_x, h);
447 hash_f32(transform.shear_y, h);
448 hash_f32(transform.origin_x, h);
449 hash_f32(transform.origin_y, h);
450 for value in transform.perspective {
451 hash_f32(value, h);
452 }
453 });
454 } else {
455 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
456 }
457 hash_opt_f32_value!(FACET_PAINT, modifier.alpha);
458 hashers.hash(FACET_LAYOUT, |h| {
459 modifier
460 .position_type
461 .map(|value| std::mem::discriminant(&value))
462 .hash(h);
463 });
464 hash_opt_dp_value!(FACET_LAYOUT, modifier.offset_left);
465 hash_opt_dp_value!(FACET_LAYOUT, modifier.offset_right);
466 hash_opt_dp_value!(FACET_LAYOUT, modifier.offset_top);
467 hash_opt_dp_value!(FACET_LAYOUT, modifier.offset_bottom);
468 if let Some(grid) = &modifier.grid {
469 hashers.hash(FACET_LAYOUT, |h| {
470 grid.columns.hash(h);
471 hash_dp(grid.row_gap, h);
472 hash_dp(grid.column_gap, h);
473 });
474 } else {
475 hashers.hash(FACET_LAYOUT, |h| 0u8.hash(h));
476 }
477 hashers.hash(FACET_LAYOUT, |h| {
478 modifier.grid_col_span.hash(h);
479 modifier.grid_row_span.hash(h);
480 });
481 hash_opt_f32_value!(FACET_LAYOUT, modifier.aspect_ratio);
482 hashers.hash(FACET_LAYOUT, |h| modifier.intrinsic_width.hash(h));
483 hashers.hash(FACET_LAYOUT, |h| modifier.intrinsic_height.hash(h));
484 hash_opt_dp_value!(FACET_LAYOUT, modifier.fit_content_width);
485 hash_opt_dp_value!(FACET_LAYOUT, modifier.fit_content_height);
486 hashers.hash(FACET_LAYOUT, |h| match modifier.contain {
487 None => 0u8.hash(h),
488 Some(value) if value == taffy::Contain::CONTENT => 3u8.hash(h),
489 Some(value) if value == taffy::Contain::LAYOUT => 1u8.hash(h),
490 Some(value) if value == taffy::Contain::PAINT => 2u8.hash(h),
491 Some(_) => 4u8.hash(h),
492 });
493 hash_f32_value!(FACET_PAINT, modifier.z_index);
494 hash_opt_f32_value!(FACET_PAINT, modifier.render_z_index);
495 hashers.hash(FACET_PAINT, |h| {
496 modifier.input_blocker.hash(h);
497 modifier.click.hash(h);
498 modifier.disabled.hash(h);
499 modifier.focusable.hash(h);
500 modifier.hit_passthrough.hash(h);
501 });
502 hashers.hash(FACET_LAYOUT, |h| modifier.propagate_min.hash(h));
503 hashers.hash(FACET_PAINT, |h| modifier.focus_group.hash(h));
504 if let Some(blur) = &modifier.blur {
505 hashers.hash(FACET_PAINT, |h| {
506 hash_dp(blur.radius_x, h);
507 hash_dp(blur.radius_y, h);
508 std::mem::discriminant(&blur.edge_treatment).hash(h);
509 });
510 } else {
511 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
512 }
513 facet_hash_scroll_binding(modifier.scroll.as_ref(), hashers);
514 facet_hash_nested_scroll_connection(modifier.nested_scroll_connection.as_ref(), hashers);
515 facet_hash_scrollbar_style(modifier.scrollbar_style.as_ref(), hashers);
516 hashers.hash(FACET_PAINT, |h| {
517 modifier
518 .overflow
519 .map(|value| std::mem::discriminant(&value))
520 .hash(h);
521 });
522 if let Some(rect) = modifier.clip_rect {
523 hashers.hash(FACET_PAINT, |h| {
524 hash_dp(rect.left, h);
525 hash_dp(rect.top, h);
526 hash_dp(rect.right, h);
527 hash_dp(rect.bottom, h);
528 std::mem::discriminant(&rect.op).hash(h);
529 });
530 } else {
531 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
532 }
533 hash_opt_dp_value!(FACET_LAYOUT, modifier.gap);
534 hash_opt_dp_value!(FACET_LAYOUT, modifier.row_gap);
535 hash_opt_dp_value!(FACET_LAYOUT, modifier.column_gap);
536 hash_opt_dp_value!(FACET_LAYOUT, modifier.margin_top);
537 hash_opt_dp_value!(FACET_LAYOUT, modifier.margin_left);
538 hash_opt_dp_value!(FACET_LAYOUT, modifier.margin_right);
539 hash_opt_dp_value!(FACET_LAYOUT, modifier.margin_bottom);
540 hash_opt_f32_value!(FACET_PAINT, modifier.graphics_layer);
541 if let Some(shadow) = &modifier.shadow {
542 hashers.hash(FACET_PAINT, |h| {
543 hash_dp(shadow.blur_radius, h);
544 hash_dp(shadow.offset_y, h);
545 hash_color(&shadow.color, h);
546 });
547 } else {
548 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
549 }
550 facet_hash_semantics(modifier.semantics.as_ref(), hashers, FACET_SEMANTICS);
551 hashers.hash(FACET_PAINT, |h| {
552 hash_cursor(&modifier.cursor, h);
553 modifier.painter.is_some().hash(h);
554 modifier.text_selection.is_some().hash(h);
555 modifier.paint_callback.is_some().hash(h);
556 modifier.indication.is_some().hash(h);
557 modifier.drag_preview.is_some().hash(h);
558 modifier.on_scroll.is_some().hash(h);
559 modifier.on_pointer_down.is_some().hash(h);
560 modifier.on_pointer_move.is_some().hash(h);
561 modifier.on_pointer_up.is_some().hash(h);
562 modifier.on_pointer_cancel.is_some().hash(h);
563 modifier.on_pointer_enter.is_some().hash(h);
564 modifier.on_pointer_leave.is_some().hash(h);
565 modifier.on_click.is_some().hash(h);
566 modifier.on_double_click.is_some().hash(h);
567 modifier.on_long_click.is_some().hash(h);
568 modifier.on_globally_positioned.is_some().hash(h);
569 modifier.on_size_changed.is_some().hash(h);
570 modifier.on_key_event.is_some().hash(h);
571 modifier.on_preview_key_event.is_some().hash(h);
572 modifier.on_drag_start.is_some().hash(h);
573 modifier.on_drag_end.is_some().hash(h);
574 modifier.on_drag_enter.is_some().hash(h);
575 modifier.on_drag_over.is_some().hash(h);
576 modifier.on_drag_leave.is_some().hash(h);
577 modifier.on_drop.is_some().hash(h);
578 modifier.on_action.is_some().hash(h);
579 modifier.focus_requester.is_some().hash(h);
580 modifier.on_focus_changed.is_some().hash(h);
581 modifier.interaction_source.is_some().hash(h);
582 });
583 hashers.hash(FACET_LAYOUT, |h| {
584 if let Some(layout) = &modifier.layout {
585 1u8.hash(h);
586 hash_rc_ptr_identity(layout, h);
587 } else {
588 0u8.hash(h);
589 }
590 });
591 if let Some(input) = &modifier.text_input {
592 facet_hash_text_input(input, hashers);
593 } else {
594 hashers.hash(FACET_ALL, |h| 0u8.hash(h));
595 }
596 if let Some(colors) = &modifier.state_colors {
597 hashers.hash(FACET_PAINT, |h| {
598 hash_color(&colors.default, h);
599 hash_color(&colors.hovered, h);
600 hash_color(&colors.focused, h);
601 hash_color(&colors.pressed, h);
602 hash_color(&colors.disabled, h);
603 hash_color(&colors.dragged, h);
604 });
605 } else {
606 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
607 }
608 if let Some(elevation) = &modifier.state_elevation {
609 hashers.hash(FACET_PAINT, |h| {
610 hash_dp(elevation.default, h);
611 hash_dp(elevation.hovered, h);
612 hash_dp(elevation.focused, h);
613 hash_dp(elevation.pressed, h);
614 hash_dp(elevation.disabled, h);
615 hash_dp(elevation.dragged, h);
616 });
617 } else {
618 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
619 }
620 if let Some(spec) = &modifier.animate_content_size {
621 facet_hash_animation_spec(spec, hashers, FACET_LAYOUT);
622 } else {
623 hashers.hash(FACET_LAYOUT, |h| 0u8.hash(h));
624 }
625}
626
627fn facet_hash_semantics(
628 semantics: Option<&repose_core::Semantics>,
629 hashers: &mut FacetHashers,
630 mask: u8,
631) {
632 hashers.hash(mask, |hasher| {
633 if let Some(semantics) = semantics {
634 1u8.hash(hasher);
635 std::mem::discriminant(&semantics.role).hash(hasher);
636 semantics.label.hash(hasher);
637 semantics.focused.hash(hasher);
638 semantics.enabled.hash(hasher);
639 semantics.selectable_group.hash(hasher);
640 semantics.checked.hash(hasher);
641 semantics.selected.hash(hasher);
642 semantics.value.hash(hasher);
643 } else {
644 0u8.hash(hasher);
645 }
646 });
647}
648
649fn facet_hash_span_style(style: &SpanStyle, hashers: &mut FacetHashers, mask: u8) {
650 hashers.hash(mask & FACET_PAINT, |h| {
651 hash_opt_color(style.color.as_ref(), h)
652 });
653 hashers.hash(mask & FACET_LAYOUT_MEASUREMENT, |h| {
654 if let Some(size) = style.font_size {
655 1u8.hash(h);
656 hash_sp(size, h);
657 } else {
658 0u8.hash(h);
659 }
660 });
661 hashers.hash(mask & FACET_LAYOUT_MEASUREMENT, |h| {
662 style.font_weight.hash(h);
663 style.font_family.hash(h);
664 style.font_style.hash(h);
665 style.text_align.hash(h);
666 if let Some(value) = style.letter_spacing {
667 1u8.hash(h);
668 hash_sp(value, h);
669 } else {
670 0u8.hash(h);
671 }
672 if let Some(value) = style.line_height {
673 1u8.hash(h);
674 hash_sp(value, h);
675 } else {
676 0u8.hash(h);
677 }
678 });
679 hashers.hash(mask & FACET_PAINT, |h| {
680 hash_opt_color(style.background.as_ref(), h);
681 if let Some(decoration) = &style.text_decoration {
682 1u8.hash(h);
683 hash_text_decoration(decoration, h);
684 } else {
685 0u8.hash(h);
686 }
687 hash_f32(style.alpha, h);
688 });
689 hashers.hash(mask & FACET_LAYOUT_MEASUREMENT, |h| {
690 hash_discriminant_option(&style.text_direction, h);
691 hash_discriminant_option(&style.font_synthesis, h);
692 if let Some(value) = style.baseline_shift {
693 1u8.hash(h);
694 hash_f32(value.0, h);
695 } else {
696 0u8.hash(h);
697 }
698 hash_discriminant_option(&style.hyphens, h);
699 hash_discriminant_option(&style.line_break, h);
700 if let Some(indent) = &style.text_indent {
701 1u8.hash(h);
702 hash_dp(indent.first_line, h);
703 hash_dp(indent.rest_lines, h);
704 } else {
705 0u8.hash(h);
706 }
707 });
708 hashers.hash(mask & FACET_LAYOUT_MEASUREMENT_PAINT, |h| {
709 if let Some(style) = &style.draw_style {
710 1u8.hash(h);
711 hash_draw_style(style, h);
712 } else {
713 0u8.hash(h);
714 }
715 });
716 hashers.hash(mask & FACET_LAYOUT_MEASUREMENT, |h| {
717 style.font_variation_settings.hash(h);
718 });
719}
720
721fn facet_hash_text_style(style: &TextStyle, hashers: &mut FacetHashers) {
722 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| {
723 hash_sp(style.font_size, h);
724 style.font_weight.hash(h);
725 style.font_family.hash(h);
726 style.font_style.hash(h);
727 style.text_align.hash(h);
728 hash_sp(style.letter_spacing, h);
729 hash_sp(style.line_height, h);
730 hash_discriminant_option(&style.text_direction, h);
731 std::mem::discriminant(&style.font_synthesis).hash(h);
732 hash_f32(style.baseline_shift.0, h);
733 std::mem::discriminant(&style.hyphens).hash(h);
734 std::mem::discriminant(&style.line_break).hash(h);
735 if let Some(indent) = &style.text_indent {
736 1u8.hash(h);
737 hash_dp(indent.first_line, h);
738 hash_dp(indent.rest_lines, h);
739 } else {
740 0u8.hash(h);
741 }
742 hash_draw_style(&style.draw_style, h);
743 style.locale_list.hash(h);
744 style.font_feature_settings.hash(h);
745 style.font_variation_settings.hash(h);
746 });
747 hashers.hash(FACET_PAINT, |h| {
748 hash_opt_color(style.color.as_ref(), h);
749 hash_opt_color(style.background.as_ref(), h);
750 if let Some(decoration) = &style.text_decoration {
751 1u8.hash(h);
752 hash_text_decoration(decoration, h);
753 } else {
754 0u8.hash(h);
755 }
756 if let Some(shadow) = &style.shadow {
757 1u8.hash(h);
758 hash_shadow(shadow, h);
759 } else {
760 0u8.hash(h);
761 }
762 hash_f32(style.alpha, h);
763 hash_draw_style(&style.draw_style, h);
764 });
765}
766
767fn facet_hash_text_input(input: &repose_core::TextInputConfig, hashers: &mut FacetHashers) {
768 hashers.hash(FACET_LAYOUT_MEASUREMENT, |h| {
769 input.hint.hash(h);
770 input.multiline.hash(h);
771 input.value.hash(h);
772 input.max_lines.hash(h);
773 input.min_lines.hash(h);
774 match &input.line_limits {
775 Some(repose_core::text::TextFieldLineLimits::SingleLine) => 1u8.hash(h),
776 Some(repose_core::text::TextFieldLineLimits::MultiLine {
777 min_height_in_lines,
778 max_height_in_lines,
779 }) => {
780 2u8.hash(h);
781 min_height_in_lines.hash(h);
782 max_height_in_lines.hash(h);
783 }
784 None => 0u8.hash(h),
785 }
786 });
787 hashers.hash(FACET_PAINT, |h| {
788 input.on_change.is_some().hash(h);
789 input.on_submit.is_some().hash(h);
790 input.focus_tracker.is_some().hash(h);
791 input.enabled.hash(h);
792 input.read_only.hash(h);
793 input.sensitive.hash(h);
794 std::mem::discriminant(&input.keyboard_type).hash(h);
795 std::mem::discriminant(&input.capitalization).hash(h);
796 std::mem::discriminant(&input.ime_action).hash(h);
797 input.auto_correct_enabled.hash(h);
798 if let Some(color) = input.cursor_color {
799 1u8.hash(h);
800 hash_color(&color, h);
801 } else {
802 0u8.hash(h);
803 }
804 input.on_text_layout.is_some().hash(h);
805 input.interaction_source.is_some().hash(h);
806 input.visual_transformation.is_some().hash(h);
807 if let Some(actions) = &input.keyboard_actions {
808 1u8.hash(h);
809 actions.on_done.is_some().hash(h);
810 actions.on_go.is_some().hash(h);
811 actions.on_next.is_some().hash(h);
812 actions.on_previous.is_some().hash(h);
813 actions.on_search.is_some().hash(h);
814 actions.on_send.is_some().hash(h);
815 } else {
816 0u8.hash(h);
817 }
818 });
819 if let Some(style) = &input.text_style {
820 hashers.hash(FACET_LAYOUT_MEASUREMENT_PAINT, |h| 1u8.hash(h));
821 facet_hash_text_style(style, hashers);
822 } else {
823 hashers.hash(FACET_LAYOUT_MEASUREMENT_PAINT, |h| 0u8.hash(h));
824 }
825}
826
827fn facet_hash_nested_scroll_connection(
828 connection: Option<&repose_core::nested_scroll::NestedScrollConnection>,
829 hashers: &mut FacetHashers,
830) {
831 hashers.hash(FACET_PAINT, |h| match connection {
832 Some(connection) => {
833 1u8.hash(h);
834 if let Some(callback) = &connection.on_pre_scroll {
835 1u8.hash(h);
836 hash_rc_ptr_identity(callback, h);
837 } else {
838 0u8.hash(h);
839 }
840 if let Some(callback) = &connection.on_post_scroll {
841 1u8.hash(h);
842 hash_rc_ptr_identity(callback, h);
843 } else {
844 0u8.hash(h);
845 }
846 if let Some(callback) = &connection.on_pre_fling {
847 1u8.hash(h);
848 hash_rc_ptr_identity(callback, h);
849 } else {
850 0u8.hash(h);
851 }
852 if let Some(callback) = &connection.on_post_fling {
853 1u8.hash(h);
854 hash_rc_ptr_identity(callback, h);
855 } else {
856 0u8.hash(h);
857 }
858 }
859 None => 0u8.hash(h),
860 });
861}
862
863fn facet_hash_scrollbar_style(
864 style: Option<&repose_core::ScrollbarStyle>,
865 hashers: &mut FacetHashers,
866) {
867 let Some(style) = style else {
868 hashers.hash(FACET_PAINT, |h| 0u8.hash(h));
869 return;
870 };
871 hashers.hash(FACET_PAINT, |h| {
872 1u8.hash(h);
873 style.thickness.0.to_bits().hash(h);
874 style.track_inset.0.to_bits().hash(h);
875 style.min_thumb_length.0.to_bits().hash(h);
876 style
877 .fixed_thumb_length
878 .map(|value| value.0.to_bits())
879 .hash(h);
880 style.radius.map(|value| value.0.to_bits()).hash(h);
881 });
882 facet_hash_control_visual_set(&style.track_visuals, hashers);
883 facet_hash_control_visual_set(&style.thumb_visuals, hashers);
884}
885
886fn facet_hash_control_visual_set(set: &repose_core::ControlVisualSet, hashers: &mut FacetHashers) {
887 for visual in [
888 &set.normal,
889 &set.hovered,
890 &set.pressed,
891 &set.dragged,
892 &set.focused,
893 &set.disabled,
894 ] {
895 match visual {
896 Some(visual) => {
897 hashers.hash(FACET_PAINT, |h| 1u8.hash(h));
898 facet_hash_control_visual(visual, hashers);
899 }
900 None => hashers.hash(FACET_PAINT, |h| 0u8.hash(h)),
901 }
902 }
903}
904
905fn facet_hash_control_visual(visual: &repose_core::ControlVisual, hashers: &mut FacetHashers) {
906 hashers.hash(FACET_PAINT, |h| std::mem::discriminant(visual).hash(h));
907 match visual {
908 repose_core::ControlVisual::Rect { brush, radius } => {
909 hashers.hash(FACET_PAINT, |h| {
910 hash_brush(brush, h);
911 for value in radius {
912 value.0.to_bits().hash(h);
913 }
914 });
915 }
916 repose_core::ControlVisual::Image {
917 handle,
918 source_rect,
919 tint,
920 fit,
921 filter,
922 alignment,
923 } => {
924 hashers.hash(FACET_PAINT, |h| {
925 handle.hash(h);
926 source_rect.hash(h);
927 hash_color(tint, h);
928 std::mem::discriminant(fit).hash(h);
929 std::mem::discriminant(filter).hash(h);
930 std::mem::discriminant(alignment).hash(h);
931 });
932 }
933 repose_core::ControlVisual::Custom(_) => {
934 hashers.hash(FACET_PAINT, |h| {
935 visual.custom_identity().unwrap_or_default().hash(h)
936 });
937 }
938 _ => {}
939 }
940}
941
942fn facet_hash_scroll_binding(binding: Option<&ScrollBinding>, hashers: &mut FacetHashers) {
943 match binding {
944 Some(ScrollBinding::Vertical(binding)) => {
945 hashers.hash(FACET_LAYOUT_PAINT, |h| 1u8.hash(h));
946 facet_hash_scroll_axis(binding, hashers);
947 }
948 Some(ScrollBinding::Horizontal(binding)) => {
949 hashers.hash(FACET_LAYOUT_PAINT, |h| 2u8.hash(h));
950 facet_hash_scroll_axis(binding, hashers);
951 }
952 Some(ScrollBinding::Both(binding)) => {
953 hashers.hash(FACET_LAYOUT_PAINT, |h| 3u8.hash(h));
954 hashers.hash(FACET_LAYOUT, |h| binding.show_scrollbar.hash(h));
955 hashers.hash(FACET_PAINT, |h| {
956 binding.show_scrollbar.hash(h);
957 hash_rc_identity(&binding.on_scroll, h);
958 hash_rc_identity(&binding.set_viewport_width, h);
959 hash_rc_identity(&binding.set_viewport_height, h);
960 hash_rc_identity(&binding.set_content_width, h);
961 hash_rc_identity(&binding.set_content_height, h);
962 hash_rc_identity(&binding.get_offset_xy, h);
963 hash_rc_identity(&binding.set_offset_xy, h);
964 hash_rc_identity(&binding.tick, h);
965 hash_rc_identity(&binding.set_nested_scroll_parent, h);
966 });
967 }
968 None => hashers.hash(FACET_LAYOUT_PAINT, |h| 0u8.hash(h)),
969 }
970}
971
972fn facet_hash_scroll_axis(
973 binding: &repose_core::scroll::ScrollAxisBinding,
974 hashers: &mut FacetHashers,
975) {
976 hashers.hash(FACET_LAYOUT, |h| binding.show_scrollbar.hash(h));
977 hashers.hash(FACET_PAINT, |h| {
978 binding.show_scrollbar.hash(h);
979 hash_rc_identity(&binding.on_scroll, h);
980 hash_rc_identity(&binding.set_viewport_main, h);
981 hash_rc_identity(&binding.set_content_main, h);
982 hash_rc_identity(&binding.get_offset_main, h);
983 hash_rc_identity(&binding.set_offset_main, h);
984 hash_rc_identity(&binding.tick, h);
985 hash_rc_identity(&binding.set_nested_scroll_parent, h);
986 });
987}
988
989fn facet_hash_animation_spec(spec: &AnimationSpec, hashers: &mut FacetHashers, mask: u8) {
990 hashers.hash(mask, |h| {
991 spec.duration.as_nanos().hash(h);
992 hash_easing(&spec.easing, h);
993 spec.delay.as_nanos().hash(h);
994 if let Some(spring) = &spec.spring {
995 1u8.hash(h);
996 hash_f32(spring.damping_ratio, h);
997 hash_f32(spring.stiffness, h);
998 hash_f32(spring.settle_progress, h);
999 hash_f32(spring.settle_velocity, h);
1000 } else {
1001 0u8.hash(h);
1002 }
1003 if let Some(repeat) = &spec.repeat {
1004 1u8.hash(h);
1005 repeat.iterations.hash(h);
1006 repeat.reverse.hash(h);
1007 repeat.delay_between.as_nanos().hash(h);
1008 } else {
1009 0u8.hash(h);
1010 }
1011 });
1012}
1013
1014pub fn hash_subtree(content_hash: u64, children_hashes: &[u64]) -> u64 {
1015 let mut hasher = RapidHasher::default();
1016 content_hash.hash(&mut hasher);
1017 children_hashes.len().hash(&mut hasher);
1018 for hash in children_hashes {
1019 hash.hash(&mut hasher);
1020 }
1021 hasher.finish()
1022}
1023
1024fn hash_opt_color(color: Option<&Color>, hasher: &mut impl Hasher) {
1025 match color {
1026 Some(color) => {
1027 1u8.hash(hasher);
1028 hash_color(color, hasher);
1029 }
1030 None => 0u8.hash(hasher),
1031 }
1032}
1033
1034fn hash_path_effect(path_effect: &Option<repose_core::PathEffect>, hasher: &mut impl Hasher) {
1035 match path_effect {
1036 Some(repose_core::PathEffect::Corner { radius }) => {
1037 0u8.hash(hasher);
1038 hash_f32(*radius, hasher);
1039 }
1040 Some(repose_core::PathEffect::Dash { intervals, phase }) => {
1041 1u8.hash(hasher);
1042 intervals.len().hash(hasher);
1043 for interval in intervals {
1044 hash_f32(*interval, hasher);
1045 }
1046 hash_f32(*phase, hasher);
1047 }
1048 None => 2u8.hash(hasher),
1049 }
1050}
1051
1052fn hash_draw_style(style: &repose_core::DrawStyle, hasher: &mut impl Hasher) {
1053 match style {
1054 repose_core::DrawStyle::Fill => 0u8.hash(hasher),
1055 repose_core::DrawStyle::Stroke {
1056 width,
1057 cap,
1058 join,
1059 miter,
1060 path_effect,
1061 }
1062 | repose_core::DrawStyle::FillAndStroke {
1063 width,
1064 cap,
1065 join,
1066 miter,
1067 path_effect,
1068 } => {
1069 std::mem::discriminant(style).hash(hasher);
1070 hash_f32(*width, hasher);
1071 (*cap as u8).hash(hasher);
1072 (*join as u8).hash(hasher);
1073 hash_f32(*miter, hasher);
1074 hash_path_effect(path_effect, hasher);
1075 }
1076 }
1077}
1078
1079fn hash_text_decoration(decoration: &repose_core::TextDecoration, hasher: &mut impl Hasher) {
1080 decoration.underline.hash(hasher);
1081 decoration.strikethrough.hash(hasher);
1082 hash_opt_color(decoration.color.as_ref(), hasher);
1083}
1084
1085fn hash_shadow(shadow: &Shadow, hasher: &mut impl Hasher) {
1086 hash_color(&shadow.color, hasher);
1087 hash_dp(shadow.offset_x, hasher);
1088 hash_dp(shadow.offset_y, hasher);
1089 hash_dp(shadow.blur_radius, hasher);
1090}
1091
1092#[allow(dead_code)]
1093fn hash_text_style(style: &TextStyle, hasher: &mut impl Hasher) {
1094 hash_sp(style.font_size, hasher);
1095 hash_opt_color(style.color.as_ref(), hasher);
1096 style.font_weight.hash(hasher);
1097 style.font_family.hash(hasher);
1098 style.font_style.hash(hasher);
1099 style.text_align.hash(hasher);
1100 hash_sp(style.letter_spacing, hasher);
1101 hash_sp(style.line_height, hasher);
1102 hash_opt_color(style.background.as_ref(), hasher);
1103 match &style.text_decoration {
1104 Some(decoration) => {
1105 1u8.hash(hasher);
1106 hash_text_decoration(decoration, hasher);
1107 }
1108 None => 0u8.hash(hasher),
1109 }
1110 match &style.shadow {
1111 Some(shadow) => {
1112 1u8.hash(hasher);
1113 hash_shadow(shadow, hasher);
1114 }
1115 None => 0u8.hash(hasher),
1116 }
1117 hash_discriminant_option(&style.text_direction, hasher);
1118 std::mem::discriminant(&style.font_synthesis).hash(hasher);
1119 hash_f32(style.baseline_shift.0, hasher);
1120 std::mem::discriminant(&style.hyphens).hash(hasher);
1121 std::mem::discriminant(&style.line_break).hash(hasher);
1122 match &style.text_indent {
1123 Some(indent) => {
1124 1u8.hash(hasher);
1125 hash_dp(indent.first_line, hasher);
1126 hash_dp(indent.rest_lines, hasher);
1127 }
1128 None => 0u8.hash(hasher),
1129 }
1130 hash_draw_style(&style.draw_style, hasher);
1131 hash_f32(style.alpha, hasher);
1132 style.locale_list.hash(hasher);
1133 style.font_feature_settings.hash(hasher);
1134 style.font_variation_settings.hash(hasher);
1135}
1136
1137#[allow(dead_code)]
1138fn hash_span_style(style: &SpanStyle, hasher: &mut impl Hasher) {
1139 hash_opt_color(style.color.as_ref(), hasher);
1140 match style.font_size {
1141 Some(size) => {
1142 1u8.hash(hasher);
1143 hash_sp(size, hasher);
1144 }
1145 None => 0u8.hash(hasher),
1146 }
1147 style.font_weight.hash(hasher);
1148 style.font_family.hash(hasher);
1149 style.font_style.hash(hasher);
1150 style.text_align.hash(hasher);
1151 match style.letter_spacing {
1152 Some(value) => {
1153 1u8.hash(hasher);
1154 hash_sp(value, hasher);
1155 }
1156 None => 0u8.hash(hasher),
1157 }
1158 match style.line_height {
1159 Some(value) => {
1160 1u8.hash(hasher);
1161 hash_sp(value, hasher);
1162 }
1163 None => 0u8.hash(hasher),
1164 }
1165 hash_opt_color(style.background.as_ref(), hasher);
1166 match &style.text_decoration {
1167 Some(decoration) => {
1168 1u8.hash(hasher);
1169 hash_text_decoration(decoration, hasher);
1170 }
1171 None => 0u8.hash(hasher),
1172 }
1173 hash_discriminant_option(&style.text_direction, hasher);
1174 hash_discriminant_option(&style.font_synthesis, hasher);
1175 match style.baseline_shift {
1176 Some(value) => {
1177 1u8.hash(hasher);
1178 hash_f32(value.0, hasher);
1179 }
1180 None => 0u8.hash(hasher),
1181 }
1182 hash_discriminant_option(&style.hyphens, hasher);
1183 hash_discriminant_option(&style.line_break, hasher);
1184 match &style.text_indent {
1185 Some(indent) => {
1186 1u8.hash(hasher);
1187 hash_dp(indent.first_line, hasher);
1188 hash_dp(indent.rest_lines, hasher);
1189 }
1190 None => 0u8.hash(hasher),
1191 }
1192 match &style.draw_style {
1193 Some(style) => {
1194 1u8.hash(hasher);
1195 hash_draw_style(style, hasher);
1196 }
1197 None => 0u8.hash(hasher),
1198 }
1199 hash_f32(style.alpha, hasher);
1200 style.font_variation_settings.hash(hasher);
1201}
1202
1203#[allow(dead_code)]
1204fn hash_view_kind(kind: &ViewKind, hasher: &mut impl Hasher) {
1205 std::mem::discriminant(kind).hash(hasher);
1206 match kind {
1207 ViewKind::Text {
1208 text,
1209 color,
1210 font_size,
1211 soft_wrap,
1212 max_lines,
1213 overflow,
1214 font_family,
1215 annotations,
1216 text_align,
1217 font_weight,
1218 font_style,
1219 text_decoration,
1220 letter_spacing,
1221 line_height,
1222 url,
1223 font_variation_settings,
1224 draw_style,
1225 } => {
1226 text.hash(hasher);
1227 hash_color(color, hasher);
1228 hash_sp(*font_size, hasher);
1229 soft_wrap.hash(hasher);
1230 max_lines.hash(hasher);
1231 hash_text_overflow(overflow, hasher);
1232 font_family.hash(hasher);
1233 text_align.hash(hasher);
1234 font_weight.0.hash(hasher);
1235 font_style.hash(hasher);
1236 hash_text_decoration(text_decoration, hasher);
1237 hash_sp(*letter_spacing, hasher);
1238 hash_sp(*line_height, hasher);
1239 url.hash(hasher);
1240 font_variation_settings.hash(hasher);
1241 hash_draw_style(draw_style, hasher);
1242 match annotations {
1243 Some(annotations) => {
1244 1u8.hash(hasher);
1245 annotations.len().hash(hasher);
1246 for span in annotations.iter() {
1247 span.start.hash(hasher);
1248 span.end.hash(hasher);
1249 hash_span_style(&span.style, hasher);
1250 match &span.url {
1251 Some(url) => {
1252 1u8.hash(hasher);
1253 url.hash(hasher);
1254 }
1255 None => 0u8.hash(hasher),
1256 }
1257 }
1258 }
1259 None => 0u8.hash(hasher),
1260 }
1261 }
1262 ViewKind::Image {
1263 handle,
1264 tint,
1265 fit,
1266 filter,
1267 source_rect,
1268 alignment,
1269 } => {
1270 handle.hash(hasher);
1271 repose_core::image_intrinsic_size(*handle).hash(hasher);
1272 hash_color(tint, hasher);
1273 std::mem::discriminant(fit).hash(hasher);
1274 std::mem::discriminant(filter).hash(hasher);
1275 source_rect.hash(hasher);
1276 std::mem::discriminant(alignment).hash(hasher);
1277 }
1278 ViewKind::OverlayHost
1279 | ViewKind::Box
1280 | ViewKind::Row
1281 | ViewKind::Column
1282 | ViewKind::ZStack
1283 | ViewKind::SubcomposeLayout { .. } => {}
1284 _ => {}
1285 }
1286}
1287
1288fn hash_discriminant_option<T>(value: &Option<T>, hasher: &mut impl Hasher) {
1289 match value {
1290 Some(value) => {
1291 1u8.hash(hasher);
1292 std::mem::discriminant(value).hash(hasher);
1293 }
1294 None => 0u8.hash(hasher),
1295 }
1296}
1297
1298fn hash_f32(v: f32, hasher: &mut impl Hasher) {
1299 v.to_bits().hash(hasher);
1300}
1301
1302fn hash_rc_identity<T: ?Sized>(value: &Option<std::rc::Rc<T>>, hasher: &mut impl Hasher) {
1303 value.is_some().hash(hasher);
1304}
1305
1306fn hash_rc_ptr_identity<T: ?Sized>(value: &std::rc::Rc<T>, hasher: &mut impl Hasher) {
1307 (std::rc::Rc::as_ptr(value) as *const () as usize).hash(hasher);
1308}
1309
1310#[allow(dead_code)]
1311fn hash_control_visual(visual: &repose_core::ControlVisual, hasher: &mut impl Hasher) {
1312 std::mem::discriminant(visual).hash(hasher);
1313 match visual {
1314 repose_core::ControlVisual::Rect { brush, radius } => {
1315 hash_brush(brush, hasher);
1316 for value in radius {
1317 value.0.to_bits().hash(hasher);
1318 }
1319 }
1320 repose_core::ControlVisual::Image {
1321 handle,
1322 source_rect,
1323 tint,
1324 fit,
1325 filter,
1326 alignment,
1327 } => {
1328 handle.hash(hasher);
1329 source_rect.hash(hasher);
1330 hash_color(tint, hasher);
1331 std::mem::discriminant(fit).hash(hasher);
1332 std::mem::discriminant(filter).hash(hasher);
1333 std::mem::discriminant(alignment).hash(hasher);
1334 }
1335 repose_core::ControlVisual::Custom(_) => {
1336 visual.custom_identity().unwrap_or_default().hash(hasher)
1337 }
1338 _ => {}
1339 }
1340}
1341
1342#[allow(dead_code)]
1343fn hash_control_visual_set(set: &repose_core::ControlVisualSet, hasher: &mut impl Hasher) {
1344 for visual in [
1345 &set.normal,
1346 &set.hovered,
1347 &set.pressed,
1348 &set.dragged,
1349 &set.focused,
1350 &set.disabled,
1351 ] {
1352 match visual {
1353 Some(visual) => {
1354 true.hash(hasher);
1355 hash_control_visual(visual, hasher);
1356 }
1357 None => false.hash(hasher),
1358 }
1359 }
1360}
1361
1362#[allow(dead_code)]
1363fn hash_scrollbar_style(style: Option<&repose_core::ScrollbarStyle>, hasher: &mut impl Hasher) {
1364 let Some(style) = style else {
1365 0u8.hash(hasher);
1366 return;
1367 };
1368 1u8.hash(hasher);
1369 style.thickness.0.to_bits().hash(hasher);
1370 style.track_inset.0.to_bits().hash(hasher);
1371 style.min_thumb_length.0.to_bits().hash(hasher);
1372 style
1373 .fixed_thumb_length
1374 .map(|value| value.0.to_bits())
1375 .hash(hasher);
1376 style.radius.map(|value| value.0.to_bits()).hash(hasher);
1377 hash_control_visual_set(&style.track_visuals, hasher);
1378 hash_control_visual_set(&style.thumb_visuals, hasher);
1379}
1380
1381#[allow(dead_code)]
1382fn hash_scroll_axis_binding(
1383 binding: &repose_core::scroll::ScrollAxisBinding,
1384 hasher: &mut impl Hasher,
1385) {
1386 binding.show_scrollbar.hash(hasher);
1387 hash_rc_identity(&binding.on_scroll, hasher);
1388 hash_rc_identity(&binding.set_viewport_main, hasher);
1389 hash_rc_identity(&binding.set_content_main, hasher);
1390 hash_rc_identity(&binding.get_offset_main, hasher);
1391 hash_rc_identity(&binding.set_offset_main, hasher);
1392 hash_rc_identity(&binding.tick, hasher);
1393 hash_rc_identity(&binding.set_nested_scroll_parent, hasher);
1394}
1395
1396#[allow(dead_code)]
1397fn hash_scroll_binding(binding: &ScrollBinding, hasher: &mut impl Hasher) {
1398 match binding {
1399 ScrollBinding::Vertical(binding) => {
1400 1u8.hash(hasher);
1401 hash_scroll_axis_binding(binding, hasher);
1402 }
1403 ScrollBinding::Horizontal(binding) => {
1404 2u8.hash(hasher);
1405 hash_scroll_axis_binding(binding, hasher);
1406 }
1407 ScrollBinding::Both(binding) => {
1408 3u8.hash(hasher);
1409 binding.show_scrollbar.hash(hasher);
1410 hash_rc_identity(&binding.on_scroll, hasher);
1411 hash_rc_identity(&binding.set_viewport_width, hasher);
1412 hash_rc_identity(&binding.set_viewport_height, hasher);
1413 hash_rc_identity(&binding.set_content_width, hasher);
1414 hash_rc_identity(&binding.set_content_height, hasher);
1415 hash_rc_identity(&binding.get_offset_xy, hasher);
1416 hash_rc_identity(&binding.set_offset_xy, hasher);
1417 hash_rc_identity(&binding.tick, hasher);
1418 hash_rc_identity(&binding.set_nested_scroll_parent, hasher);
1419 }
1420 }
1421}
1422
1423#[allow(dead_code)]
1424fn hash_cursor(cursor: &Option<repose_core::CursorIcon>, hasher: &mut impl Hasher) {
1425 match cursor {
1426 Some(repose_core::CursorIcon::Custom(image)) => {
1427 10u8.hash(hasher);
1428 image.size.hash(hasher);
1429 image.hotspot.hash(hasher);
1430 image.rgba.hash(hasher);
1431 }
1432 Some(cursor) => {
1433 1u8.hash(hasher);
1434 std::mem::discriminant(cursor).hash(hasher);
1435 }
1436 None => 0u8.hash(hasher),
1437 }
1438}
1439
1440fn hash_opt_f32(v: Option<f32>, hasher: &mut impl Hasher) {
1441 match v {
1442 Some(value) => {
1443 1u8.hash(hasher);
1444 hash_f32(value, hasher);
1445 }
1446 None => 0u8.hash(hasher),
1447 }
1448}
1449
1450fn hash_dp(v: Dp, hasher: &mut impl Hasher) {
1451 hash_f32(v.0, hasher);
1452}
1453
1454fn hash_opt_dp(v: Option<Dp>, hasher: &mut impl Hasher) {
1455 match v {
1456 Some(value) => {
1457 1u8.hash(hasher);
1458 hash_dp(value, hasher);
1459 }
1460 None => 0u8.hash(hasher),
1461 }
1462}
1463
1464fn hash_sp(v: Sp, hasher: &mut impl Hasher) {
1465 hash_f32(v.0, hasher);
1466}
1467
1468#[allow(dead_code)]
1469fn hash_px(v: Px, hasher: &mut impl Hasher) {
1470 hash_f32(v.0, hasher);
1471}
1472
1473#[allow(dead_code)]
1474fn hash_modifier(m: &Modifier, hasher: &mut impl Hasher) {
1475 match m.size {
1476 Some(size) => {
1477 1u8.hash(hasher);
1478 hash_dp(size.width, hasher);
1479 hash_dp(size.height, hasher);
1480 }
1481 None => 0u8.hash(hasher),
1482 }
1483 hash_opt_dp(m.width, hasher);
1484 hash_opt_dp(m.height, hasher);
1485 match m.required_size {
1486 Some(size) => {
1487 1u8.hash(hasher);
1488 hash_dp(size.width, hasher);
1489 hash_dp(size.height, hasher);
1490 }
1491 None => 0u8.hash(hasher),
1492 }
1493 hash_opt_dp(m.required_min_width, hasher);
1494 hash_opt_dp(m.required_max_width, hasher);
1495 hash_opt_dp(m.required_min_height, hasher);
1496 hash_opt_dp(m.required_max_height, hasher);
1497 hash_opt_dp(m.default_min_width, hasher);
1498 hash_opt_dp(m.default_min_height, hasher);
1499 hash_opt_f32(m.fill_max, hasher);
1500 hash_opt_f32(m.fill_max_w, hasher);
1501 hash_opt_f32(m.fill_max_h, hasher);
1502 m.repaint_boundary.hash(hasher);
1503 hash_opt_dp(m.padding, hasher);
1504 match m.padding_values {
1505 Some(padding) => {
1506 1u8.hash(hasher);
1507 hash_dp(padding.left, hasher);
1508 hash_dp(padding.right, hasher);
1509 hash_dp(padding.top, hasher);
1510 hash_dp(padding.bottom, hasher);
1511 }
1512 None => 0u8.hash(hasher),
1513 }
1514 hash_opt_dp(m.min_width, hasher);
1515 hash_opt_dp(m.min_height, hasher);
1516 hash_opt_dp(m.max_width, hasher);
1517 hash_opt_dp(m.max_height, hasher);
1518 match &m.background {
1519 Some(background) => {
1520 1u8.hash(hasher);
1521 hash_brush(background, hasher);
1522 }
1523 None => 0u8.hash(hasher),
1524 }
1525 match &m.border {
1526 Some(border) => {
1527 1u8.hash(hasher);
1528 hash_dp(border.width, hasher);
1529 hash_brush(&border.brush, hasher);
1530 for &radius in &border.radius {
1531 hash_dp(radius, hasher);
1532 }
1533 }
1534 None => 0u8.hash(hasher),
1535 }
1536 hash_opt_f32(m.flex_grow, hasher);
1537 hash_opt_f32(m.flex_shrink, hasher);
1538 hash_opt_dp(m.flex_basis, hasher);
1539 m.flex_wrap
1540 .map(|value| std::mem::discriminant(&value))
1541 .hash(hasher);
1542 m.flex_basis_content.hash(hasher);
1543 m.flex_line_count.hash(hasher);
1544 m.flex_dir
1545 .map(|value| std::mem::discriminant(&value))
1546 .hash(hasher);
1547 m.align_self
1548 .map(|value| (value.keyword as u8, value.safety as u8))
1549 .hash(hasher);
1550 m.justify_content
1551 .map(|value| (value.keyword as u8, value.safety as u8))
1552 .hash(hasher);
1553 m.align_items_container
1554 .map(|value| (value.keyword as u8, value.safety as u8))
1555 .hash(hasher);
1556 m.align_content
1557 .map(|value| (value.keyword as u8, value.safety as u8))
1558 .hash(hasher);
1559 m.baseline_align.hash(hasher);
1560 match m.clip_rounded {
1561 Some(radius) => {
1562 1u8.hash(hasher);
1563 for value in radius {
1564 hash_dp(value, hasher);
1565 }
1566 }
1567 None => 0u8.hash(hasher),
1568 }
1569 match m.transform {
1570 Some(transform) => {
1571 1u8.hash(hasher);
1572 hash_f32(transform.translate_x, hasher);
1573 hash_f32(transform.translate_y, hasher);
1574 hash_f32(transform.scale_x, hasher);
1575 hash_f32(transform.scale_y, hasher);
1576 hash_f32(transform.rotate, hasher);
1577 hash_f32(transform.shear_x, hasher);
1578 hash_f32(transform.shear_y, hasher);
1579 hash_f32(transform.origin_x, hasher);
1580 hash_f32(transform.origin_y, hasher);
1581 for value in transform.perspective {
1582 hash_f32(value, hasher);
1583 }
1584 }
1585 None => 0u8.hash(hasher),
1586 }
1587 hash_opt_f32(m.alpha, hasher);
1588 m.position_type
1589 .map(|value| std::mem::discriminant(&value))
1590 .hash(hasher);
1591 hash_opt_dp(m.offset_left, hasher);
1592 hash_opt_dp(m.offset_right, hasher);
1593 hash_opt_dp(m.offset_top, hasher);
1594 hash_opt_dp(m.offset_bottom, hasher);
1595 match &m.grid {
1596 Some(grid) => {
1597 1u8.hash(hasher);
1598 grid.columns.hash(hasher);
1599 hash_dp(grid.row_gap, hasher);
1600 hash_dp(grid.column_gap, hasher);
1601 }
1602 None => 0u8.hash(hasher),
1603 }
1604 m.grid_col_span.hash(hasher);
1605 m.grid_row_span.hash(hasher);
1606 hash_opt_f32(m.aspect_ratio, hasher);
1607 m.intrinsic_width.hash(hasher);
1608 m.intrinsic_height.hash(hasher);
1609 hash_opt_dp(m.fit_content_width, hasher);
1610 hash_opt_dp(m.fit_content_height, hasher);
1611 match m.contain {
1612 None => 0u8.hash(hasher),
1613 Some(value) if value == taffy::Contain::CONTENT => 3u8.hash(hasher),
1614 Some(value) if value == taffy::Contain::LAYOUT => 1u8.hash(hasher),
1615 Some(value) if value == taffy::Contain::PAINT => 2u8.hash(hasher),
1616 Some(_) => 4u8.hash(hasher),
1617 }
1618 hash_f32(m.z_index, hasher);
1619 hash_opt_f32(m.render_z_index, hasher);
1620 m.input_blocker.hash(hasher);
1621 m.click.hash(hasher);
1622 m.disabled.hash(hasher);
1623 m.focusable.hash(hasher);
1624 m.hit_passthrough.hash(hasher);
1625 m.propagate_min.hash(hasher);
1626 m.focus_group.hash(hasher);
1627 match &m.blur {
1628 Some(blur) => {
1629 1u8.hash(hasher);
1630 hash_dp(blur.radius_x, hasher);
1631 hash_dp(blur.radius_y, hasher);
1632 std::mem::discriminant(&blur.edge_treatment).hash(hasher);
1633 }
1634 None => 0u8.hash(hasher),
1635 }
1636 match &m.scroll {
1637 Some(binding) => hash_scroll_binding(binding, hasher),
1638 None => 0u8.hash(hasher),
1639 }
1640 hash_scrollbar_style(m.scrollbar_style.as_ref(), hasher);
1641 m.overflow
1642 .map(|value| std::mem::discriminant(&value))
1643 .hash(hasher);
1644 match m.clip_rect {
1645 Some(rect) => {
1646 1u8.hash(hasher);
1647 hash_dp(rect.left, hasher);
1648 hash_dp(rect.top, hasher);
1649 hash_dp(rect.right, hasher);
1650 hash_dp(rect.bottom, hasher);
1651 std::mem::discriminant(&rect.op).hash(hasher);
1652 }
1653 None => 0u8.hash(hasher),
1654 }
1655 hash_opt_dp(m.gap, hasher);
1656 hash_opt_dp(m.row_gap, hasher);
1657 hash_opt_dp(m.column_gap, hasher);
1658 hash_opt_dp(m.margin_top, hasher);
1659 hash_opt_dp(m.margin_left, hasher);
1660 hash_opt_dp(m.margin_right, hasher);
1661 hash_opt_dp(m.margin_bottom, hasher);
1662 hash_opt_f32(m.graphics_layer, hasher);
1663 match &m.shadow {
1664 Some(shadow) => {
1665 1u8.hash(hasher);
1666 hash_dp(shadow.blur_radius, hasher);
1667 hash_dp(shadow.offset_y, hasher);
1668 hash_color(&shadow.color, hasher);
1669 }
1670 None => 0u8.hash(hasher),
1671 }
1672 match &m.semantics {
1673 Some(semantics) => {
1674 1u8.hash(hasher);
1675 std::mem::discriminant(&semantics.role).hash(hasher);
1676 semantics.label.hash(hasher);
1677 semantics.focused.hash(hasher);
1678 semantics.enabled.hash(hasher);
1679 semantics.selectable_group.hash(hasher);
1680 semantics.checked.hash(hasher);
1681 semantics.selected.hash(hasher);
1682 semantics.value.hash(hasher);
1683 }
1684 None => 0u8.hash(hasher),
1685 }
1686 hash_cursor(&m.cursor, hasher);
1687 match &m.text_input {
1688 Some(input) => {
1689 1u8.hash(hasher);
1690 input.hint.hash(hasher);
1691 input.multiline.hash(hasher);
1692 input.value.hash(hasher);
1693 input.enabled.hash(hasher);
1694 input.read_only.hash(hasher);
1695 input.sensitive.hash(hasher);
1696 input.max_lines.hash(hasher);
1697 input.min_lines.hash(hasher);
1698 std::mem::discriminant(&input.keyboard_type).hash(hasher);
1699 std::mem::discriminant(&input.capitalization).hash(hasher);
1700 std::mem::discriminant(&input.ime_action).hash(hasher);
1701 input.auto_correct_enabled.hash(hasher);
1702 match input.cursor_color {
1703 Some(color) => {
1704 1u8.hash(hasher);
1705 hash_color(&color, hasher);
1706 }
1707 None => 0u8.hash(hasher),
1708 }
1709 match &input.text_style {
1710 Some(style) => {
1711 1u8.hash(hasher);
1712 hash_text_style(style, hasher);
1713 }
1714 None => 0u8.hash(hasher),
1715 }
1716 match &input.line_limits {
1717 Some(repose_core::text::TextFieldLineLimits::SingleLine) => 1u8.hash(hasher),
1718 Some(repose_core::text::TextFieldLineLimits::MultiLine {
1719 min_height_in_lines,
1720 max_height_in_lines,
1721 }) => {
1722 2u8.hash(hasher);
1723 min_height_in_lines.hash(hasher);
1724 max_height_in_lines.hash(hasher);
1725 }
1726 None => 0u8.hash(hasher),
1727 }
1728 }
1729 None => 0u8.hash(hasher),
1730 }
1731 m.text_selection.is_some().hash(hasher);
1732 match &m.state_colors {
1733 Some(colors) => {
1734 1u8.hash(hasher);
1735 hash_color(&colors.default, hasher);
1736 hash_color(&colors.hovered, hasher);
1737 hash_color(&colors.focused, hasher);
1738 hash_color(&colors.pressed, hasher);
1739 hash_color(&colors.disabled, hasher);
1740 hash_color(&colors.dragged, hasher);
1741 }
1742 None => 0u8.hash(hasher),
1743 }
1744 match &m.state_elevation {
1745 Some(elevation) => {
1746 1u8.hash(hasher);
1747 hash_dp(elevation.default, hasher);
1748 hash_dp(elevation.hovered, hasher);
1749 hash_dp(elevation.focused, hasher);
1750 hash_dp(elevation.pressed, hasher);
1751 hash_dp(elevation.disabled, hasher);
1752 hash_dp(elevation.dragged, hasher);
1753 }
1754 None => 0u8.hash(hasher),
1755 }
1756 match &m.animate_content_size {
1757 Some(spec) => {
1758 1u8.hash(hasher);
1759 hash_animation_spec(spec, hasher);
1760 }
1761 None => 0u8.hash(hasher),
1762 }
1763}
1764
1765#[allow(dead_code)]
1766fn hash_animation_spec(spec: &AnimationSpec, hasher: &mut impl Hasher) {
1767 spec.duration.as_nanos().hash(hasher);
1768 hash_easing(&spec.easing, hasher);
1769 spec.delay.as_nanos().hash(hasher);
1770 match &spec.spring {
1771 Some(spring) => {
1772 1u8.hash(hasher);
1773 hash_f32(spring.damping_ratio, hasher);
1774 hash_f32(spring.stiffness, hasher);
1775 hash_f32(spring.settle_progress, hasher);
1776 hash_f32(spring.settle_velocity, hasher);
1777 }
1778 None => 0u8.hash(hasher),
1779 }
1780 match &spec.repeat {
1781 Some(repeat) => {
1782 1u8.hash(hasher);
1783 repeat.iterations.hash(hasher);
1784 repeat.reverse.hash(hasher);
1785 repeat.delay_between.as_nanos().hash(hasher);
1786 }
1787 None => 0u8.hash(hasher),
1788 }
1789}
1790
1791fn hash_easing(easing: &Easing, hasher: &mut impl Hasher) {
1792 std::mem::discriminant(easing).hash(hasher);
1793 match easing {
1794 Easing::SpringCrit { omega } => hash_f32(*omega, hasher),
1795 Easing::Custom(cb) => {
1796 hash_f32(cb.p1x, hasher);
1797 hash_f32(cb.p1y, hasher);
1798 hash_f32(cb.p2x, hasher);
1799 hash_f32(cb.p2y, hasher);
1800 }
1801 _ => {}
1802 }
1803}
1804
1805fn hash_color(c: &Color, hasher: &mut impl Hasher) {
1806 c.0.hash(hasher);
1807 c.1.hash(hasher);
1808 c.2.hash(hasher);
1809 c.3.hash(hasher);
1810}
1811
1812fn hash_brush(b: &Brush, hasher: &mut impl Hasher) {
1813 std::mem::discriminant(b).hash(hasher);
1814 match b {
1815 Brush::Solid(c) => hash_color(c, hasher),
1816 Brush::Linear {
1817 start,
1818 end,
1819 start_color,
1820 end_color,
1821 } => {
1822 hash_f32(start.x, hasher);
1823 hash_f32(start.y, hasher);
1824 hash_f32(end.x, hasher);
1825 hash_f32(end.y, hasher);
1826 hash_color(start_color, hasher);
1827 hash_color(end_color, hasher);
1828 }
1829 Brush::LinearNormalized {
1830 start,
1831 end,
1832 start_color,
1833 end_color,
1834 } => {
1835 hash_f32(start.x, hasher);
1836 hash_f32(start.y, hasher);
1837 hash_f32(end.x, hasher);
1838 hash_f32(end.y, hasher);
1839 hash_color(start_color, hasher);
1840 hash_color(end_color, hasher);
1841 }
1842 Brush::Radial {
1843 center,
1844 radius,
1845 start_color,
1846 end_color,
1847 } => {
1848 hash_f32(center.x, hasher);
1849 hash_f32(center.y, hasher);
1850 hash_f32(*radius, hasher);
1851 hash_color(start_color, hasher);
1852 hash_color(end_color, hasher);
1853 }
1854 Brush::Sweep {
1855 center,
1856 start_color,
1857 end_color,
1858 } => {
1859 hash_f32(center.x, hasher);
1860 hash_f32(center.y, hasher);
1861 hash_color(start_color, hasher);
1862 hash_color(end_color, hasher);
1863 }
1864 _ => {}
1865 }
1866}
1867
1868fn hash_text_overflow(o: &TextOverflow, hasher: &mut impl Hasher) {
1869 std::mem::discriminant(o).hash(hasher);
1870}
1871
1872#[cfg(test)]
1873mod tests {
1874 use super::*;
1875 use repose_core::{
1876 DrawStyle, FontStyle, FontWeight, ImageAlignment, ImageFilter, ImageFit, ImageSourceRect,
1877 Modifier, TextAlign, TextDecoration, UnitExt, View, ViewKind,
1878 };
1879
1880 #[test]
1881 fn test_same_view_same_hash() {
1882 let v1 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(100.0.dp()));
1883 let v2 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(100.0.dp()));
1884
1885 assert_eq!(hash_view_content(&v1), hash_view_content(&v2));
1886 }
1887
1888 #[test]
1889 fn image_source_rect_changes_hash() {
1890 let image = |source_rect| {
1891 View::new(
1892 0,
1893 ViewKind::Image {
1894 handle: 1,
1895 tint: Color::WHITE,
1896 fit: ImageFit::FillBounds,
1897 filter: ImageFilter::Nearest,
1898 source_rect,
1899 alignment: ImageAlignment::Center,
1900 },
1901 )
1902 };
1903 let full = image(None);
1904 let first = image(Some(ImageSourceRect::new(0, 0, 8, 8)));
1905 let second = image(Some(ImageSourceRect::new(8, 0, 8, 8)));
1906 assert_ne!(hash_view_content(&full), hash_view_content(&first));
1907 assert_ne!(hash_view_content(&first), hash_view_content(&second));
1908 }
1909
1910 #[test]
1911 fn test_scroll_presence_changes_hash() {
1912 use repose_core::scroll::ScrollAxisBinding;
1913
1914 let v1 = View::new(0, ViewKind::Box).modifier(Modifier::new());
1915 let v2 = View::new(0, ViewKind::Box).modifier(Modifier::new().vertical_scroll(
1916 ScrollAxisBinding {
1917 show_scrollbar: true,
1918 ..Default::default()
1919 },
1920 ));
1921
1922 assert_ne!(hash_view_content(&v1), hash_view_content(&v2));
1923 }
1924
1925 #[test]
1926 fn test_different_view_different_hash() {
1927 let v1 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(100.0.dp()));
1928 let v2 = View::new(0, ViewKind::Box).modifier(Modifier::new().width(200.0.dp()));
1929
1930 assert_ne!(hash_view_content(&v1), hash_view_content(&v2));
1931 }
1932
1933 #[test]
1934 fn test_text_content_hash() {
1935 let v1 = View::new(
1936 0,
1937 ViewKind::Text {
1938 text: "Hello".to_string(),
1939 color: Color::WHITE,
1940 font_size: 16.0.sp(),
1941 soft_wrap: true,
1942 max_lines: None,
1943 overflow: TextOverflow::Visible,
1944 font_family: None,
1945 annotations: None,
1946 text_align: TextAlign::Unspecified,
1947 font_weight: FontWeight::NORMAL,
1948 font_style: FontStyle::Normal,
1949 text_decoration: TextDecoration::default(),
1950 letter_spacing: Sp::ZERO,
1951 line_height: Sp::ZERO,
1952 url: None,
1953 font_variation_settings: None,
1954 draw_style: DrawStyle::Fill,
1955 },
1956 );
1957 let v2 = View::new(
1958 0,
1959 ViewKind::Text {
1960 text: "Hello".to_string(),
1961 color: Color::WHITE,
1962 font_size: 16.0.sp(),
1963 soft_wrap: true,
1964 max_lines: None,
1965 overflow: TextOverflow::Visible,
1966 font_family: None,
1967 annotations: None,
1968 text_align: TextAlign::Unspecified,
1969 font_weight: FontWeight::NORMAL,
1970 font_style: FontStyle::Normal,
1971 text_decoration: TextDecoration::default(),
1972 letter_spacing: Sp::ZERO,
1973 line_height: Sp::ZERO,
1974 url: None,
1975 font_variation_settings: None,
1976 draw_style: DrawStyle::Fill,
1977 },
1978 );
1979 let v3 = View::new(
1980 0,
1981 ViewKind::Text {
1982 text: "World".to_string(),
1983 color: Color::WHITE,
1984 font_size: 16.0.sp(),
1985 soft_wrap: true,
1986 max_lines: None,
1987 overflow: TextOverflow::Visible,
1988 font_family: None,
1989 annotations: None,
1990 text_align: TextAlign::Unspecified,
1991 font_weight: FontWeight::NORMAL,
1992 font_style: FontStyle::Normal,
1993 text_decoration: TextDecoration::default(),
1994 letter_spacing: Sp::ZERO,
1995 line_height: Sp::ZERO,
1996 url: None,
1997 font_variation_settings: None,
1998 draw_style: DrawStyle::Fill,
1999 },
2000 );
2001
2002 assert_eq!(hash_view_content(&v1), hash_view_content(&v2));
2003 assert_ne!(hash_view_content(&v1), hash_view_content(&v3));
2004 }
2005
2006 fn text_view_with_url(url: Option<&str>) -> View {
2007 View::new(
2008 0,
2009 ViewKind::Text {
2010 text: "link".to_string(),
2011 color: Color::WHITE,
2012 font_size: 16.0.sp(),
2013 soft_wrap: true,
2014 max_lines: None,
2015 overflow: TextOverflow::Visible,
2016 font_family: None,
2017 annotations: None,
2018 text_align: TextAlign::Unspecified,
2019 font_weight: FontWeight::NORMAL,
2020 font_style: FontStyle::Normal,
2021 text_decoration: TextDecoration::default(),
2022 letter_spacing: Sp::ZERO,
2023 line_height: Sp::ZERO,
2024 url: url.map(|u| std::sync::Arc::from(u)),
2025 font_variation_settings: None,
2026 draw_style: DrawStyle::Fill,
2027 },
2028 )
2029 }
2030
2031 #[test]
2032 fn view_id_and_span_baseline_change_hash() {
2033 let first = View::new(1, ViewKind::Box);
2034 let second = View::new(2, ViewKind::Box);
2035 assert_ne!(hash_view_content(&first), hash_view_content(&second));
2036
2037 let mut a = text_view_with_url(None);
2038 let mut b = a.clone();
2039 let span = repose_core::TextSpan {
2040 start: 0,
2041 end: 4,
2042 style: repose_core::SpanStyle::default()
2043 .baseline_shift(repose_core::BaselineShift::Superscript),
2044 url: None,
2045 };
2046 if let ViewKind::Text { annotations, .. } = &mut a.kind {
2047 *annotations = Some(std::sync::Arc::from([span.clone()]));
2048 }
2049 if let ViewKind::Text { annotations, .. } = &mut b.kind {
2050 *annotations = Some(std::sync::Arc::from([repose_core::TextSpan {
2051 style: repose_core::SpanStyle::default()
2052 .baseline_shift(repose_core::BaselineShift::Subscript),
2053 ..span
2054 }]));
2055 }
2056 assert_ne!(hash_view_content(&a), hash_view_content(&b));
2057 }
2058
2059 #[test]
2060 fn test_text_url_change_invalidates() {
2061 let a = text_view_with_url(None);
2062 let b = text_view_with_url(Some("https://a.example"));
2063 let c = text_view_with_url(Some("https://b.example"));
2064 assert_ne!(hash_view_content(&a), hash_view_content(&b));
2065 assert_ne!(hash_view_content(&b), hash_view_content(&c));
2066 }
2067
2068 #[test]
2069 fn test_semantics_state_invalidates() {
2070 let mk = |checked: Option<bool>| {
2071 View::new(0, ViewKind::Box).semantics(
2072 repose_core::Semantics::new(repose_core::Role::Checkbox)
2073 .with_checked(checked.unwrap_or(false)),
2074 )
2075 };
2076 assert_ne!(
2077 hash_view_content(&mk(Some(true))),
2078 hash_view_content(&mk(Some(false)))
2079 );
2080 let m1 = View::new(0, ViewKind::Box).modifier(
2081 Modifier::new().semantics(repose_core::Semantics::new(repose_core::Role::Button)),
2082 );
2083 let m2 = View::new(0, ViewKind::Box).modifier(
2084 Modifier::new()
2085 .semantics(repose_core::Semantics::new(repose_core::Role::Button).with_value("v")),
2086 );
2087 assert_ne!(hash_view_content(&m1), hash_view_content(&m2));
2088 }
2089
2090 #[test]
2091 fn test_then_preserves_layer_fields() {
2092 let base = Modifier::new().width(10.0.dp());
2093 let overlay = Modifier::new()
2094 .blur(4.0.dp())
2095 .shadow_with_color(8.0.dp(), 2.0.dp(), Color::BLACK)
2096 .graphics_layer(0.5);
2097 let merged = base.then(overlay);
2098 assert!(merged.blur.is_some());
2099 assert!(merged.shadow.is_some());
2100 assert!(merged.graphics_layer.is_some());
2101 assert_eq!(
2102 Modifier::new()
2103 .z_index(5.0)
2104 .then(Modifier::new().z_index(0.0))
2105 .z_index,
2106 0.0
2107 );
2108 }
2109}