1use std::ops::RangeInclusive;
2
3use crate::context::Context;
4use crate::element::{AccentColor, ElementDecl, ElementKind, ElementMeta, PlotSeries, Value};
5
6pub struct Window<'a> {
8 name: String,
9 ctx: &'a mut Context,
10}
11
12impl<'a> Window<'a> {
13 pub(crate) fn new(name: String, ctx: &'a mut Context) -> Self {
14 Self { name, ctx }
15 }
16
17 fn make_id(&self, label: &str) -> String {
18 format!("{}::{}", self.name, label)
19 }
20
21 pub fn slider(&mut self, label: &str, value: &mut f32, range: RangeInclusive<f32>) {
23 let id = self.make_id(label);
24
25 if let Some(Value::Float(v)) = self.ctx.consume_edit(&id) {
26 *value = v as f32;
27 }
28
29 self.ctx.declare(ElementDecl {
30 id,
31 kind: ElementKind::Slider,
32 label: label.to_string(),
33 value: Value::Float(*value as f64),
34 meta: ElementMeta {
35 min: Some(*range.start() as f64),
36 max: Some(*range.end() as f64),
37 step: Some(0.01),
38 ..Default::default()
39 },
40 window: self.name.clone(),
41 });
42 }
43
44 pub fn slider_int(&mut self, label: &str, value: &mut i32, range: RangeInclusive<i32>) {
46 let id = self.make_id(label);
47
48 if let Some(Value::Int(v)) = self.ctx.consume_edit(&id) {
49 *value = v as i32;
50 }
51
52 self.ctx.declare(ElementDecl {
53 id,
54 kind: ElementKind::Slider,
55 label: label.to_string(),
56 value: Value::Int(*value as i64),
57 meta: ElementMeta {
58 min: Some(*range.start() as f64),
59 max: Some(*range.end() as f64),
60 step: Some(1.0),
61 ..Default::default()
62 },
63 window: self.name.clone(),
64 });
65 }
66
67 pub fn checkbox(&mut self, label: &str, value: &mut bool) {
69 let id = self.make_id(label);
70
71 if let Some(Value::Bool(v)) = self.ctx.consume_edit(&id) {
72 *value = v;
73 }
74
75 self.ctx.declare(ElementDecl {
76 id,
77 kind: ElementKind::Checkbox,
78 label: label.to_string(),
79 value: Value::Bool(*value),
80 meta: ElementMeta::default(),
81 window: self.name.clone(),
82 });
83 }
84
85 pub fn color_picker(&mut self, label: &str, value: &mut [f32; 3]) {
87 let id = self.make_id(label);
88
89 if let Some(Value::Color3(c)) = self.ctx.consume_edit(&id) {
90 *value = c;
91 }
92
93 self.ctx.declare(ElementDecl {
94 id,
95 kind: ElementKind::ColorPicker3,
96 label: label.to_string(),
97 value: Value::Color3(*value),
98 meta: ElementMeta::default(),
99 window: self.name.clone(),
100 });
101 }
102
103 pub fn color_picker4(&mut self, label: &str, value: &mut [f32; 4]) {
105 let id = self.make_id(label);
106
107 if let Some(Value::Color4(c)) = self.ctx.consume_edit(&id) {
108 *value = c;
109 }
110
111 self.ctx.declare(ElementDecl {
112 id,
113 kind: ElementKind::ColorPicker4,
114 label: label.to_string(),
115 value: Value::Color4(*value),
116 meta: ElementMeta::default(),
117 window: self.name.clone(),
118 });
119 }
120
121 pub fn text_input(&mut self, label: &str, value: &mut String) {
123 let id = self.make_id(label);
124
125 if let Some(Value::String(s)) = self.ctx.consume_edit(&id) {
126 *value = s;
127 }
128
129 self.ctx.declare(ElementDecl {
130 id,
131 kind: ElementKind::TextInput,
132 label: label.to_string(),
133 value: Value::String(value.clone()),
134 meta: ElementMeta::default(),
135 window: self.name.clone(),
136 });
137 }
138
139 pub fn dropdown(&mut self, label: &str, selected: &mut usize, options: &[&str]) {
141 let id = self.make_id(label);
142
143 if let Some(Value::Enum {
144 selected: s,
145 options: _,
146 }) = self.ctx.consume_edit(&id)
147 {
148 *selected = s;
149 }
150
151 self.ctx.declare(ElementDecl {
152 id,
153 kind: ElementKind::Dropdown,
154 label: label.to_string(),
155 value: Value::Enum {
156 selected: *selected,
157 options: options.iter().map(|s| s.to_string()).collect(),
158 },
159 meta: ElementMeta::default(),
160 window: self.name.clone(),
161 });
162 }
163
164 pub fn button(&mut self, label: &str) -> bool {
166 let id = self.make_id(label);
167
168 let clicked = matches!(self.ctx.consume_edit(&id), Some(Value::Button(true)));
169
170 self.ctx.declare(ElementDecl {
171 id,
172 kind: ElementKind::Button,
173 label: label.to_string(),
174 value: Value::Button(false),
175 meta: ElementMeta::default(),
176 window: self.name.clone(),
177 });
178
179 clicked
180 }
181
182 pub fn label(&mut self, text: &str) {
184 let id = self.make_id(text);
185
186 self.ctx.declare(ElementDecl {
187 id,
188 kind: ElementKind::Label,
189 label: text.to_string(),
190 value: Value::String(text.to_string()),
191 meta: ElementMeta::default(),
192 window: self.name.clone(),
193 });
194 }
195
196 pub fn separator(&mut self) {
198 let id = format!("{}::__sep_{}", self.name, self.ctx.current_frame_len());
199
200 self.ctx.declare(ElementDecl {
201 id,
202 kind: ElementKind::Separator,
203 label: String::new(),
204 value: Value::Bool(false),
205 meta: ElementMeta::default(),
206 window: self.name.clone(),
207 });
208 }
209
210 pub fn section(&mut self, title: &str) {
212 let id = format!("{}::__sec_{}", self.name, self.ctx.current_frame_len());
213
214 self.ctx.declare(ElementDecl {
215 id,
216 kind: ElementKind::Section,
217 label: title.to_string(),
218 value: Value::String(title.to_string()),
219 meta: ElementMeta::default(),
220 window: self.name.clone(),
221 });
222 }
223
224 pub fn progress_bar(&mut self, label: &str, value: f64, accent: AccentColor) {
232 let id = self.make_id(label);
233 let clamped = value.clamp(0.0, 1.0);
234
235 self.ctx.declare(ElementDecl {
236 id,
237 kind: ElementKind::ProgressBar,
238 label: label.to_string(),
239 value: Value::Progress(clamped),
240 meta: ElementMeta {
241 accent: Some(accent.as_str().to_string()),
242 ..Default::default()
243 },
244 window: self.name.clone(),
245 });
246 }
247
248 pub fn progress_bar_with_subtitle(
250 &mut self,
251 label: &str,
252 value: f64,
253 accent: AccentColor,
254 subtitle: &str,
255 ) {
256 let id = self.make_id(label);
257 let clamped = value.clamp(0.0, 1.0);
258
259 self.ctx.declare(ElementDecl {
260 id,
261 kind: ElementKind::ProgressBar,
262 label: label.to_string(),
263 value: Value::Progress(clamped),
264 meta: ElementMeta {
265 accent: Some(accent.as_str().to_string()),
266 subtitle: Some(subtitle.to_string()),
267 ..Default::default()
268 },
269 window: self.name.clone(),
270 });
271 }
272
273 pub fn stat(&mut self, label: &str, value: &str, subvalue: Option<&str>, accent: AccentColor) {
280 let id = self.make_id(label);
281
282 self.ctx.declare(ElementDecl {
283 id,
284 kind: ElementKind::Stat,
285 label: label.to_string(),
286 value: Value::StatValue {
287 value: value.to_string(),
288 subvalue: subvalue.map(|s| s.to_string()),
289 },
290 meta: ElementMeta {
291 accent: Some(accent.as_str().to_string()),
292 ..Default::default()
293 },
294 window: self.name.clone(),
295 });
296 }
297
298 pub fn status(
305 &mut self,
306 label: &str,
307 active: bool,
308 active_text: Option<&str>,
309 inactive_text: Option<&str>,
310 active_color: AccentColor,
311 inactive_color: AccentColor,
312 ) {
313 let id = self.make_id(label);
314
315 self.ctx.declare(ElementDecl {
316 id,
317 kind: ElementKind::Status,
318 label: label.to_string(),
319 value: Value::StatusValue {
320 active,
321 active_text: active_text.map(|s| s.to_string()),
322 inactive_text: inactive_text.map(|s| s.to_string()),
323 active_color: Some(active_color.as_str().to_string()),
324 inactive_color: Some(inactive_color.as_str().to_string()),
325 },
326 meta: ElementMeta::default(),
327 window: self.name.clone(),
328 });
329 }
330
331 pub fn mini_chart(&mut self, label: &str, values: &[f32], unit: Option<&str>, accent: AccentColor) {
339 let id = self.make_id(label);
340 let current = values.last().copied();
341
342 self.ctx.declare(ElementDecl {
343 id,
344 kind: ElementKind::MiniChart,
345 label: label.to_string(),
346 value: Value::ChartValue {
347 values: values.to_vec(),
348 current,
349 unit: unit.map(|s| s.to_string()),
350 },
351 meta: ElementMeta {
352 accent: Some(accent.as_str().to_string()),
353 ..Default::default()
354 },
355 window: self.name.clone(),
356 });
357 }
358
359 pub fn set_accent(&mut self, accent: AccentColor) {
362 let id = format!("{}::__accent_{}", self.name, accent.as_str());
365 self.ctx.declare(ElementDecl {
366 id,
367 kind: ElementKind::Label,
368 label: String::new(),
369 value: Value::String(String::new()),
370 meta: ElementMeta {
371 accent: Some(accent.as_str().to_string()),
372 ..Default::default()
373 },
374 window: self.name.clone(),
375 });
376 }
377
378 pub fn grid<F>(&mut self, cols: usize, f: F)
391 where
392 F: FnOnce(&mut Grid<'_, 'a>),
393 {
394 let grid_id = format!("{}::__grid_{}", self.name, self.ctx.current_frame_len());
395 let mut grid = Grid::new(&grid_id, self, cols);
396 f(&mut grid);
397 grid.finish();
398 }
399
400 pub fn plot(
408 &mut self,
409 label: &str,
410 series: &[(&str, &[f32], AccentColor)],
411 x_label: Option<&str>,
412 y_label: Option<&str>,
413 ) {
414 let id = self.make_id(label);
415
416 let plot_series: Vec<PlotSeries> = series
417 .iter()
418 .map(|(name, values, color)| PlotSeries {
419 name: name.to_string(),
420 values: values.to_vec(),
421 color: color.as_str().to_string(),
422 })
423 .collect();
424
425 self.ctx.declare(ElementDecl {
426 id,
427 kind: ElementKind::Plot,
428 label: label.to_string(),
429 value: Value::PlotValue {
430 series: plot_series,
431 x_label: x_label.map(|s| s.to_string()),
432 y_label: y_label.map(|s| s.to_string()),
433 },
434 meta: ElementMeta::default(),
435 window: self.name.clone(),
436 });
437 }
438}
439
440pub struct Grid<'a, 'ctx> {
442 id: String,
443 window: &'a mut Window<'ctx>,
444 cols: usize,
445 children: Vec<String>,
446}
447
448impl<'a, 'ctx> Grid<'a, 'ctx> {
449 fn new(id: &str, window: &'a mut Window<'ctx>, cols: usize) -> Self {
450 Self {
451 id: id.to_string(),
452 window,
453 cols,
454 children: Vec::new(),
455 }
456 }
457
458 fn record_child(&mut self, id: String) {
459 self.children.push(id);
460 }
461
462 fn finish(self) {
463 self.window.ctx.declare(ElementDecl {
465 id: self.id,
466 kind: ElementKind::Grid,
467 label: String::new(),
468 value: Value::GridValue {
469 cols: self.cols,
470 children: self.children,
471 },
472 meta: ElementMeta::default(),
473 window: self.window.name.clone(),
474 });
475 }
476
477 fn make_id(&self, label: &str) -> String {
478 format!("{}::{}", self.id, label)
479 }
480
481 pub fn stat(&mut self, label: &str, value: &str, subvalue: Option<&str>, accent: AccentColor) {
483 let id = self.make_id(label);
484
485 self.record_child(id.clone());
486 self.window.ctx.declare(ElementDecl {
487 id,
488 kind: ElementKind::Stat,
489 label: label.to_string(),
490 value: Value::StatValue {
491 value: value.to_string(),
492 subvalue: subvalue.map(|s| s.to_string()),
493 },
494 meta: ElementMeta {
495 accent: Some(accent.as_str().to_string()),
496 ..Default::default()
497 },
498 window: self.window.name.clone(),
499 });
500 }
501
502 pub fn mini_chart(&mut self, label: &str, values: &[f32], unit: Option<&str>, accent: AccentColor) {
504 let id = self.make_id(label);
505 let current = values.last().copied();
506
507 self.record_child(id.clone());
508 self.window.ctx.declare(ElementDecl {
509 id,
510 kind: ElementKind::MiniChart,
511 label: label.to_string(),
512 value: Value::ChartValue {
513 values: values.to_vec(),
514 current,
515 unit: unit.map(|s| s.to_string()),
516 },
517 meta: ElementMeta {
518 accent: Some(accent.as_str().to_string()),
519 ..Default::default()
520 },
521 window: self.window.name.clone(),
522 });
523 }
524
525 pub fn progress_bar(&mut self, label: &str, value: f64, accent: AccentColor) {
527 let id = self.make_id(label);
528 let clamped = value.clamp(0.0, 1.0);
529
530 self.record_child(id.clone());
531 self.window.ctx.declare(ElementDecl {
532 id,
533 kind: ElementKind::ProgressBar,
534 label: label.to_string(),
535 value: Value::Progress(clamped),
536 meta: ElementMeta {
537 accent: Some(accent.as_str().to_string()),
538 ..Default::default()
539 },
540 window: self.window.name.clone(),
541 });
542 }
543
544 pub fn status(
546 &mut self,
547 label: &str,
548 active: bool,
549 active_text: Option<&str>,
550 inactive_text: Option<&str>,
551 active_color: AccentColor,
552 inactive_color: AccentColor,
553 ) {
554 let id = self.make_id(label);
555
556 self.record_child(id.clone());
557 self.window.ctx.declare(ElementDecl {
558 id,
559 kind: ElementKind::Status,
560 label: label.to_string(),
561 value: Value::StatusValue {
562 active,
563 active_text: active_text.map(|s| s.to_string()),
564 inactive_text: inactive_text.map(|s| s.to_string()),
565 active_color: Some(active_color.as_str().to_string()),
566 inactive_color: Some(inactive_color.as_str().to_string()),
567 },
568 meta: ElementMeta::default(),
569 window: self.window.name.clone(),
570 });
571 }
572}