1use std::ops::RangeInclusive;
2use std::sync::Arc;
3
4use crate::context::Context;
5use crate::element::{AccentColor, ElementDecl, ElementKind, ElementMeta, PlotSeries, Value};
6
7fn build_slider(
11 id: String,
12 window: Arc<str>,
13 label: &str,
14 value: f32,
15 range: &RangeInclusive<f32>,
16) -> ElementDecl {
17 ElementDecl {
18 id,
19 kind: ElementKind::Slider,
20 label: label.to_string(),
21 value: Value::Float(value as f64),
22 meta: ElementMeta {
23 min: Some(*range.start() as f64),
24 max: Some(*range.end() as f64),
25 step: Some(0.01),
26 ..Default::default()
27 },
28 window,
29 }
30}
31
32fn build_slider_int(
33 id: String,
34 window: Arc<str>,
35 label: &str,
36 value: i32,
37 range: &RangeInclusive<i32>,
38) -> ElementDecl {
39 ElementDecl {
40 id,
41 kind: ElementKind::Slider,
42 label: label.to_string(),
43 value: Value::Int(value as i64),
44 meta: ElementMeta {
45 min: Some(*range.start() as f64),
46 max: Some(*range.end() as f64),
47 step: Some(1.0),
48 ..Default::default()
49 },
50 window,
51 }
52}
53
54fn build_checkbox(id: String, window: Arc<str>, label: &str, value: bool) -> ElementDecl {
55 ElementDecl {
56 id,
57 kind: ElementKind::Checkbox,
58 label: label.to_string(),
59 value: Value::Bool(value),
60 meta: ElementMeta::default(),
61 window,
62 }
63}
64
65fn build_color3(id: String, window: Arc<str>, label: &str, value: [f32; 3]) -> ElementDecl {
66 ElementDecl {
67 id,
68 kind: ElementKind::ColorPicker3,
69 label: label.to_string(),
70 value: Value::Color3(value),
71 meta: ElementMeta::default(),
72 window,
73 }
74}
75
76fn build_color4(id: String, window: Arc<str>, label: &str, value: [f32; 4]) -> ElementDecl {
77 ElementDecl {
78 id,
79 kind: ElementKind::ColorPicker4,
80 label: label.to_string(),
81 value: Value::Color4(value),
82 meta: ElementMeta::default(),
83 window,
84 }
85}
86
87fn build_text_input(id: String, window: Arc<str>, label: &str, value: &str) -> ElementDecl {
88 ElementDecl {
89 id,
90 kind: ElementKind::TextInput,
91 label: label.to_string(),
92 value: Value::String(value.to_string()),
93 meta: ElementMeta::default(),
94 window,
95 }
96}
97
98fn build_dropdown(
99 id: String,
100 window: Arc<str>,
101 label: &str,
102 selected: usize,
103 options: &[&str],
104) -> ElementDecl {
105 ElementDecl {
106 id,
107 kind: ElementKind::Dropdown,
108 label: label.to_string(),
109 value: Value::Enum {
110 selected,
111 options: options.iter().map(|s| s.to_string()).collect(),
112 },
113 meta: ElementMeta::default(),
114 window,
115 }
116}
117
118fn build_button(id: String, window: Arc<str>, label: &str) -> ElementDecl {
119 ElementDecl {
120 id,
121 kind: ElementKind::Button,
122 label: label.to_string(),
123 value: Value::Button(false),
124 meta: ElementMeta::default(),
125 window,
126 }
127}
128
129fn build_label(id: String, window: Arc<str>, text: &str) -> ElementDecl {
130 ElementDecl {
131 id,
132 kind: ElementKind::Label,
133 label: text.to_string(),
134 value: Value::String(text.to_string()),
135 meta: ElementMeta::default(),
136 window,
137 }
138}
139
140fn build_progress_bar(
141 id: String,
142 window: Arc<str>,
143 label: &str,
144 value: f64,
145 accent: AccentColor,
146 subtitle: Option<&str>,
147) -> ElementDecl {
148 ElementDecl {
149 id,
150 kind: ElementKind::ProgressBar,
151 label: label.to_string(),
152 value: Value::Progress(value.clamp(0.0, 1.0)),
153 meta: ElementMeta {
154 accent: Some(accent.as_str().to_string()),
155 subtitle: subtitle.map(|s| s.to_string()),
156 ..Default::default()
157 },
158 window,
159 }
160}
161
162fn build_stat(
163 id: String,
164 window: Arc<str>,
165 label: &str,
166 value: &str,
167 subvalue: Option<&str>,
168 accent: AccentColor,
169) -> ElementDecl {
170 ElementDecl {
171 id,
172 kind: ElementKind::Stat,
173 label: label.to_string(),
174 value: Value::StatValue {
175 value: value.to_string(),
176 subvalue: subvalue.map(|s| s.to_string()),
177 },
178 meta: ElementMeta {
179 accent: Some(accent.as_str().to_string()),
180 ..Default::default()
181 },
182 window,
183 }
184}
185
186fn build_status(
187 id: String,
188 window: Arc<str>,
189 label: &str,
190 active: bool,
191 active_text: Option<&str>,
192 inactive_text: Option<&str>,
193 active_color: AccentColor,
194 inactive_color: AccentColor,
195) -> ElementDecl {
196 ElementDecl {
197 id,
198 kind: ElementKind::Status,
199 label: label.to_string(),
200 value: Value::StatusValue {
201 active,
202 active_text: active_text.map(|s| s.to_string()),
203 inactive_text: inactive_text.map(|s| s.to_string()),
204 active_color: Some(active_color.as_str().to_string()),
205 inactive_color: Some(inactive_color.as_str().to_string()),
206 },
207 meta: ElementMeta::default(),
208 window,
209 }
210}
211
212fn build_mini_chart(
213 id: String,
214 window: Arc<str>,
215 label: &str,
216 values: &[f32],
217 unit: Option<&str>,
218 accent: AccentColor,
219) -> ElementDecl {
220 ElementDecl {
221 id,
222 kind: ElementKind::MiniChart,
223 label: label.to_string(),
224 value: Value::ChartValue {
225 values: values.to_vec(),
226 current: values.last().copied(),
227 unit: unit.map(|s| s.to_string()),
228 },
229 meta: ElementMeta {
230 accent: Some(accent.as_str().to_string()),
231 ..Default::default()
232 },
233 window,
234 }
235}
236
237fn build_plot(
238 id: String,
239 window: Arc<str>,
240 label: &str,
241 series: &[(&str, &[f32], AccentColor)],
242 x_label: Option<&str>,
243 y_label: Option<&str>,
244) -> ElementDecl {
245 let plot_series: Vec<PlotSeries> = series
246 .iter()
247 .map(|(name, values, color)| PlotSeries {
248 name: name.to_string(),
249 values: values.to_vec(),
250 color: color.as_str().to_string(),
251 })
252 .collect();
253
254 ElementDecl {
255 id,
256 kind: ElementKind::Plot,
257 label: label.to_string(),
258 value: Value::PlotValue {
259 series: plot_series,
260 x_label: x_label.map(|s| s.to_string()),
261 y_label: y_label.map(|s| s.to_string()),
262 },
263 meta: ElementMeta::default(),
264 window,
265 }
266}
267
268pub struct Window<'a> {
272 name: Arc<str>,
273 ctx: &'a mut Context,
274}
275
276impl<'a> Window<'a> {
277 pub(crate) fn new(name: String, ctx: &'a mut Context) -> Self {
278 Self {
279 name: Arc::from(name.as_str()),
280 ctx,
281 }
282 }
283
284 fn make_id(&self, label: &str) -> String {
285 format!("{}::{}", self.name, label)
286 }
287
288 pub fn slider(&mut self, label: &str, value: &mut f32, range: RangeInclusive<f32>) {
290 let id = self.make_id(label);
291 if let Some(Value::Float(v)) = self.ctx.consume_edit(&id) {
292 *value = v as f32;
293 }
294 self.ctx
295 .declare(build_slider(id, self.name.clone(), label, *value, &range));
296 }
297
298 pub fn slider_int(&mut self, label: &str, value: &mut i32, range: RangeInclusive<i32>) {
300 let id = self.make_id(label);
301 if let Some(Value::Int(v)) = self.ctx.consume_edit(&id) {
302 *value = v as i32;
303 }
304 self.ctx
305 .declare(build_slider_int(id, self.name.clone(), label, *value, &range));
306 }
307
308 pub fn checkbox(&mut self, label: &str, value: &mut bool) {
310 let id = self.make_id(label);
311 if let Some(Value::Bool(v)) = self.ctx.consume_edit(&id) {
312 *value = v;
313 }
314 self.ctx
315 .declare(build_checkbox(id, self.name.clone(), label, *value));
316 }
317
318 pub fn color_picker(&mut self, label: &str, value: &mut [f32; 3]) {
320 let id = self.make_id(label);
321 if let Some(Value::Color3(c)) = self.ctx.consume_edit(&id) {
322 *value = c;
323 }
324 self.ctx
325 .declare(build_color3(id, self.name.clone(), label, *value));
326 }
327
328 pub fn color_picker4(&mut self, label: &str, value: &mut [f32; 4]) {
330 let id = self.make_id(label);
331 if let Some(Value::Color4(c)) = self.ctx.consume_edit(&id) {
332 *value = c;
333 }
334 self.ctx
335 .declare(build_color4(id, self.name.clone(), label, *value));
336 }
337
338 pub fn text_input(&mut self, label: &str, value: &mut String) {
340 let id = self.make_id(label);
341 if let Some(Value::String(s)) = self.ctx.consume_edit(&id) {
342 *value = s;
343 }
344 self.ctx
345 .declare(build_text_input(id, self.name.clone(), label, value));
346 }
347
348 pub fn dropdown(&mut self, label: &str, selected: &mut usize, options: &[&str]) {
350 let id = self.make_id(label);
351 if let Some(Value::Enum { selected: s, .. }) = self.ctx.consume_edit(&id) {
352 *selected = s;
353 }
354 self.ctx
355 .declare(build_dropdown(id, self.name.clone(), label, *selected, options));
356 }
357
358 pub fn button(&mut self, label: &str) -> bool {
360 let id = self.make_id(label);
361 let clicked = matches!(self.ctx.consume_edit(&id), Some(Value::Button(true)));
362 self.ctx
363 .declare(build_button(id, self.name.clone(), label));
364 clicked
365 }
366
367 pub fn label(&mut self, text: &str) {
369 let id = self.make_id(text);
370 self.ctx
371 .declare(build_label(id, self.name.clone(), text));
372 }
373
374 pub fn separator(&mut self) {
376 let id = format!("{}::__sep_{}", self.name, self.ctx.current_frame_len());
377 self.ctx.declare(ElementDecl {
378 id,
379 kind: ElementKind::Separator,
380 label: String::new(),
381 value: Value::Bool(false),
382 meta: ElementMeta::default(),
383 window: self.name.clone(),
384 });
385 }
386
387 pub fn section(&mut self, title: &str) {
389 let id = format!("{}::__sec_{}", self.name, self.ctx.current_frame_len());
390 self.ctx.declare(ElementDecl {
391 id,
392 kind: ElementKind::Section,
393 label: title.to_string(),
394 value: Value::String(title.to_string()),
395 meta: ElementMeta::default(),
396 window: self.name.clone(),
397 });
398 }
399
400 pub fn progress_bar(&mut self, label: &str, value: f64, accent: AccentColor) {
402 let id = self.make_id(label);
403 self.ctx
404 .declare(build_progress_bar(id, self.name.clone(), label, value, accent, None));
405 }
406
407 pub fn progress_bar_with_subtitle(
409 &mut self,
410 label: &str,
411 value: f64,
412 accent: AccentColor,
413 subtitle: &str,
414 ) {
415 let id = self.make_id(label);
416 self.ctx.declare(build_progress_bar(
417 id,
418 self.name.clone(),
419 label,
420 value,
421 accent,
422 Some(subtitle),
423 ));
424 }
425
426 pub fn stat(&mut self, label: &str, value: &str, subvalue: Option<&str>, accent: AccentColor) {
428 let id = self.make_id(label);
429 self.ctx
430 .declare(build_stat(id, self.name.clone(), label, value, subvalue, accent));
431 }
432
433 pub fn status(
435 &mut self,
436 label: &str,
437 active: bool,
438 active_text: Option<&str>,
439 inactive_text: Option<&str>,
440 active_color: AccentColor,
441 inactive_color: AccentColor,
442 ) {
443 let id = self.make_id(label);
444 self.ctx.declare(build_status(
445 id,
446 self.name.clone(),
447 label,
448 active,
449 active_text,
450 inactive_text,
451 active_color,
452 inactive_color,
453 ));
454 }
455
456 pub fn mini_chart(
458 &mut self,
459 label: &str,
460 values: &[f32],
461 unit: Option<&str>,
462 accent: AccentColor,
463 ) {
464 let id = self.make_id(label);
465 self.ctx
466 .declare(build_mini_chart(id, self.name.clone(), label, values, unit, accent));
467 }
468
469 pub fn set_accent(&mut self, accent: AccentColor) {
472 let id = format!("{}::__accent_{}", self.name, accent.as_str());
473 self.ctx.declare(ElementDecl {
474 id,
475 kind: ElementKind::Label,
476 label: String::new(),
477 value: Value::String(String::new()),
478 meta: ElementMeta {
479 accent: Some(accent.as_str().to_string()),
480 ..Default::default()
481 },
482 window: self.name.clone(),
483 });
484 }
485
486 pub fn grid<F>(&mut self, cols: usize, f: F)
489 where
490 F: FnOnce(&mut Grid<'_, 'a>),
491 {
492 let grid_id = format!("{}::__grid_{}", self.name, self.ctx.current_frame_len());
493 let mut grid = Grid::new(&grid_id, self, cols);
494 f(&mut grid);
495 grid.finish();
496 }
497
498 pub fn plot(
500 &mut self,
501 label: &str,
502 series: &[(&str, &[f32], AccentColor)],
503 x_label: Option<&str>,
504 y_label: Option<&str>,
505 ) {
506 let id = self.make_id(label);
507 self.ctx
508 .declare(build_plot(id, self.name.clone(), label, series, x_label, y_label));
509 }
510}
511
512pub struct Grid<'a, 'ctx> {
516 id: String,
517 window: &'a mut Window<'ctx>,
518 cols: usize,
519 children: Vec<String>,
520}
521
522impl<'a, 'ctx> Grid<'a, 'ctx> {
523 fn new(id: &str, window: &'a mut Window<'ctx>, cols: usize) -> Self {
524 Self {
525 id: id.to_string(),
526 window,
527 cols,
528 children: Vec::new(),
529 }
530 }
531
532 fn record_child(&mut self, id: String) {
533 self.children.push(id);
534 }
535
536 fn finish(self) {
537 self.window.ctx.declare(ElementDecl {
538 id: self.id,
539 kind: ElementKind::Grid,
540 label: String::new(),
541 value: Value::GridValue {
542 cols: self.cols,
543 children: self.children,
544 },
545 meta: ElementMeta::default(),
546 window: self.window.name.clone(),
547 });
548 }
549
550 fn make_id(&self, label: &str) -> String {
551 format!("{}::{}", self.id, label)
552 }
553
554 pub fn slider(&mut self, label: &str, value: &mut f32, range: RangeInclusive<f32>) {
558 let id = self.make_id(label);
559 if let Some(Value::Float(v)) = self.window.ctx.consume_edit(&id) {
560 *value = v as f32;
561 }
562 self.record_child(id.clone());
563 self.window
564 .ctx
565 .declare(build_slider(id, self.window.name.clone(), label, *value, &range));
566 }
567
568 pub fn slider_int(&mut self, label: &str, value: &mut i32, range: RangeInclusive<i32>) {
570 let id = self.make_id(label);
571 if let Some(Value::Int(v)) = self.window.ctx.consume_edit(&id) {
572 *value = v as i32;
573 }
574 self.record_child(id.clone());
575 self.window.ctx.declare(build_slider_int(
576 id,
577 self.window.name.clone(),
578 label,
579 *value,
580 &range,
581 ));
582 }
583
584 pub fn checkbox(&mut self, label: &str, value: &mut bool) {
586 let id = self.make_id(label);
587 if let Some(Value::Bool(v)) = self.window.ctx.consume_edit(&id) {
588 *value = v;
589 }
590 self.record_child(id.clone());
591 self.window
592 .ctx
593 .declare(build_checkbox(id, self.window.name.clone(), label, *value));
594 }
595
596 pub fn color_picker(&mut self, label: &str, value: &mut [f32; 3]) {
598 let id = self.make_id(label);
599 if let Some(Value::Color3(c)) = self.window.ctx.consume_edit(&id) {
600 *value = c;
601 }
602 self.record_child(id.clone());
603 self.window
604 .ctx
605 .declare(build_color3(id, self.window.name.clone(), label, *value));
606 }
607
608 pub fn color_picker4(&mut self, label: &str, value: &mut [f32; 4]) {
610 let id = self.make_id(label);
611 if let Some(Value::Color4(c)) = self.window.ctx.consume_edit(&id) {
612 *value = c;
613 }
614 self.record_child(id.clone());
615 self.window
616 .ctx
617 .declare(build_color4(id, self.window.name.clone(), label, *value));
618 }
619
620 pub fn text_input(&mut self, label: &str, value: &mut String) {
622 let id = self.make_id(label);
623 if let Some(Value::String(s)) = self.window.ctx.consume_edit(&id) {
624 *value = s;
625 }
626 self.record_child(id.clone());
627 self.window
628 .ctx
629 .declare(build_text_input(id, self.window.name.clone(), label, value));
630 }
631
632 pub fn dropdown(&mut self, label: &str, selected: &mut usize, options: &[&str]) {
634 let id = self.make_id(label);
635 if let Some(Value::Enum { selected: s, .. }) = self.window.ctx.consume_edit(&id) {
636 *selected = s;
637 }
638 self.record_child(id.clone());
639 self.window.ctx.declare(build_dropdown(
640 id,
641 self.window.name.clone(),
642 label,
643 *selected,
644 options,
645 ));
646 }
647
648 pub fn button(&mut self, label: &str) -> bool {
650 let id = self.make_id(label);
651 let clicked = matches!(self.window.ctx.consume_edit(&id), Some(Value::Button(true)));
652 self.record_child(id.clone());
653 self.window
654 .ctx
655 .declare(build_button(id, self.window.name.clone(), label));
656 clicked
657 }
658
659 pub fn label(&mut self, text: &str) {
663 let id = self.make_id(text);
664 self.record_child(id.clone());
665 self.window
666 .ctx
667 .declare(build_label(id, self.window.name.clone(), text));
668 }
669
670 pub fn progress_bar(&mut self, label: &str, value: f64, accent: AccentColor) {
672 let id = self.make_id(label);
673 self.record_child(id.clone());
674 self.window
675 .ctx
676 .declare(build_progress_bar(id, self.window.name.clone(), label, value, accent, None));
677 }
678
679 pub fn progress_bar_with_subtitle(
681 &mut self,
682 label: &str,
683 value: f64,
684 accent: AccentColor,
685 subtitle: &str,
686 ) {
687 let id = self.make_id(label);
688 self.record_child(id.clone());
689 self.window.ctx.declare(build_progress_bar(
690 id,
691 self.window.name.clone(),
692 label,
693 value,
694 accent,
695 Some(subtitle),
696 ));
697 }
698
699 pub fn stat(&mut self, label: &str, value: &str, subvalue: Option<&str>, accent: AccentColor) {
701 let id = self.make_id(label);
702 self.record_child(id.clone());
703 self.window
704 .ctx
705 .declare(build_stat(id, self.window.name.clone(), label, value, subvalue, accent));
706 }
707
708 pub fn status(
710 &mut self,
711 label: &str,
712 active: bool,
713 active_text: Option<&str>,
714 inactive_text: Option<&str>,
715 active_color: AccentColor,
716 inactive_color: AccentColor,
717 ) {
718 let id = self.make_id(label);
719 self.record_child(id.clone());
720 self.window.ctx.declare(build_status(
721 id,
722 self.window.name.clone(),
723 label,
724 active,
725 active_text,
726 inactive_text,
727 active_color,
728 inactive_color,
729 ));
730 }
731
732 pub fn mini_chart(
734 &mut self,
735 label: &str,
736 values: &[f32],
737 unit: Option<&str>,
738 accent: AccentColor,
739 ) {
740 let id = self.make_id(label);
741 self.record_child(id.clone());
742 self.window.ctx.declare(build_mini_chart(
743 id,
744 self.window.name.clone(),
745 label,
746 values,
747 unit,
748 accent,
749 ));
750 }
751
752 pub fn plot(
754 &mut self,
755 label: &str,
756 series: &[(&str, &[f32], AccentColor)],
757 x_label: Option<&str>,
758 y_label: Option<&str>,
759 ) {
760 let id = self.make_id(label);
761 self.record_child(id.clone());
762 self.window.ctx.declare(build_plot(
763 id,
764 self.window.name.clone(),
765 label,
766 series,
767 x_label,
768 y_label,
769 ));
770 }
771}