Skip to main content

rustmotion_components/
divider.rs

1use 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)]
31#[deprecated(
32    since = "0.7.1",
33    note = "`divider` is a frozen composition (issue #333). Compose a separator from a \
34            single thin `shape` (`rect`), full width or full height, instead — see \
35            crates/rustmotion/skills/rules/composition-recipes.md. Kept for compatibility; \
36            scheduled for removal in a future major version via `rustmotion migrate` \
37            (#335)."
38)]
39pub struct Divider {
40    #[serde(default)]
41    pub direction: DividerDirection,
42    #[serde(default = "default_thickness")]
43    pub thickness: f32,
44    #[serde(default)]
45    pub line_style: DividerLineStyle,
46    #[serde(default)]
47    pub length: Option<f32>,
48    #[serde(flatten)]
49    pub timing: TimingConfig,
50    #[serde(default)]
51    pub style: CssStyle,
52    #[serde(default)]
53    pub timeline: Vec<TimelineStep>,
54    #[serde(default)]
55    pub stagger: Option<f32>,
56}
57
58fn default_thickness() -> f32 {
59    2.0
60}
61
62rustmotion_core::impl_traits!(Divider {
63    Animatable => animation,
64    Timed => timing,
65    Styled => style,
66});
67
68impl Painter for Divider {
69    fn paint_content(
70        &self,
71        canvas: &Canvas,
72        layout: &BoxLayout,
73        _props: &rustmotion_core::engine::animator::AnimatedProperties,
74        _ctx: &PaintCtx,
75    ) {
76        let color = self.style.color_str_or("#FFFFFF");
77        let mut paint = paint_from_hex(color);
78        paint.set_anti_alias(true);
79
80        let is_horizontal = matches!(self.direction, DividerDirection::Horizontal);
81
82        match self.line_style {
83            DividerLineStyle::Solid => {
84                paint.set_style(PaintStyle::Fill);
85                if is_horizontal {
86                    let rect = Rect::from_xywh(0.0, 0.0, layout.width, self.thickness);
87                    canvas.draw_rect(rect, &paint);
88                } else {
89                    let rect = Rect::from_xywh(0.0, 0.0, self.thickness, layout.height);
90                    canvas.draw_rect(rect, &paint);
91                }
92            }
93            DividerLineStyle::Dashed | DividerLineStyle::Dotted => {
94                paint.set_style(PaintStyle::Stroke);
95                paint.set_stroke_width(self.thickness);
96
97                let intervals = if matches!(self.line_style, DividerLineStyle::Dashed) {
98                    [self.thickness * 4.0, self.thickness * 3.0]
99                } else {
100                    [self.thickness, self.thickness * 2.0]
101                };
102
103                if let Some(effect) = skia_safe::PathEffect::dash(&intervals, 0.0) {
104                    paint.set_path_effect(effect);
105                }
106
107                let mut path = PathBuilder::new();
108                if is_horizontal {
109                    let y = self.thickness / 2.0;
110                    path.move_to((0.0, y));
111                    path.line_to((layout.width, y));
112                } else {
113                    let x = self.thickness / 2.0;
114                    path.move_to((x, 0.0));
115                    path.line_to((x, layout.height));
116                }
117                canvas.draw_path(&path.detach(), &paint);
118            }
119        }
120    }
121}