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