1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct Color {
16 pub r: u8,
17 pub g: u8,
18 pub b: u8,
19 pub a: u8,
20}
21
22impl Color {
23 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
24 Self { r, g, b, a: 255 }
25 }
26 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
27 Self { r, g, b, a }
28 }
29 pub const TRANSPARENT: Color = Color::rgba(0, 0, 0, 0);
30 pub const WHITE: Color = Color::rgb(255, 255, 255);
31 pub const GRAY: Color = Color::rgb(128, 128, 128);
32
33 pub fn dim(self, k: f32) -> Color {
35 Color::rgba(self.r, self.g, self.b, (self.a as f32 * k) as u8)
36 }
37}
38
39impl From<egui::Color32> for Color {
40 fn from(c: egui::Color32) -> Self {
41 let [r, g, b, a] = c.to_srgba_unmultiplied();
42 Color { r, g, b, a }
43 }
44}
45
46impl From<Color> for egui::Color32 {
47 fn from(c: Color) -> Self {
48 egui::Color32::from_rgba_unmultiplied(c.r, c.g, c.b, c.a)
49 }
50}
51
52#[derive(Clone, Copy, Debug, PartialEq)]
54pub struct Pos {
55 pub x: f32,
56 pub y: f32,
57}
58
59impl Pos {
60 pub const fn new(x: f32, y: f32) -> Self {
61 Self { x, y }
62 }
63}
64
65pub const BOX_W: f32 = 184.0;
68pub const BOX_H: f32 = 34.0;
69
70#[derive(Clone, Debug)]
73pub struct GraphNode {
74 pub id: String,
75 pub label: String,
76 pub fill: Color,
77 pub stroke: Color,
78 pub pos: Pos,
79}
80
81#[derive(Clone, Debug)]
84pub struct GraphEdge {
85 pub from: String,
86 pub to: String,
87 pub color: Color,
88 pub dashed: bool,
89 pub label: Option<String>,
90}
91
92#[derive(Clone, Debug, Default)]
94pub struct GraphModel {
95 pub nodes: Vec<GraphNode>,
96 pub edges: Vec<GraphEdge>,
97}
98
99impl GraphModel {
100 pub fn world_bounds(&self) -> Option<(f32, f32, f32, f32)> {
103 let mut it = self.nodes.iter();
104 let first = it.next()?;
105 let (hw, hh) = (BOX_W * 0.5, BOX_H * 0.5);
106 let mut b = (first.pos.x - hw, first.pos.y - hh, first.pos.x + hw, first.pos.y + hh);
107 for n in it {
108 b.0 = b.0.min(n.pos.x - hw);
109 b.1 = b.1.min(n.pos.y - hh);
110 b.2 = b.2.max(n.pos.x + hw);
111 b.3 = b.3.max(n.pos.y + hh);
112 }
113 Some(b)
114 }
115}
116
117#[derive(Clone, Debug, Default)]
121pub struct NodeDecoration {
122 pub ring: Option<Color>,
123 pub badge: Option<String>,
124 pub badge_color: Option<Color>,
125}
126
127#[derive(Clone, Debug, Default)]
131pub struct Decorations {
132 pub nodes: std::collections::HashMap<String, NodeDecoration>,
134 pub edges: Vec<GraphEdge>,
136}
137
138#[derive(Clone, Copy, Debug)]
142pub struct Camera {
143 pub pan_x: f32,
144 pub pan_y: f32,
145 pub zoom: f32,
146}
147
148impl Default for Camera {
149 fn default() -> Self {
150 Self { pan_x: 0.0, pan_y: 0.0, zoom: 1.0 }
151 }
152}
153
154impl Camera {
155 pub fn fit(model: &GraphModel, w: f32, h: f32, margin: f32) -> Camera {
158 let Some((min_x, min_y, max_x, max_y)) = model.world_bounds() else {
159 return Camera::default();
160 };
161 let (bw, bh) = ((max_x - min_x).max(1.0), (max_y - min_y).max(1.0));
162 let zoom = ((w - 2.0 * margin) / bw).min((h - 2.0 * margin) / bh).clamp(0.05, 4.0);
163 let (cx, cy) = ((min_x + max_x) * 0.5, (min_y + max_y) * 0.5);
165 Camera { pan_x: w * 0.5 - cx * zoom, pan_y: h * 0.5 - cy * zoom, zoom }
166 }
167
168 #[inline]
170 pub fn project(&self, p: Pos) -> (f32, f32) {
171 (p.x * self.zoom + self.pan_x, p.y * self.zoom + self.pan_y)
172 }
173}