Skip to main content

oak_visualize/
theme.rs

1/// Visualization theme configuration.
2#[derive(Debug, Clone)]
3#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
4pub struct VisualizationTheme {
5    /// The name of the theme.
6    pub name: String,
7    /// The background color of the visualization (hex string).
8    pub background_color: String,
9    /// The style configuration for nodes.
10    pub node: NodeTheme,
11    /// The style configuration for edges.
12    pub edge: EdgeTheme,
13    /// The style configuration for text.
14    pub text: TextTheme,
15    /// The style configuration for highlighted states.
16    pub highlight: HighlightTheme,
17}
18
19/// Node theme configuration.
20#[derive(Debug, Clone)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct NodeTheme {
23    /// The default fill color for nodes (hex string).
24    pub fill_color: String,
25    /// The default stroke color for nodes (hex string).
26    pub stroke_color: String,
27    /// The width of the node's stroke.
28    pub stroke_width: f32,
29    /// The border radius for rounded node corners.
30    pub border_radius: f32,
31    /// The shadow configuration for nodes.
32    pub shadow: ShadowConfig,
33}
34
35/// Edge theme configuration.
36#[derive(Debug, Clone)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct EdgeTheme {
39    /// The default color for edges (hex string).
40    pub color: String,
41    /// The width of the edge line.
42    pub width: f32,
43    /// The line style (e.g., "solid", "dashed", "dotted").
44    pub style: String,
45    /// The arrowhead configuration for directed edges.
46    pub arrow: ArrowConfig,
47}
48
49/// Text theme configuration.
50#[derive(Debug, Clone)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52pub struct TextTheme {
53    /// The font family for text elements.
54    pub font_family: String,
55    /// The font size in pixels.
56    pub font_size: f32,
57    /// The default color for text (hex string).
58    pub color: String,
59    /// The font weight (e.g., "normal", "bold").
60    pub font_weight: String,
61}
62
63/// Highlight theme configuration.
64#[derive(Debug, Clone)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66pub struct HighlightTheme {
67    /// The color used for selected elements (hex string).
68    pub selected_color: String,
69    /// The color used for elements on hover (hex string).
70    pub hover_color: String,
71    /// The color used for elements in an error state (hex string).
72    pub error_color: String,
73    /// The color used for elements in a warning state (hex string).
74    pub warning_color: String,
75}
76
77/// Shadow configuration.
78#[derive(Debug, Clone)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
80pub struct ShadowConfig {
81    /// Whether to enable shadow effects.
82    pub enabled: bool,
83    /// The color of the shadow (hex string).
84    pub color: String,
85    /// The horizontal offset of the shadow.
86    pub offset_x: f32,
87    /// The vertical offset of the shadow.
88    pub offset_y: f32,
89    /// The blur radius of the shadow.
90    pub blur_radius: f32,
91}
92
93/// Arrow configuration for directed edges.
94#[derive(Debug, Clone)]
95#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96pub struct ArrowConfig {
97    /// Whether to show arrowheads.
98    pub enabled: bool,
99    /// The size of the arrowhead.
100    pub size: f32,
101    /// The type of arrowhead (e.g., "triangle", "circle", "diamond").
102    pub arrow_type: String,
103}
104
105impl VisualizationTheme {
106    /// Converts to render configuration
107    pub fn to_render_config(&self) -> crate::render::RenderConfig {
108        crate::render::RenderConfig {
109            background_color: self.background_color.clone(),
110            node_fill_color: self.node.fill_color.clone(),
111            node_stroke_color: self.node.stroke_color.clone(),
112            node_stroke_width: self.node.stroke_width as f64,
113            edge_color: self.edge.color.clone(),
114            edge_width: self.edge.width as f64,
115            text_color: self.text.color.clone(),
116            text_size: self.text.font_size as f64,
117            font_family: self.text.font_family.clone(),
118            show_arrows: self.edge.arrow.enabled,
119            arrow_size: self.edge.arrow.size as f64,
120            ..Default::default()
121        }
122    }
123}
124
125impl Default for VisualizationTheme {
126    fn default() -> Self {
127        Self::light()
128    }
129}
130
131impl VisualizationTheme {
132    /// Light theme
133    pub fn light() -> Self {
134        Self {
135            name: "Light".to_string(),
136            background_color: "#FFFFFF".to_string(),
137            node: NodeTheme {
138                fill_color: "#F8F9FA".to_string(),
139                stroke_color: "#DEE2E6".to_string(),
140                stroke_width: 1.0,
141                border_radius: 4.0,
142                shadow: ShadowConfig { enabled: true, color: "rgba(0, 0, 0, 0.1)".to_string(), offset_x: 0.0, offset_y: 2.0, blur_radius: 4.0 },
143            },
144            edge: EdgeTheme { color: "#6C757D".to_string(), width: 1.5, style: "solid".to_string(), arrow: ArrowConfig { enabled: true, size: 8.0, arrow_type: "triangle".to_string() } },
145            text: TextTheme { font_family: "Arial, sans-serif".to_string(), font_size: 12.0, color: "#212529".to_string(), font_weight: "normal".to_string() },
146            highlight: HighlightTheme { selected_color: "#007BFF".to_string(), hover_color: "#0056B3".to_string(), error_color: "#DC3545".to_string(), warning_color: "#FFC107".to_string() },
147        }
148    }
149
150    /// Dark theme
151    pub fn dark() -> Self {
152        Self {
153            name: "Dark".to_string(),
154            background_color: "#1E1E1E".to_string(),
155            node: NodeTheme {
156                fill_color: "#2D2D30".to_string(),
157                stroke_color: "#3E3E42".to_string(),
158                stroke_width: 1.0,
159                border_radius: 4.0,
160                shadow: ShadowConfig { enabled: true, color: "rgba(0, 0, 0, 0.3)".to_string(), offset_x: 0.0, offset_y: 2.0, blur_radius: 4.0 },
161            },
162            edge: EdgeTheme { color: "#CCCCCC".to_string(), width: 1.5, style: "solid".to_string(), arrow: ArrowConfig { enabled: true, size: 8.0, arrow_type: "triangle".to_string() } },
163            text: TextTheme { font_family: "Arial, sans-serif".to_string(), font_size: 12.0, color: "#CCCCCC".to_string(), font_weight: "normal".to_string() },
164            highlight: HighlightTheme { selected_color: "#0E639C".to_string(), hover_color: "#1177BB".to_string(), error_color: "#F14C4C".to_string(), warning_color: "#FFCC02".to_string() },
165        }
166    }
167
168    /// One Light theme - based on Atom One Light
169    pub fn one_light() -> Self {
170        Self {
171            name: "One Light".to_string(),
172            background_color: "#FAFAFA".to_string(),
173            node: NodeTheme {
174                fill_color: "#FFFFFF".to_string(),
175                stroke_color: "#E1E4E8".to_string(),
176                stroke_width: 1.0,
177                border_radius: 6.0,
178                shadow: ShadowConfig { enabled: true, color: "rgba(149, 157, 165, 0.2)".to_string(), offset_x: 0.0, offset_y: 8.0, blur_radius: 24.0 },
179            },
180            edge: EdgeTheme { color: "#586069".to_string(), width: 1.5, style: "solid".to_string(), arrow: ArrowConfig { enabled: true, size: 8.0, arrow_type: "triangle".to_string() } },
181            text: TextTheme { font_family: "SF Pro Display, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif".to_string(), font_size: 12.0, color: "#24292E".to_string(), font_weight: "400".to_string() },
182            highlight: HighlightTheme { selected_color: "#0366D6".to_string(), hover_color: "#0256CC".to_string(), error_color: "#D73A49".to_string(), warning_color: "#F66A0A".to_string() },
183        }
184    }
185
186    /// One Dark Pro theme - based on Atom One Dark Pro
187    pub fn one_dark_pro() -> Self {
188        Self {
189            name: "One Dark Pro".to_string(),
190            background_color: "#282C34".to_string(),
191            node: NodeTheme {
192                fill_color: "#21252B".to_string(),
193                stroke_color: "#3E4451".to_string(),
194                stroke_width: 1.0,
195                border_radius: 6.0,
196                shadow: ShadowConfig { enabled: true, color: "rgba(0, 0, 0, 0.4)".to_string(), offset_x: 0.0, offset_y: 8.0, blur_radius: 24.0 },
197            },
198            edge: EdgeTheme { color: "#ABB2BF".to_string(), width: 1.5, style: "solid".to_string(), arrow: ArrowConfig { enabled: true, size: 8.0, arrow_type: "triangle".to_string() } },
199            text: TextTheme { font_family: "SF Mono, Monaco, Inconsolata, Roboto Mono, monospace".to_string(), font_size: 12.0, color: "#ABB2BF".to_string(), font_weight: "400".to_string() },
200            highlight: HighlightTheme { selected_color: "#61AFEF".to_string(), hover_color: "#528BFF".to_string(), error_color: "#E06C75".to_string(), warning_color: "#E5C07B".to_string() },
201        }
202    }
203
204    /// GitHub theme
205    pub fn github() -> Self {
206        Self {
207            name: "GitHub".to_string(),
208            background_color: "#FFFFFF".to_string(),
209            node: NodeTheme {
210                fill_color: "#F6F8FA".to_string(),
211                stroke_color: "#D0D7DE".to_string(),
212                stroke_width: 1.0,
213                border_radius: 6.0,
214                shadow: ShadowConfig { enabled: true, color: "rgba(31, 35, 40, 0.04)".to_string(), offset_x: 0.0, offset_y: 1.0, blur_radius: 0.0 },
215            },
216            edge: EdgeTheme { color: "#656D76".to_string(), width: 1.0, style: "solid".to_string(), arrow: ArrowConfig { enabled: true, size: 6.0, arrow_type: "triangle".to_string() } },
217            text: TextTheme { font_family: "-apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif".to_string(), font_size: 12.0, color: "#24292F".to_string(), font_weight: "400".to_string() },
218            highlight: HighlightTheme { selected_color: "#0969DA".to_string(), hover_color: "#0860CA".to_string(), error_color: "#CF222E".to_string(), warning_color: "#9A6700".to_string() },
219        }
220    }
221}