Skip to main content

rustmotion_components/
qrcode.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use skia_safe::{Canvas, PaintStyle, Rect};
4
5use rustmotion_core::css::CssStyle;
6use rustmotion_core::engine::animator::AnimatedProperties;
7use rustmotion_core::engine::layout_pass::BoxLayout;
8use rustmotion_core::engine::renderer::color4f_from_hex;
9use rustmotion_core::schema::TimelineStep;
10use rustmotion_core::traits::{PaintCtx, Painter, TimingConfig};
11
12fn default_qr_size() -> f32 {
13    200.0
14}
15fn default_qr_fg() -> String {
16    "#000000".to_string()
17}
18fn default_qr_bg() -> String {
19    "#FFFFFF".to_string()
20}
21
22#[derive(Debug, Serialize, Deserialize, JsonSchema)]
23pub struct QrCode {
24    pub content: String,
25    #[serde(default = "default_qr_size")]
26    pub size: f32,
27    #[serde(default = "default_qr_fg")]
28    pub foreground_color: String,
29    #[serde(default = "default_qr_bg")]
30    pub background_color: String,
31    #[serde(flatten)]
32    pub timing: TimingConfig,
33    #[serde(default)]
34    pub style: CssStyle,
35    #[serde(default)]
36    pub timeline: Vec<TimelineStep>,
37    #[serde(default)]
38    pub stagger: Option<f32>,
39}
40
41rustmotion_core::impl_traits!(QrCode {
42    Animatable => animation,
43    Timed => timing,
44    Styled => style,
45});
46
47impl Painter for QrCode {
48    fn paint_content(
49        &self,
50        canvas: &Canvas,
51        _layout: &BoxLayout,
52        _props: &AnimatedProperties,
53        _ctx: &PaintCtx,
54    ) {
55        use qrcode::QrCode as QrCodeLib;
56
57        let Ok(code) = QrCodeLib::new(self.content.as_bytes()) else {
58            return;
59        };
60
61        let modules = code.to_colors();
62        let module_count = code.width() as f32;
63        let module_size = self.size / module_count;
64
65        let mut bg_paint = skia_safe::Paint::new(color4f_from_hex(&self.background_color), None);
66        bg_paint.set_style(PaintStyle::Fill);
67        canvas.draw_rect(Rect::from_xywh(0.0, 0.0, self.size, self.size), &bg_paint);
68
69        let mut fg_paint = skia_safe::Paint::new(color4f_from_hex(&self.foreground_color), None);
70        fg_paint.set_style(PaintStyle::Fill);
71        fg_paint.set_anti_alias(false);
72
73        for (idx, &color) in modules.iter().enumerate() {
74            if color == qrcode::Color::Dark {
75                let col = (idx % code.width()) as f32;
76                let row = (idx / code.width()) as f32;
77                let rect = Rect::from_xywh(
78                    col * module_size,
79                    row * module_size,
80                    module_size,
81                    module_size,
82                );
83                canvas.draw_rect(rect, &fg_paint);
84            }
85        }
86    }
87}