1use super::semantics::{ActionEntry, Semantics};
2pub use crate::viewport::{
3 ViewportBoundary, ViewportClip, ViewportMargin, ViewportPanAxis, ViewportTransform,
4 ViewportZoomPolicy,
5};
6use crate::WidgetId;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum Op {
13 Structural(StructuralOp),
14 Layout(LayoutOp),
15 Paint(PaintOp),
16 Semantics(Semantics),
17}
18
19impl std::hash::Hash for Op {
20 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
21 match self {
22 Self::Structural(s) => {
23 0.hash(state);
24 s.hash(state);
25 }
26 Self::Layout(l) => {
27 1.hash(state);
28 l.hash(state);
29 }
30 Self::Paint(p) => {
31 2.hash(state);
32 p.hash(state);
33 }
34 Self::Semantics(s) => {
35 3.hash(state);
36 s.hash(state);
37 }
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Hash)]
43pub enum StructuralOp {
44 Group { stable_hash: u64 },
45}
46
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
48pub struct CompositeScalar {
49 pub base: f32,
50 pub motion_target: Option<WidgetId>,
51}
52
53impl CompositeScalar {
54 pub fn new(base: f32) -> Self {
55 Self {
56 base,
57 motion_target: None,
58 }
59 }
60
61 pub fn motion(mut self, target: WidgetId) -> Self {
62 self.motion_target = Some(target);
63 self
64 }
65}
66
67impl std::hash::Hash for CompositeScalar {
68 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
69 self.base.to_bits().hash(state);
70 self.motion_target.hash(state);
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Hash, Default)]
75pub struct CompositeStyle {
76 pub opacity: Option<CompositeScalar>,
77 pub translate_x: Option<CompositeScalar>,
78 pub translate_y: Option<CompositeScalar>,
79 pub scale: Option<CompositeScalar>,
80 pub rotation: Option<CompositeScalar>,
81 pub clip_to_bounds: bool,
82 pub repaint_boundary: bool,
83}
84
85pub type LayoutUnit = f32;
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89pub enum Length {
90 Points(LayoutUnit),
92 Percent(f32),
94 ViewportWidth(f32),
96 ViewportHeight(f32),
98 Add(Box<Length>, Box<Length>),
100 Subtract(Box<Length>, Box<Length>),
102 Min(Vec<Length>),
104 Max(Vec<Length>),
106 Clamp {
108 min: Box<Length>,
110 preferred: Box<Length>,
112 max: Box<Length>,
114 },
115 FitContent(Option<Box<Length>>),
117 MinContent,
119 MaxContent,
121 Auto,
123}
124
125impl Length {
126 pub fn points(value: LayoutUnit) -> Self {
128 Self::Points(value)
129 }
130
131 pub fn percent(value: f32) -> Self {
133 Self::Percent(value)
134 }
135
136 pub fn vw(value: f32) -> Self {
138 Self::ViewportWidth(value)
139 }
140
141 pub fn vh(value: f32) -> Self {
143 Self::ViewportHeight(value)
144 }
145
146 pub fn clamp(min: Length, preferred: Length, max: Length) -> Self {
148 Self::Clamp {
149 min: Box::new(min),
150 preferred: Box::new(preferred),
151 max: Box::new(max),
152 }
153 }
154
155 pub fn min(values: impl Into<Vec<Length>>) -> Self {
157 Self::Min(values.into())
158 }
159
160 pub fn max(values: impl Into<Vec<Length>>) -> Self {
162 Self::Max(values.into())
163 }
164
165 pub fn fit_content(limit: impl Into<Option<Length>>) -> Self {
167 Self::FitContent(limit.into().map(Box::new))
168 }
169
170 pub fn all(value: Length) -> [Length; 4] {
172 std::array::from_fn(|_| value.clone())
173 }
174
175 pub fn symmetric(horizontal: Length, vertical: Length) -> [Length; 4] {
177 [horizontal.clone(), horizontal, vertical.clone(), vertical]
178 }
179
180 pub fn resolve(
185 &self,
186 reference: LayoutUnit,
187 viewport_width: LayoutUnit,
188 viewport_height: LayoutUnit,
189 ) -> Option<LayoutUnit> {
190 let resolved = match self {
191 Self::Points(value) => *value,
192 Self::Percent(value) => reference.is_finite().then_some(reference * value / 100.0)?,
193 Self::ViewportWidth(value) => viewport_width * value / 100.0,
194 Self::ViewportHeight(value) => viewport_height * value / 100.0,
195 Self::Add(left, right) => {
196 left.resolve(reference, viewport_width, viewport_height)?
197 + right.resolve(reference, viewport_width, viewport_height)?
198 }
199 Self::Subtract(left, right) => {
200 left.resolve(reference, viewport_width, viewport_height)?
201 - right.resolve(reference, viewport_width, viewport_height)?
202 }
203 Self::Min(values) => resolve_length_list(
204 values,
205 reference,
206 viewport_width,
207 viewport_height,
208 LayoutUnit::min,
209 )?,
210 Self::Max(values) => resolve_length_list(
211 values,
212 reference,
213 viewport_width,
214 viewport_height,
215 LayoutUnit::max,
216 )?,
217 Self::Clamp {
218 min,
219 preferred,
220 max,
221 } => {
222 let minimum = min.resolve(reference, viewport_width, viewport_height)?;
223 let maximum = max.resolve(reference, viewport_width, viewport_height)?;
224 preferred
225 .resolve(reference, viewport_width, viewport_height)?
226 .clamp(minimum.min(maximum), minimum.max(maximum))
227 }
228 Self::FitContent(_) | Self::MinContent | Self::MaxContent | Self::Auto => return None,
229 };
230 resolved.is_finite().then_some(resolved)
231 }
232}
233
234fn resolve_length_list(
235 values: &[Length],
236 reference: LayoutUnit,
237 viewport_width: LayoutUnit,
238 viewport_height: LayoutUnit,
239 combine: impl Fn(LayoutUnit, LayoutUnit) -> LayoutUnit,
240) -> Option<LayoutUnit> {
241 let mut values = values.iter();
242 let mut resolved = values
243 .next()?
244 .resolve(reference, viewport_width, viewport_height)?;
245 for value in values {
246 resolved = combine(
247 resolved,
248 value.resolve(reference, viewport_width, viewport_height)?,
249 );
250 }
251 Some(resolved)
252}
253
254impl From<LayoutUnit> for Length {
255 fn from(value: LayoutUnit) -> Self {
256 Self::Points(value)
257 }
258}
259
260impl std::ops::Add for Length {
261 type Output = Self;
262
263 fn add(self, rhs: Self) -> Self::Output {
264 Self::Add(Box::new(self), Box::new(rhs))
265 }
266}
267
268impl std::ops::Sub for Length {
269 type Output = Self;
270
271 fn sub(self, rhs: Self) -> Self::Output {
272 Self::Subtract(Box::new(self), Box::new(rhs))
273 }
274}
275
276impl std::hash::Hash for Length {
277 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
278 std::mem::discriminant(self).hash(state);
279 match self {
280 Self::Points(value)
281 | Self::Percent(value)
282 | Self::ViewportWidth(value)
283 | Self::ViewportHeight(value) => value.to_bits().hash(state),
284 Self::Add(left, right) | Self::Subtract(left, right) => {
285 left.hash(state);
286 right.hash(state);
287 }
288 Self::Min(values) | Self::Max(values) => values.hash(state),
289 Self::Clamp {
290 min,
291 preferred,
292 max,
293 } => {
294 min.hash(state);
295 preferred.hash(state);
296 max.hash(state);
297 }
298 Self::FitContent(limit) => limit.hash(state),
299 Self::MinContent | Self::MaxContent | Self::Auto => {}
300 }
301 }
302}
303
304#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, Hash)]
306pub enum Overflow {
307 #[default]
309 Visible,
310 Clip,
312}
313
314#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, Hash)]
316pub enum BoxAlignment {
317 #[default]
319 Start,
320 Center,
322 End,
324 Stretch,
326}
327
328#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, Hash)]
330pub struct BoxPosition {
331 pub left: Option<Length>,
333 pub top: Option<Length>,
335 pub right: Option<Length>,
337 pub bottom: Option<Length>,
339}
340
341#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, Hash)]
343pub struct BoxGridPlacement {
344 pub row_start: GridPlacement,
346 pub row_end: GridPlacement,
348 pub col_start: GridPlacement,
350 pub col_end: GridPlacement,
352}
353
354#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, Hash)]
375pub struct BoxStyle {
376 pub width: Option<Length>,
378 pub height: Option<Length>,
380 pub min_width: Option<Length>,
382 pub max_width: Option<Length>,
384 pub min_height: Option<Length>,
386 pub max_height: Option<Length>,
388 pub padding: Option<[Length; 4]>,
390 pub margin: Option<[Length; 4]>,
392 pub aspect_ratio: Option<OrderedLayoutUnit>,
394 pub overflow: Overflow,
396 pub alignment: BoxAlignment,
398 pub position: Option<BoxPosition>,
400 pub grid: Option<BoxGridPlacement>,
402 pub flex_grow: Option<OrderedLayoutUnit>,
404 pub flex_shrink: Option<OrderedLayoutUnit>,
406}
407
408impl BoxStyle {
409 pub fn width(mut self, value: Length) -> Self {
411 self.width = Some(value);
412 self
413 }
414
415 pub fn height(mut self, value: Length) -> Self {
417 self.height = Some(value);
418 self
419 }
420
421 pub fn min_width(mut self, value: Length) -> Self {
423 self.min_width = Some(value);
424 self
425 }
426
427 pub fn max_width(mut self, value: Length) -> Self {
429 self.max_width = Some(value);
430 self
431 }
432
433 pub fn min_height(mut self, value: Length) -> Self {
435 self.min_height = Some(value);
436 self
437 }
438
439 pub fn max_height(mut self, value: Length) -> Self {
441 self.max_height = Some(value);
442 self
443 }
444
445 pub fn padding(mut self, edges: [Length; 4]) -> Self {
447 self.padding = Some(edges);
448 self
449 }
450
451 pub fn padding_all(self, value: Length) -> Self {
453 self.padding(Length::all(value))
454 }
455
456 pub fn padding_symmetric(self, horizontal: Length, vertical: Length) -> Self {
458 self.padding(Length::symmetric(horizontal, vertical))
459 }
460
461 pub fn margin(mut self, edges: [Length; 4]) -> Self {
463 self.margin = Some(edges);
464 self
465 }
466
467 pub fn margin_all(self, value: Length) -> Self {
469 self.margin(Length::all(value))
470 }
471
472 pub fn margin_symmetric(self, horizontal: Length, vertical: Length) -> Self {
474 self.margin(Length::symmetric(horizontal, vertical))
475 }
476
477 pub fn overflow(mut self, overflow: Overflow) -> Self {
479 self.overflow = overflow;
480 self
481 }
482
483 pub fn align(mut self, alignment: BoxAlignment) -> Self {
485 self.alignment = alignment;
486 self
487 }
488
489 pub fn aspect_ratio(mut self, ratio: LayoutUnit) -> Self {
491 self.aspect_ratio = Some(OrderedLayoutUnit(ratio.max(0.0)));
492 self
493 }
494
495 pub fn positioned(mut self, position: BoxPosition) -> Self {
497 self.position = Some(position);
498 self
499 }
500
501 pub fn grid(mut self, placement: BoxGridPlacement) -> Self {
503 self.grid = Some(placement);
504 self
505 }
506
507 pub fn flex(mut self, grow: LayoutUnit, shrink: LayoutUnit) -> Self {
509 self.flex_grow = Some(OrderedLayoutUnit(grow));
510 self.flex_shrink = Some(OrderedLayoutUnit(shrink));
511 self
512 }
513}
514
515#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
517pub struct OrderedLayoutUnit(
518 pub LayoutUnit,
520);
521
522impl std::hash::Hash for OrderedLayoutUnit {
523 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
524 self.0.to_bits().hash(state);
525 }
526}
527
528#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
529pub enum TextAlign {
530 Left,
531 Right,
532 Center,
533 Justify,
534 #[default]
535 Start,
536 End,
537}
538
539#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
540pub enum TextOverflow {
541 Clip,
542 Ellipsis,
543 Fade,
544 #[default]
545 Visible,
546}
547
548#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
549pub enum TextDirection {
550 #[default]
551 Auto,
552 Ltr,
553 Rtl,
554}
555
556#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
557pub enum TextWidthBasis {
558 #[default]
559 Parent,
560 LongestLine,
561}
562
563#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
564pub enum MouseCursor {
565 #[default]
566 Basic,
567 Pointer,
568 Text,
569}
570
571#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
572pub struct TextHeightBehavior {
573 pub apply_height_to_first_ascent: bool,
574 pub apply_height_to_last_descent: bool,
575}
576
577impl Default for TextHeightBehavior {
578 fn default() -> Self {
579 Self {
580 apply_height_to_first_ascent: true,
581 apply_height_to_last_descent: true,
582 }
583 }
584}
585
586#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
587pub struct TextParagraphStyle {
588 pub text_align: TextAlign,
589 pub max_lines: Option<usize>,
590 pub overflow: TextOverflow,
591 #[serde(default)]
592 pub text_direction: TextDirection,
593 #[serde(default)]
594 pub text_width_basis: TextWidthBasis,
595 #[serde(default)]
596 pub strut_line_height: Option<LayoutUnit>,
597 #[serde(default)]
598 pub text_height_behavior: TextHeightBehavior,
599}
600
601impl PartialEq for TextParagraphStyle {
602 fn eq(&self, other: &Self) -> bool {
603 self.text_align == other.text_align
604 && self.max_lines == other.max_lines
605 && self.overflow == other.overflow
606 && self.text_direction == other.text_direction
607 && self.text_width_basis == other.text_width_basis
608 && self.strut_line_height.map(f32::to_bits) == other.strut_line_height.map(f32::to_bits)
609 && self.text_height_behavior == other.text_height_behavior
610 }
611}
612
613impl Eq for TextParagraphStyle {}
614
615impl std::hash::Hash for TextParagraphStyle {
616 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
617 self.text_align.hash(state);
618 self.max_lines.hash(state);
619 self.overflow.hash(state);
620 self.text_direction.hash(state);
621 self.text_width_basis.hash(state);
622 self.strut_line_height.map(f32::to_bits).hash(state);
623 self.text_height_behavior.hash(state);
624 }
625}
626
627const TEXT_PARAGRAPH_ALIGN_BITS: u32 = 0b111;
628const TEXT_PARAGRAPH_OVERFLOW_BITS: u32 = 0b111 << 3;
629const TEXT_PARAGRAPH_MAX_LINES_SHIFT: u32 = 6;
630const TEXT_PARAGRAPH_SENTINEL: u32 = 1;
631pub(super) const TEXT_PARAGRAPH_MAX_ENCODED_LINES: usize =
632 ((1 << 24) - 1) >> TEXT_PARAGRAPH_MAX_LINES_SHIFT;
633
634const fn text_align_code(align: TextAlign) -> u32 {
635 match align {
636 TextAlign::Start => 0,
637 TextAlign::Left => 1,
638 TextAlign::Center => 2,
639 TextAlign::Right => 3,
640 TextAlign::End => 4,
641 TextAlign::Justify => 5,
642 }
643}
644
645const fn text_overflow_code(overflow: TextOverflow) -> u32 {
646 match overflow {
647 TextOverflow::Visible => 0,
648 TextOverflow::Clip => 1,
649 TextOverflow::Ellipsis => 2,
650 TextOverflow::Fade => 3,
651 }
652}
653
654const fn decode_text_align(code: u32) -> TextAlign {
655 match code {
656 1 => TextAlign::Left,
657 2 => TextAlign::Center,
658 3 => TextAlign::Right,
659 4 => TextAlign::End,
660 5 => TextAlign::Justify,
661 _ => TextAlign::Start,
662 }
663}
664
665const fn decode_text_overflow(code: u32) -> TextOverflow {
666 match code {
667 1 => TextOverflow::Clip,
668 2 => TextOverflow::Ellipsis,
669 3 => TextOverflow::Fade,
670 _ => TextOverflow::Visible,
671 }
672}
673
674pub fn encode_text_paragraph_style(style: TextParagraphStyle) -> Option<LayoutUnit> {
675 if style == TextParagraphStyle::default() {
676 return None;
677 }
678 if style.text_direction != TextDirection::Auto
679 || style.text_width_basis != TextWidthBasis::Parent
680 || style.strut_line_height.is_some()
681 || style.text_height_behavior != TextHeightBehavior::default()
682 {
683 return None;
684 }
685
686 let max_lines = style
687 .max_lines
688 .unwrap_or(0)
689 .min(TEXT_PARAGRAPH_MAX_ENCODED_LINES) as u32;
690 let encoded = TEXT_PARAGRAPH_SENTINEL
691 + text_align_code(style.text_align)
692 + (text_overflow_code(style.overflow) << 3)
693 + (max_lines << TEXT_PARAGRAPH_MAX_LINES_SHIFT);
694
695 Some(-(encoded as LayoutUnit))
696}
697
698pub fn decode_text_paragraph_style(
699 encoded_width: Option<LayoutUnit>,
700) -> Option<TextParagraphStyle> {
701 let encoded_width = encoded_width?;
702 if !encoded_width.is_finite() || encoded_width >= 0.0 {
703 return None;
704 }
705
706 let raw = (-encoded_width).round();
707 if raw < TEXT_PARAGRAPH_SENTINEL as f32 {
708 return None;
709 }
710
711 let bits = raw as u32 - TEXT_PARAGRAPH_SENTINEL;
712 let text_align = decode_text_align(bits & TEXT_PARAGRAPH_ALIGN_BITS);
713 let overflow = decode_text_overflow((bits & TEXT_PARAGRAPH_OVERFLOW_BITS) >> 3);
714 let max_lines = match bits >> TEXT_PARAGRAPH_MAX_LINES_SHIFT {
715 0 => None,
716 lines => Some(lines as usize),
717 };
718
719 Some(TextParagraphStyle {
720 text_align,
721 max_lines,
722 overflow,
723 text_direction: TextDirection::Auto,
724 text_width_basis: TextWidthBasis::Parent,
725 strut_line_height: None,
726 text_height_behavior: TextHeightBehavior::default(),
727 })
728}
729
730#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Hash)]
731pub enum FlexDirection {
732 Row,
733 Column,
734}
735
736impl Default for FlexDirection {
737 fn default() -> Self {
738 FlexDirection::Row
739 }
740}
741
742#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Hash)]
743pub enum EmbedKind {
744 Video,
745 Web,
746 Custom(Vec<u8>),
747}
748
749#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
750pub enum GridTrack {
751 Points(LayoutUnit),
753 Percent(f32),
755 Fr(f32),
757 Auto,
759 MinContent,
761 MaxContent,
763 MinMax(Box<GridTrack>, Box<GridTrack>),
765 Repeat { count: u16, tracks: Vec<GridTrack> },
767 AutoFit(Box<GridTrack>),
769 AutoFill(Box<GridTrack>),
771}
772
773#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, Hash)]
775pub enum ResponsiveQuery {
776 #[default]
778 Viewport,
779 Container,
781}
782
783#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
785pub struct ResponsiveCondition {
786 pub min_width: Option<LayoutUnit>,
788 pub max_width: Option<LayoutUnit>,
790}
791
792impl ResponsiveCondition {
793 pub fn matches(self, width: LayoutUnit) -> bool {
794 self.min_width.is_none_or(|minimum| width >= minimum)
795 && self.max_width.is_none_or(|maximum| width < maximum)
796 }
797}
798
799impl std::hash::Hash for ResponsiveCondition {
800 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
801 self.min_width.map(f32::to_bits).hash(state);
802 self.max_width.map(f32::to_bits).hash(state);
803 }
804}
805
806impl GridTrack {
807 pub fn minmax(min: GridTrack, max: GridTrack) -> Self {
817 Self::MinMax(Box::new(min), Box::new(max))
818 }
819
820 pub fn repeat(count: u16, tracks: impl Into<Vec<GridTrack>>) -> Self {
822 Self::Repeat {
823 count,
824 tracks: tracks.into(),
825 }
826 }
827
828 pub fn auto_fit(track: GridTrack) -> Self {
830 Self::AutoFit(Box::new(track))
831 }
832
833 pub fn auto_fill(track: GridTrack) -> Self {
835 Self::AutoFill(Box::new(track))
836 }
837}
838
839impl std::hash::Hash for GridTrack {
840 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
841 match self {
842 Self::Points(u) => {
843 0.hash(state);
844 u.to_bits().hash(state);
845 }
846 Self::Percent(f) => {
847 1.hash(state);
848 f.to_bits().hash(state);
849 }
850 Self::Fr(f) => {
851 2.hash(state);
852 f.to_bits().hash(state);
853 }
854 Self::Auto => {
855 3.hash(state);
856 }
857 Self::MinContent => {
858 4.hash(state);
859 }
860 Self::MaxContent => {
861 5.hash(state);
862 }
863 Self::MinMax(min, max) => {
864 6.hash(state);
865 min.hash(state);
866 max.hash(state);
867 }
868 Self::Repeat { count, tracks } => {
869 7.hash(state);
870 count.hash(state);
871 tracks.hash(state);
872 }
873 Self::AutoFit(track) => {
874 8.hash(state);
875 track.hash(state);
876 }
877 Self::AutoFill(track) => {
878 9.hash(state);
879 track.hash(state);
880 }
881 }
882 }
883}
884
885#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
886pub enum GridPlacement {
887 Auto,
889 Line(i16),
891 Span(u16),
893}
894
895impl Default for GridPlacement {
896 fn default() -> Self {
897 Self::Auto
898 }
899}
900
901#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Hash)]
902pub enum FlexWrap {
903 NoWrap,
904 Wrap,
905 WrapReverse,
906}
907
908impl Default for FlexWrap {
909 fn default() -> Self {
910 FlexWrap::NoWrap
911 }
912}
913
914#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Hash)]
915pub enum AlignItems {
916 Start,
917 End,
918 Center,
919 Stretch,
920 Baseline,
921}
922
923impl Default for AlignItems {
924 fn default() -> Self {
925 AlignItems::Stretch
926 }
927}
928
929#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Hash)]
930pub enum JustifyContent {
931 Start,
932 End,
933 Center,
934 SpaceBetween,
935 SpaceAround,
936 SpaceEvenly,
937}
938
939impl Default for JustifyContent {
940 fn default() -> Self {
941 JustifyContent::Start
942 }
943}
944
945#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
946pub enum LayoutOp {
947 Box {
948 width: Option<LayoutUnit>,
949 height: Option<LayoutUnit>,
950 min_width: Option<LayoutUnit>,
951 max_width: Option<LayoutUnit>,
952 min_height: Option<LayoutUnit>,
953 max_height: Option<LayoutUnit>,
954 padding: [LayoutUnit; 4],
955 flex_grow: LayoutUnit,
956 flex_shrink: LayoutUnit,
957 aspect_ratio: Option<f32>,
958 },
959 StyledBox {
961 style: BoxStyle,
962 flex_grow: LayoutUnit,
963 flex_shrink: LayoutUnit,
964 },
965 Flex {
966 direction: FlexDirection,
967 wrap: FlexWrap,
968 flex_grow: LayoutUnit,
969 flex_shrink: LayoutUnit,
970 padding: [LayoutUnit; 4],
971 gap: Option<LayoutUnit>,
972 align_items: AlignItems,
973 justify_content: JustifyContent,
974 },
975 Grid {
976 columns: Vec<GridTrack>,
977 rows: Vec<GridTrack>,
978 column_gap: Option<LayoutUnit>,
979 row_gap: Option<LayoutUnit>,
980 padding: [LayoutUnit; 4],
981 },
982 GridItem {
983 row_start: GridPlacement,
984 row_end: GridPlacement,
985 col_start: GridPlacement,
986 col_end: GridPlacement,
987 },
988 Responsive {
990 query: ResponsiveQuery,
991 cases: Vec<ResponsiveCondition>,
992 },
993 Scroll {
994 direction: FlexDirection,
995 show_scrollbar: bool,
996 width: Option<LayoutUnit>,
997 height: Option<LayoutUnit>,
998 min_width: Option<LayoutUnit>,
999 max_width: Option<LayoutUnit>,
1000 min_height: Option<LayoutUnit>,
1001 max_height: Option<LayoutUnit>,
1002 padding: [LayoutUnit; 4],
1003 flex_grow: LayoutUnit,
1004 flex_shrink: LayoutUnit,
1005 },
1006 Embed {
1007 kind: EmbedKind,
1008 widget_id: WidgetId,
1009 width: Option<LayoutUnit>,
1010 height: Option<LayoutUnit>,
1011 },
1012 AbsoluteFill,
1013 Positioned {
1014 left: Option<LayoutUnit>,
1015 top: Option<LayoutUnit>,
1016 right: Option<LayoutUnit>,
1017 bottom: Option<LayoutUnit>,
1018 width: Option<LayoutUnit>,
1019 height: Option<LayoutUnit>,
1020 },
1021 PositionedLengths {
1023 left: Option<Length>,
1024 top: Option<Length>,
1025 right: Option<Length>,
1026 bottom: Option<Length>,
1027 width: Option<Length>,
1028 height: Option<Length>,
1029 },
1030 ZStack,
1031 Align,
1032 Flyout {
1033 anchor: WidgetId,
1034 content: WidgetId,
1035 },
1036 Spotlight {
1041 anchor: WidgetId,
1042 padding: LayoutUnit,
1043 },
1044 Transform {
1045 transform: [f32; 16],
1046 },
1047 InteractiveViewport {
1048 initial_transform: ViewportTransform,
1049 controlled_transform: Option<ViewportTransform>,
1050 pan_axis: ViewportPanAxis,
1051 boundary: ViewportBoundary,
1052 clip: ViewportClip,
1053 zoom_policy: ViewportZoomPolicy,
1054 min_scale: f32,
1055 max_scale: f32,
1056 friction: f32,
1057 on_interaction_start: Option<ActionEntry>,
1058 on_interaction_update: Option<ActionEntry>,
1059 on_interaction_end: Option<ActionEntry>,
1060 },
1061 Clip {
1062 path: Option<String>,
1063 },
1064}
1065
1066impl std::hash::Hash for LayoutOp {
1067 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1068 let hash_unit = |u: LayoutUnit, h: &mut H| u.to_bits().hash(h);
1069 let hash_opt_unit = |u: Option<LayoutUnit>, h: &mut H| u.map(|v| v.to_bits()).hash(h);
1070 let hash_units = |us: [LayoutUnit; 4], h: &mut H| {
1071 for u in us {
1072 u.to_bits().hash(h);
1073 }
1074 };
1075
1076 match self {
1077 Self::Box {
1078 width,
1079 height,
1080 min_width,
1081 max_width,
1082 min_height,
1083 max_height,
1084 padding,
1085 flex_grow,
1086 flex_shrink,
1087 aspect_ratio,
1088 } => {
1089 0.hash(state);
1090 hash_opt_unit(*width, state);
1091 hash_opt_unit(*height, state);
1092 hash_opt_unit(*min_width, state);
1093 hash_opt_unit(*max_width, state);
1094 hash_opt_unit(*min_height, state);
1095 hash_opt_unit(*max_height, state);
1096 hash_units(*padding, state);
1097 hash_unit(*flex_grow, state);
1098 hash_unit(*flex_shrink, state);
1099 aspect_ratio.map(|f| f.to_bits()).hash(state);
1100 }
1101 Self::StyledBox {
1102 style,
1103 flex_grow,
1104 flex_shrink,
1105 } => {
1106 13.hash(state);
1107 style.hash(state);
1108 hash_unit(*flex_grow, state);
1109 hash_unit(*flex_shrink, state);
1110 }
1111 Self::Flex {
1112 direction,
1113 wrap,
1114 flex_grow,
1115 flex_shrink,
1116 padding,
1117 gap,
1118 align_items,
1119 justify_content,
1120 } => {
1121 1.hash(state);
1122 direction.hash(state);
1123 wrap.hash(state);
1124 hash_unit(*flex_grow, state);
1125 hash_unit(*flex_shrink, state);
1126 hash_units(*padding, state);
1127 hash_opt_unit(*gap, state);
1128 align_items.hash(state);
1129 justify_content.hash(state);
1130 }
1131 Self::Grid {
1132 columns,
1133 rows,
1134 column_gap,
1135 row_gap,
1136 padding,
1137 } => {
1138 2.hash(state);
1139 columns.hash(state);
1140 rows.hash(state);
1141 hash_opt_unit(*column_gap, state);
1142 hash_opt_unit(*row_gap, state);
1143 hash_units(*padding, state);
1144 }
1145 Self::GridItem {
1146 row_start,
1147 row_end,
1148 col_start,
1149 col_end,
1150 } => {
1151 3.hash(state);
1152 row_start.hash(state);
1153 row_end.hash(state);
1154 col_start.hash(state);
1155 col_end.hash(state);
1156 }
1157 Self::Responsive { query, cases } => {
1158 14.hash(state);
1159 query.hash(state);
1160 cases.hash(state);
1161 }
1162 Self::Scroll {
1163 direction,
1164 show_scrollbar,
1165 width,
1166 height,
1167 min_width,
1168 max_width,
1169 min_height,
1170 max_height,
1171 padding,
1172 flex_grow,
1173 flex_shrink,
1174 } => {
1175 4.hash(state);
1176 direction.hash(state);
1177 show_scrollbar.hash(state);
1178 hash_opt_unit(*width, state);
1179 hash_opt_unit(*height, state);
1180 hash_opt_unit(*min_width, state);
1181 hash_opt_unit(*max_width, state);
1182 hash_opt_unit(*min_height, state);
1183 hash_opt_unit(*max_height, state);
1184 hash_units(*padding, state);
1185 hash_unit(*flex_grow, state);
1186 hash_unit(*flex_shrink, state);
1187 }
1188 Self::Embed {
1189 kind,
1190 widget_id,
1191 width,
1192 height,
1193 } => {
1194 5.hash(state);
1195 kind.hash(state);
1196 widget_id.hash(state);
1197 hash_opt_unit(*width, state);
1198 hash_opt_unit(*height, state);
1199 }
1200 Self::AbsoluteFill => {
1201 6.hash(state);
1202 }
1203 Self::Positioned {
1204 left,
1205 top,
1206 right,
1207 bottom,
1208 width,
1209 height,
1210 } => {
1211 7.hash(state);
1212 hash_opt_unit(*left, state);
1213 hash_opt_unit(*top, state);
1214 hash_opt_unit(*right, state);
1215 hash_opt_unit(*bottom, state);
1216 hash_opt_unit(*width, state);
1217 hash_opt_unit(*height, state);
1218 }
1219 Self::PositionedLengths {
1220 left,
1221 top,
1222 right,
1223 bottom,
1224 width,
1225 height,
1226 } => {
1227 15.hash(state);
1228 left.hash(state);
1229 top.hash(state);
1230 right.hash(state);
1231 bottom.hash(state);
1232 width.hash(state);
1233 height.hash(state);
1234 }
1235 Self::ZStack => {
1236 8.hash(state);
1237 }
1238 Self::Align => {
1239 9.hash(state);
1240 }
1241 Self::Flyout { anchor, content } => {
1242 10.hash(state);
1243 anchor.hash(state);
1244 content.hash(state);
1245 }
1246 Self::Transform { transform } => {
1247 11.hash(state);
1248 for v in transform {
1249 v.to_bits().hash(state);
1250 }
1251 }
1252 Self::InteractiveViewport {
1253 initial_transform,
1254 controlled_transform,
1255 pan_axis,
1256 boundary,
1257 clip,
1258 zoom_policy,
1259 min_scale,
1260 max_scale,
1261 friction,
1262 on_interaction_start,
1263 on_interaction_update,
1264 on_interaction_end,
1265 } => {
1266 17.hash(state);
1267 initial_transform.hash(state);
1268 controlled_transform.hash(state);
1269 pan_axis.hash(state);
1270 boundary.hash(state);
1271 clip.hash(state);
1272 zoom_policy.hash(state);
1273 min_scale.to_bits().hash(state);
1274 max_scale.to_bits().hash(state);
1275 friction.to_bits().hash(state);
1276 on_interaction_start.hash(state);
1277 on_interaction_update.hash(state);
1278 on_interaction_end.hash(state);
1279 }
1280 Self::Clip { path } => {
1281 12.hash(state);
1282 path.hash(state);
1283 }
1284 Self::Spotlight { anchor, padding } => {
1285 16.hash(state);
1286 anchor.hash(state);
1287 hash_unit(*padding, state);
1288 }
1289 }
1290 }
1291}
1292
1293#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Hash)]
1294pub struct Color {
1295 pub r: u8,
1296 pub g: u8,
1297 pub b: u8,
1298 pub a: u8,
1299}
1300
1301impl Color {
1302 pub const TRANSPARENT: Self = Self {
1303 r: 0,
1304 g: 0,
1305 b: 0,
1306 a: 0,
1307 };
1308 pub const BLACK: Self = Self {
1309 r: 0,
1310 g: 0,
1311 b: 0,
1312 a: 255,
1313 };
1314 pub const WHITE: Self = Self {
1315 r: 255,
1316 g: 255,
1317 b: 255,
1318 a: 255,
1319 };
1320 pub const RED: Self = Self {
1321 r: 255,
1322 g: 0,
1323 b: 0,
1324 a: 255,
1325 };
1326 pub const GREEN: Self = Self {
1327 r: 0,
1328 g: 255,
1329 b: 0,
1330 a: 255,
1331 };
1332 pub const BLUE: Self = Self {
1333 r: 0,
1334 g: 0,
1335 b: 255,
1336 a: 255,
1337 };
1338
1339 pub fn with_alpha(mut self, a: u8) -> Self {
1340 self.a = a;
1341 self
1342 }
1343}
1344
1345#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1346pub enum Fill {
1347 Solid(Color),
1348 LinearGradient {
1352 start: (f32, f32),
1353 end: (f32, f32),
1354 stops: Vec<(f32, Color)>,
1355 },
1356 RadialGradient {
1358 center: (f32, f32),
1359 radius: f32,
1360 stops: Vec<(f32, Color)>,
1361 },
1362}
1363
1364impl std::hash::Hash for Fill {
1365 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1366 match self {
1367 Self::Solid(c) => {
1368 0.hash(state);
1369 c.hash(state);
1370 }
1371 Self::LinearGradient { start, end, stops } => {
1372 1.hash(state);
1373 start.0.to_bits().hash(state);
1374 start.1.to_bits().hash(state);
1375 end.0.to_bits().hash(state);
1376 end.1.to_bits().hash(state);
1377 for (off, c) in stops {
1378 off.to_bits().hash(state);
1379 c.hash(state);
1380 }
1381 }
1382 Self::RadialGradient {
1383 center,
1384 radius,
1385 stops,
1386 } => {
1387 2.hash(state);
1388 center.0.to_bits().hash(state);
1389 center.1.to_bits().hash(state);
1390 radius.to_bits().hash(state);
1391 for (off, c) in stops {
1392 off.to_bits().hash(state);
1393 c.hash(state);
1394 }
1395 }
1396 }
1397 }
1398}
1399
1400#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1401pub enum LineCap {
1402 Butt,
1403 Round,
1404 Square,
1405}
1406
1407#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1408pub enum LineJoin {
1409 Miter,
1410 Round,
1411 Bevel,
1412}
1413
1414#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1415pub struct Stroke {
1416 pub fill: Fill,
1417 pub width: LayoutUnit,
1418 pub dash_array: Option<Vec<f32>>,
1419 pub line_cap: LineCap,
1420 pub line_join: LineJoin,
1421}
1422
1423impl std::hash::Hash for Stroke {
1424 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1425 self.fill.hash(state);
1426 self.width.to_bits().hash(state);
1427 if let Some(da) = &self.dash_array {
1428 1.hash(state);
1429 for d in da {
1430 d.to_bits().hash(state);
1431 }
1432 } else {
1433 0.hash(state);
1434 }
1435 self.line_cap.hash(state);
1436 self.line_join.hash(state);
1437 }
1438}
1439
1440#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1441pub struct BoxShadow {
1442 pub color: Color,
1443 pub blur_radius: LayoutUnit,
1444 pub spread_radius: LayoutUnit,
1446 pub offset: (LayoutUnit, LayoutUnit),
1447 pub inset: bool,
1449}
1450
1451impl std::hash::Hash for BoxShadow {
1452 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1453 self.color.hash(state);
1454 self.blur_radius.to_bits().hash(state);
1455 self.spread_radius.to_bits().hash(state);
1456 self.offset.0.to_bits().hash(state);
1457 self.offset.1.to_bits().hash(state);
1458 self.inset.hash(state);
1459 }
1460}
1461
1462#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Hash)]
1463pub enum ImageFit {
1464 Contain,
1465 Cover,
1466 Fill,
1467 None,
1468}
1469
1470#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
1471pub enum ImageAlignment {
1472 TopStart,
1473 TopCenter,
1474 TopEnd,
1475 CenterStart,
1476 #[default]
1477 Center,
1478 CenterEnd,
1479 BottomStart,
1480 BottomCenter,
1481 BottomEnd,
1482}
1483
1484#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
1485pub struct HttpHeader {
1486 pub name: String,
1487 pub value: String,
1488}
1489
1490#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
1491pub enum ImageCachePolicy {
1492 #[default]
1493 Default,
1494 Reload,
1495 MemoryOnly,
1496 Disk,
1497 NoStore,
1498}
1499
1500#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
1501pub enum ImageSource {
1502 Asset {
1503 path: String,
1504 },
1505 File {
1506 path: String,
1507 },
1508 Network {
1509 url: String,
1510 #[serde(default)]
1511 headers: Vec<HttpHeader>,
1512 #[serde(default)]
1513 cache_policy: ImageCachePolicy,
1514 },
1515 Memory {
1516 bytes: Vec<u8>,
1517 #[serde(default)]
1518 mime_type: Option<String>,
1519 },
1520 SvgText {
1521 content: String,
1522 },
1523}
1524
1525impl Default for ImageSource {
1526 fn default() -> Self {
1527 Self::Asset {
1528 path: String::new(),
1529 }
1530 }
1531}
1532
1533#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
1534pub enum ImageLoadingBehavior {
1535 #[default]
1536 Empty,
1537 ThemePlaceholder,
1538 BlurHash(String),
1539}
1540
1541#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
1542pub enum ImageErrorBehavior {
1543 #[default]
1544 Empty,
1545 ThemeError,
1546 AltText,
1547}
1548
1549#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
1550pub struct ImageRequest {
1551 pub source: ImageSource,
1552 #[serde(default)]
1553 pub cache_width: Option<u32>,
1554 #[serde(default)]
1555 pub cache_height: Option<u32>,
1556 #[serde(default)]
1557 pub semantic_label: Option<String>,
1558 #[serde(default)]
1559 pub loading: ImageLoadingBehavior,
1560 #[serde(default)]
1561 pub error: ImageErrorBehavior,
1562}
1563
1564impl ImageSource {
1565 pub fn stable_identity(&self) -> String {
1566 match self {
1567 Self::Asset { path } => format!("asset:{path}"),
1568 Self::File { path } => format!("file:{path}"),
1569 Self::Network {
1570 url,
1571 headers,
1572 cache_policy,
1573 } => {
1574 let mut identity = format!("network:{cache_policy:?}:{url}");
1575 for header in headers {
1576 identity.push('|');
1577 identity.push_str(&header.name.to_ascii_lowercase());
1578 identity.push('=');
1579 identity.push_str(&header.value);
1580 }
1581 identity
1582 }
1583 Self::Memory { bytes, mime_type } => {
1584 let digest = blake3::hash(bytes);
1585 format!("memory:{}:{digest}", mime_type.as_deref().unwrap_or(""))
1586 }
1587 Self::SvgText { content } => {
1588 let digest = blake3::hash(content.as_bytes());
1589 format!("svg:{digest}")
1590 }
1591 }
1592 }
1593
1594 pub fn local_path(&self) -> Option<&str> {
1595 match self {
1596 Self::Asset { path } | Self::File { path } => Some(path),
1597 _ => None,
1598 }
1599 }
1600
1601 pub fn network_url(&self) -> Option<&str> {
1602 match self {
1603 Self::Network { url, .. } => Some(url),
1604 _ => None,
1605 }
1606 }
1607}
1608
1609impl ImageRequest {
1610 pub fn stable_cache_key(&self) -> String {
1611 let mut hasher = blake3::Hasher::new();
1612 hasher.update(self.source.stable_identity().as_bytes());
1613 hasher.update(&self.cache_width.unwrap_or_default().to_le_bytes());
1614 hasher.update(&self.cache_height.unwrap_or_default().to_le_bytes());
1615 hasher.finalize().to_hex().to_string()
1616 }
1617}
1618
1619#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1620pub struct TextStyle {
1621 pub font_size: LayoutUnit,
1622 pub color: Color,
1623 pub underline: bool,
1624 #[serde(default)]
1625 pub font_family: Option<String>,
1626 #[serde(default)]
1627 pub locale: Option<String>,
1628 #[serde(default = "text_weight_default")]
1629 pub font_weight: u16,
1630 #[serde(default)]
1631 pub font_style: FontStyle,
1632 #[serde(default)]
1633 pub line_height: Option<LayoutUnit>,
1634 #[serde(default)]
1635 pub letter_spacing: LayoutUnit,
1636 pub background_color: Option<Color>,
1638}
1639
1640impl std::hash::Hash for TextStyle {
1641 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1642 self.font_size.to_bits().hash(state);
1643 self.color.hash(state);
1644 self.underline.hash(state);
1645 self.font_family.hash(state);
1646 self.locale.hash(state);
1647 self.font_weight.hash(state);
1648 self.font_style.hash(state);
1649 self.line_height.map(f32::to_bits).hash(state);
1650 self.letter_spacing.to_bits().hash(state);
1651 self.background_color.hash(state);
1652 }
1653}
1654
1655#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
1656pub enum FontStyle {
1657 #[default]
1658 Normal,
1659 Italic,
1660}
1661
1662const fn text_weight_default() -> u16 {
1663 400
1664}
1665
1666#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Hash)]
1667pub struct TextRun {
1668 pub text: String,
1669 pub style: TextStyle,
1670}
1671
1672#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
1673pub struct RichTextAnnotation {
1674 pub range: std::ops::Range<usize>,
1675 #[serde(default)]
1676 pub semantics_label: Option<String>,
1677 #[serde(default)]
1678 pub semantics_identifier: Option<String>,
1679 #[serde(default)]
1680 pub spell_out: Option<bool>,
1681 #[serde(default)]
1682 pub mouse_cursor: Option<MouseCursor>,
1683 #[serde(default)]
1684 pub actions: Vec<ActionEntry>,
1685}
1686
1687pub const INLINE_WIDGET_MARKER_PREFIX: &str = "__fission_inline_widget__:";
1688
1689#[derive(Debug, Clone, Copy, PartialEq)]
1690pub struct InlineWidgetMarker {
1691 pub id: u64,
1692 pub width: LayoutUnit,
1693 pub height: LayoutUnit,
1694}
1695
1696pub fn encode_inline_widget_marker(id: u64, width: LayoutUnit, height: LayoutUnit) -> String {
1697 format!("{INLINE_WIDGET_MARKER_PREFIX}{id}:{width}:{height}")
1698}
1699
1700pub fn decode_inline_widget_marker(family: Option<&str>) -> Option<InlineWidgetMarker> {
1701 let family = family?;
1702 let encoded = family.strip_prefix(INLINE_WIDGET_MARKER_PREFIX)?;
1703 let mut parts = encoded.split(':');
1704 let id = parts.next()?.parse().ok()?;
1705 let width = parts.next()?.parse().ok()?;
1706 let height = parts.next()?.parse().ok()?;
1707 if parts.next().is_some() {
1708 return None;
1709 }
1710 Some(InlineWidgetMarker { id, width, height })
1711}
1712
1713const fn text_wrap_default() -> bool {
1714 true
1715}
1716
1717#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1719pub enum BackdropFilter {
1720 Blur(LayoutUnit),
1722}
1723
1724impl std::hash::Hash for BackdropFilter {
1725 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1726 match self {
1727 Self::Blur(sigma) => {
1728 0_u8.hash(state);
1729 sigma.to_bits().hash(state);
1730 }
1731 }
1732 }
1733}
1734
1735#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1736pub enum PaintOp {
1737 BackdropFilter {
1738 filter: BackdropFilter,
1739 corner_radius: LayoutUnit,
1740 },
1741 DrawRect {
1742 fill: Option<Fill>,
1743 stroke: Option<Stroke>,
1744 corner_radius: LayoutUnit,
1745 shadow: Option<BoxShadow>,
1746 },
1747 DrawText {
1748 text: String,
1749 size: LayoutUnit,
1750 color: Color,
1751 underline: bool,
1752 #[serde(default = "text_wrap_default")]
1753 wrap: bool,
1754 caret_index: Option<usize>,
1755 #[serde(default)]
1756 caret_color: Option<Color>,
1757 #[serde(default)]
1758 caret_width: Option<LayoutUnit>,
1759 #[serde(default)]
1760 caret_height: Option<LayoutUnit>,
1761 #[serde(default)]
1762 caret_radius: Option<LayoutUnit>,
1763 #[serde(default)]
1764 paragraph_style: Option<TextParagraphStyle>,
1765 },
1766 DrawRichText {
1767 runs: Vec<TextRun>,
1768 #[serde(default = "text_wrap_default")]
1769 wrap: bool,
1770 caret_index: Option<usize>,
1771 #[serde(default)]
1772 caret_color: Option<Color>,
1773 #[serde(default)]
1774 caret_width: Option<LayoutUnit>,
1775 #[serde(default)]
1776 caret_height: Option<LayoutUnit>,
1777 #[serde(default)]
1778 caret_radius: Option<LayoutUnit>,
1779 #[serde(default)]
1780 paragraph_style: Option<TextParagraphStyle>,
1781 },
1782 DrawImage {
1783 request: ImageRequest,
1784 fit: ImageFit,
1785 alignment: ImageAlignment,
1786 },
1787 DrawPath {
1788 path: String,
1789 fill: Option<Fill>,
1790 stroke: Option<Stroke>,
1791 },
1792 DrawSvg {
1793 content: String,
1794 fill: Option<Fill>,
1795 stroke: Option<Stroke>,
1796 },
1797}
1798
1799impl std::hash::Hash for PaintOp {
1800 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1801 match self {
1802 Self::BackdropFilter {
1803 filter,
1804 corner_radius,
1805 } => {
1806 0_u8.hash(state);
1807 filter.hash(state);
1808 corner_radius.to_bits().hash(state);
1809 }
1810 Self::DrawRect {
1811 fill,
1812 stroke,
1813 corner_radius,
1814 shadow,
1815 } => {
1816 1_u8.hash(state);
1817 fill.hash(state);
1818 stroke.hash(state);
1819 corner_radius.to_bits().hash(state);
1820 shadow.hash(state);
1821 }
1822 Self::DrawText {
1823 text,
1824 size,
1825 color,
1826 underline,
1827 wrap,
1828 caret_index,
1829 caret_color,
1830 caret_width,
1831 caret_height,
1832 caret_radius,
1833 paragraph_style,
1834 } => {
1835 2_u8.hash(state);
1836 text.hash(state);
1837 size.to_bits().hash(state);
1838 color.hash(state);
1839 underline.hash(state);
1840 wrap.hash(state);
1841 caret_index.hash(state);
1842 caret_color.hash(state);
1843 caret_width.map(|w| w.to_bits()).hash(state);
1844 caret_height.map(|h| h.to_bits()).hash(state);
1845 caret_radius.map(|r| r.to_bits()).hash(state);
1846 paragraph_style.hash(state);
1847 }
1848 Self::DrawRichText {
1849 runs,
1850 wrap,
1851 caret_index,
1852 caret_color,
1853 caret_width,
1854 caret_height,
1855 caret_radius,
1856 paragraph_style,
1857 } => {
1858 3_u8.hash(state);
1859 runs.hash(state);
1860 wrap.hash(state);
1861 caret_index.hash(state);
1862 caret_color.hash(state);
1863 caret_width.map(|w| w.to_bits()).hash(state);
1864 caret_height.map(|h| h.to_bits()).hash(state);
1865 caret_radius.map(|r| r.to_bits()).hash(state);
1866 paragraph_style.hash(state);
1867 }
1868 Self::DrawImage {
1869 request,
1870 fit,
1871 alignment,
1872 } => {
1873 4_u8.hash(state);
1874 request.hash(state);
1875 fit.hash(state);
1876 alignment.hash(state);
1877 }
1878 Self::DrawPath { path, fill, stroke } => {
1879 5_u8.hash(state);
1880 path.hash(state);
1881 fill.hash(state);
1882 stroke.hash(state);
1883 }
1884 Self::DrawSvg {
1885 content,
1886 fill,
1887 stroke,
1888 } => {
1889 6_u8.hash(state);
1890 content.hash(state);
1891 fill.hash(state);
1892 stroke.hash(state);
1893 }
1894 }
1895 }
1896}