1use crate::{Painter, Rect};
2use serde::Serialize;
3use std::sync::Arc;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
7pub struct Color(pub u32);
8impl Color {
9 pub const fn rgb(rgb: u32) -> Self {
10 Self(rgb & 0xffffff)
11 }
12 pub fn mix(self, other: Self, amount: f32) -> Self {
13 let t = amount.clamp(0.0, 1.0);
14 let channel = |shift: u32| {
15 (((self.0 >> shift) & 255) as f32 * (1.0 - t) + ((other.0 >> shift) & 255) as f32 * t)
16 as u32
17 };
18 Self(channel(16) << 16 | channel(8) << 8 | channel(0))
19 }
20}
21#[derive(Clone, Copy, Debug)]
23pub struct Theme {
24 pub background: Color,
25 pub panel: Color,
26 pub elevated: Color,
27 pub border: Color,
28 pub text: Color,
29 pub muted: Color,
30 pub accent: Color,
31 pub on_accent: Color,
32 pub success: Color,
33}
34impl Theme {
35 pub const DARK: Self = Self {
36 background: Color(0x101316),
37 panel: Color(0x191d21),
38 elevated: Color(0x242a30),
39 border: Color(0x343c43),
40 text: Color(0xf2f3ef),
41 muted: Color(0x9ca8b2),
42 accent: Color(0xff6548),
43 on_accent: Color(0x17120f),
44 success: Color(0x8cdbb6),
45 };
46 pub const LIGHT: Self = Self {
47 background: Color(0xf1f2ef),
48 panel: Color(0xffffff),
49 elevated: Color(0xe6e9e6),
50 border: Color(0xc8ceca),
51 text: Color(0x182025),
52 muted: Color(0x56636c),
53 accent: Color(0xc73e26),
54 on_accent: Color(0xffffff),
55 success: Color(0x19774e),
56 };
57}
58impl Default for Theme {
59 fn default() -> Self {
60 Self::DARK
61 }
62}
63#[derive(Clone, Copy, Debug, Default)]
64pub enum Length {
65 #[default]
66 Auto,
67 Px(f32),
68 Fill,
69}
70#[derive(Clone, Copy, Debug, PartialEq)]
71pub enum Axis {
72 Horizontal,
73 Vertical,
74}
75#[derive(Clone, Debug)]
76pub struct Style {
77 pub width: Length,
78 pub height: Length,
79 pub padding: f32,
80 pub gap: f32,
81 pub background: Option<Color>,
82 pub foreground: Option<Color>,
83 pub border: bool,
84 pub radius: f32,
85 pub text_size: f32,
86 pub bold: bool,
87}
88impl Default for Style {
89 fn default() -> Self {
90 Self {
91 width: Length::Fill,
92 height: Length::Auto,
93 padding: 0.0,
94 gap: 0.0,
95 background: None,
96 foreground: None,
97 border: false,
98 radius: 6.0,
99 text_size: 14.0,
100 bold: false,
101 }
102 }
103}
104pub type PaintFn = Arc<dyn Fn(&mut Painter, Rect, &Theme) + Send + Sync>;
106#[derive(Clone)]
107pub enum Kind {
108 Container {
109 axis: Axis,
110 scroll: bool,
111 },
112 Label,
113 Button,
114 Toggle(bool),
115 Slider {
116 value: f32,
117 min: f32,
118 max: f32,
119 step: f32,
120 },
121 TextInput {
122 value: String,
123 placeholder: String,
124 },
125 Custom {
126 paint: PaintFn,
127 interactive: bool,
128 },
129}
130#[derive(Clone)]
132pub struct Node {
133 pub id: String,
134 pub label: String,
135 pub kind: Kind,
136 pub style: Style,
137 pub enabled: bool,
138 pub children: Vec<Node>,
139}
140impl Node {
141 fn new(id: impl Into<String>, label: impl Into<String>, kind: Kind) -> Self {
142 Self {
143 id: id.into(),
144 label: label.into(),
145 kind,
146 style: Style::default(),
147 enabled: true,
148 children: vec![],
149 }
150 }
151 pub fn column(id: impl Into<String>, children: Vec<Self>) -> Self {
152 Self::container(id, Axis::Vertical, children)
153 }
154 pub fn row(id: impl Into<String>, children: Vec<Self>) -> Self {
155 Self::container(id, Axis::Horizontal, children)
156 }
157 fn container(id: impl Into<String>, axis: Axis, children: Vec<Self>) -> Self {
158 let mut n = Self::new(
159 id,
160 "",
161 Kind::Container {
162 axis,
163 scroll: false,
164 },
165 );
166 n.children = children;
167 n
168 }
169 pub fn scroll(id: impl Into<String>, children: Vec<Self>) -> Self {
171 let mut n = Self::column(id, children);
172 n.kind = Kind::Container {
173 axis: Axis::Vertical,
174 scroll: true,
175 };
176 n
177 }
178 pub fn label(id: impl Into<String>, text: impl Into<String>) -> Self {
179 Self::new(id, text, Kind::Label)
180 }
181 pub fn button(id: impl Into<String>, text: impl Into<String>) -> Self {
182 Self::new(id, text, Kind::Button).height(40.0)
183 }
184 pub fn toggle(id: impl Into<String>, label: impl Into<String>, checked: bool) -> Self {
185 Self::new(id, label, Kind::Toggle(checked)).height(40.0)
186 }
187 pub fn slider(
189 id: impl Into<String>,
190 label: impl Into<String>,
191 value: f32,
192 min: f32,
193 max: f32,
194 step: f32,
195 ) -> Self {
196 Self::new(
197 id,
198 label,
199 Kind::Slider {
200 value,
201 min,
202 max,
203 step,
204 },
205 )
206 .height(54.0)
207 }
208 pub fn text_input(
209 id: impl Into<String>,
210 label: impl Into<String>,
211 value: impl Into<String>,
212 placeholder: impl Into<String>,
213 ) -> Self {
214 Self::new(
215 id,
216 label,
217 Kind::TextInput {
218 value: value.into(),
219 placeholder: placeholder.into(),
220 },
221 )
222 .height(42.0)
223 }
224 pub fn custom(
225 id: impl Into<String>,
226 label: impl Into<String>,
227 interactive: bool,
228 paint: impl Fn(&mut Painter, Rect, &Theme) + Send + Sync + 'static,
229 ) -> Self {
230 Self::new(
231 id,
232 label,
233 Kind::Custom {
234 paint: Arc::new(paint),
235 interactive,
236 },
237 )
238 .height(120.0)
239 }
240 pub fn width(mut self, px: f32) -> Self {
241 self.style.width = Length::Px(px);
242 self
243 }
244 pub fn height(mut self, px: f32) -> Self {
245 self.style.height = Length::Px(px);
246 self
247 }
248 pub fn auto_width(mut self) -> Self {
249 self.style.width = Length::Auto;
250 self
251 }
252 pub fn fill_height(mut self) -> Self {
253 self.style.height = Length::Fill;
254 self
255 }
256 pub fn padding(mut self, px: f32) -> Self {
257 self.style.padding = px;
258 self
259 }
260 pub fn gap(mut self, px: f32) -> Self {
261 self.style.gap = px;
262 self
263 }
264 pub fn background(mut self, color: Color) -> Self {
265 self.style.background = Some(color);
266 self
267 }
268 pub fn color(mut self, color: Color) -> Self {
269 self.style.foreground = Some(color);
270 self
271 }
272 pub fn border(mut self) -> Self {
273 self.style.border = true;
274 self
275 }
276 pub fn radius(mut self, px: f32) -> Self {
277 self.style.radius = px;
278 self
279 }
280 pub fn font_size(mut self, px: f32) -> Self {
281 self.style.text_size = px;
282 self
283 }
284 pub fn bold(mut self) -> Self {
285 self.style.bold = true;
286 self
287 }
288 pub fn disabled(mut self) -> Self {
289 self.enabled = false;
290 self
291 }
292 pub fn focusable(&self) -> bool {
293 self.enabled
294 && matches!(
295 self.kind,
296 Kind::Button
297 | Kind::Toggle(_)
298 | Kind::Slider { .. }
299 | Kind::TextInput { .. }
300 | Kind::Custom {
301 interactive: true,
302 ..
303 }
304 )
305 }
306 pub fn role(&self) -> &'static str {
307 match self.kind {
308 Kind::Container { scroll: true, .. } => "scroll",
309 Kind::Container { .. } => "group",
310 Kind::Label => "label",
311 Kind::Button => "button",
312 Kind::Toggle(_) => "switch",
313 Kind::Slider { .. } => "slider",
314 Kind::TextInput { .. } => "textbox",
315 Kind::Custom { .. } => "custom",
316 }
317 }
318}
319#[derive(Clone, Debug, Serialize, PartialEq)]
321pub struct Action {
322 pub id: String,
323 pub kind: ActionKind,
324}
325#[derive(Clone, Debug, Serialize, PartialEq)]
326#[serde(tag = "type", content = "value", rename_all = "snake_case")]
327pub enum ActionKind {
328 Activate,
329 Toggle(bool),
330 ChangeNumber(f32),
331 ChangeText(String),
332 Submit(String),
333}