1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//! The canvas background pattern (dots, lines, or crosses), kept in lockstep
//! with the viewport transform.
use dioxus::prelude::*;
use crate::state::FlowCore;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum BackgroundVariant {
#[default]
Dots,
Lines,
Cross,
}
/// A pan/zoom-aware background pattern. Render as a child of
/// [`crate::Flow`].
#[component]
pub fn Background(
#[props(default)] variant: BackgroundVariant,
/// Grid spacing in flow units.
#[props(default = 24.0)]
gap: f64,
/// Dot radius / line width in flow units.
#[props(default = 1.0)]
size: f64,
class: Option<String>,
) -> Element {
let core = use_context::<FlowCore>();
let vp = *core.viewport.read();
let scaled = (gap * vp.zoom).max(1.0);
let x = vp.x.rem_euclid(scaled);
let y = vp.y.rem_euclid(scaled);
let pattern_id = format!("df-bg-{}", core.iid);
let class = format!(
"df-background{}",
class
.as_deref()
.map(|c| format!(" {c}"))
.unwrap_or_default()
);
rsx! {
svg { class,
defs {
pattern {
id: "{pattern_id}",
x,
y,
width: scaled,
height: scaled,
"patternUnits": "userSpaceOnUse",
match variant {
BackgroundVariant::Dots => rsx! {
circle {
class: "df-background-dot",
cx: scaled / 2.0,
cy: scaled / 2.0,
r: (size * vp.zoom).max(0.4),
}
},
BackgroundVariant::Lines => rsx! {
path {
class: "df-background-line",
d: "M {scaled} 0 H 0 V {scaled}",
fill: "none",
stroke_width: (size * vp.zoom).max(0.3),
}
},
BackgroundVariant::Cross => rsx! {
path {
class: "df-background-line",
d: {
let c = scaled / 2.0;
let arm = (3.0 * vp.zoom).max(1.5);
format!(
"M {} {c} H {} M {c} {} V {}",
c - arm,
c + arm,
c - arm,
c + arm,
)
},
fill: "none",
stroke_width: (size * vp.zoom).max(0.3),
}
},
}
}
}
rect {
width: "100%",
height: "100%",
fill: "url(#{pattern_id})",
}
}
}
}