rustmotion_components/
divider.rs1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use skia_safe::{Canvas, PaintStyle, PathBuilder, Rect};
4
5use rustmotion_core::css::CssStyle;
6use rustmotion_core::engine::layout_pass::BoxLayout;
7use rustmotion_core::engine::renderer::paint_from_hex;
8use rustmotion_core::schema::TimelineStep;
9use rustmotion_core::traits::{PaintCtx, Painter, TimingConfig};
10
11#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
12#[serde(rename_all = "snake_case")]
13#[derive(Default)]
14pub enum DividerDirection {
15 #[default]
16 Horizontal,
17 Vertical,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
21#[serde(rename_all = "snake_case")]
22#[derive(Default)]
23pub enum DividerLineStyle {
24 #[default]
25 Solid,
26 Dashed,
27 Dotted,
28}
29
30#[derive(Debug, Serialize, Deserialize, JsonSchema)]
31pub struct Divider {
32 #[serde(default)]
33 pub direction: DividerDirection,
34 #[serde(default = "default_thickness")]
35 pub thickness: f32,
36 #[serde(default)]
37 pub line_style: DividerLineStyle,
38 #[serde(default)]
39 pub length: Option<f32>,
40 #[serde(flatten)]
41 pub timing: TimingConfig,
42 #[serde(default)]
43 pub style: CssStyle,
44 #[serde(default)]
45 pub timeline: Vec<TimelineStep>,
46 #[serde(default)]
47 pub stagger: Option<f32>,
48}
49
50fn default_thickness() -> f32 {
51 2.0
52}
53
54rustmotion_core::impl_traits!(Divider {
55 Animatable => animation,
56 Timed => timing,
57 Styled => style,
58});
59
60impl Painter for Divider {
61 fn paint_content(
62 &self,
63 canvas: &Canvas,
64 layout: &BoxLayout,
65 _props: &rustmotion_core::engine::animator::AnimatedProperties,
66 _ctx: &PaintCtx,
67 ) {
68 let color = self.style.color_str_or("#FFFFFF");
69 let mut paint = paint_from_hex(color);
70 paint.set_anti_alias(true);
71
72 let is_horizontal = matches!(self.direction, DividerDirection::Horizontal);
73
74 match self.line_style {
75 DividerLineStyle::Solid => {
76 paint.set_style(PaintStyle::Fill);
77 if is_horizontal {
78 let rect = Rect::from_xywh(0.0, 0.0, layout.width, self.thickness);
79 canvas.draw_rect(rect, &paint);
80 } else {
81 let rect = Rect::from_xywh(0.0, 0.0, self.thickness, layout.height);
82 canvas.draw_rect(rect, &paint);
83 }
84 }
85 DividerLineStyle::Dashed | DividerLineStyle::Dotted => {
86 paint.set_style(PaintStyle::Stroke);
87 paint.set_stroke_width(self.thickness);
88
89 let intervals = if matches!(self.line_style, DividerLineStyle::Dashed) {
90 [self.thickness * 4.0, self.thickness * 3.0]
91 } else {
92 [self.thickness, self.thickness * 2.0]
93 };
94
95 if let Some(effect) = skia_safe::PathEffect::dash(&intervals, 0.0) {
96 paint.set_path_effect(effect);
97 }
98
99 let mut path = PathBuilder::new();
100 if is_horizontal {
101 let y = self.thickness / 2.0;
102 path.move_to((0.0, y));
103 path.line_to((layout.width, y));
104 } else {
105 let x = self.thickness / 2.0;
106 path.move_to((x, 0.0));
107 path.line_to((x, layout.height));
108 }
109 canvas.draw_path(&path.detach(), &paint);
110 }
111 }
112 }
113}