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 next_idx: usize,
293}
294
295impl<'a> Window<'a> {
296 pub(crate) fn new(name: String, ctx: &'a mut Context) -> Self {
297 Self {
298 name: Arc::from(name.as_str()),
299 ctx,
300 next_idx: 0,
301 }
302 }
303
304 fn make_id(&mut self, _label: &str) -> String {
305 let id = format!("{}::_{}", self.name, self.next_idx);
306 self.next_idx += 1;
307 id
308 }
309
310 pub fn slider(&mut self, label: &str, value: &mut f32, range: RangeInclusive<f32>) -> Response {
312 let id = self.make_id(label);
313 let (clicked, changed) = if let Some(Value::Float(v)) = self.ctx.consume_edit(&id) {
314 let new = v as f32;
315 let changed = *value != new;
316 *value = new;
317 (true, changed)
318 } else {
319 (false, false)
320 };
321 self.ctx
322 .declare(build_slider(id, self.name.clone(), label, *value, &range));
323 Response { clicked, changed }
324 }
325
326 pub fn slider_int(&mut self, label: &str, value: &mut i32, range: RangeInclusive<i32>) -> Response {
328 let id = self.make_id(label);
329 let (clicked, changed) = if let Some(Value::Int(v)) = self.ctx.consume_edit(&id) {
330 let new = v as i32;
331 let changed = *value != new;
332 *value = new;
333 (true, changed)
334 } else {
335 (false, false)
336 };
337 self.ctx
338 .declare(build_slider_int(id, self.name.clone(), label, *value, &range));
339 Response { clicked, changed }
340 }
341
342 pub fn checkbox(&mut self, label: &str, value: &mut bool) -> Response {
344 let id = self.make_id(label);
345 let (clicked, changed) = if let Some(Value::Bool(v)) = self.ctx.consume_edit(&id) {
346 let changed = *value != v;
347 *value = v;
348 (true, changed)
349 } else {
350 (false, false)
351 };
352 self.ctx
353 .declare(build_checkbox(id, self.name.clone(), label, *value));
354 Response { clicked, changed }
355 }
356
357 pub fn color_picker(&mut self, label: &str, value: &mut [f32; 3]) -> Response {
359 let id = self.make_id(label);
360 let (clicked, changed) = if let Some(Value::Color3(c)) = self.ctx.consume_edit(&id) {
361 let changed = *value != c;
362 *value = c;
363 (true, changed)
364 } else {
365 (false, false)
366 };
367 self.ctx
368 .declare(build_color3(id, self.name.clone(), label, *value));
369 Response { clicked, changed }
370 }
371
372 pub fn color_picker4(&mut self, label: &str, value: &mut [f32; 4]) -> Response {
374 let id = self.make_id(label);
375 let (clicked, changed) = if let Some(Value::Color4(c)) = self.ctx.consume_edit(&id) {
376 let changed = *value != c;
377 *value = c;
378 (true, changed)
379 } else {
380 (false, false)
381 };
382 self.ctx
383 .declare(build_color4(id, self.name.clone(), label, *value));
384 Response { clicked, changed }
385 }
386
387 pub fn text_input(&mut self, label: &str, value: &mut String) -> Response {
389 let id = self.make_id(label);
390 let (clicked, changed) = if let Some(Value::String(s)) = self.ctx.consume_edit(&id) {
391 let changed = *value != s;
392 *value = s;
393 (true, changed)
394 } else {
395 (false, false)
396 };
397 self.ctx
398 .declare(build_text_input(id, self.name.clone(), label, value));
399 Response { clicked, changed }
400 }
401
402 pub fn dropdown(&mut self, label: &str, selected: &mut usize, options: &[&str]) -> Response {
404 let id = self.make_id(label);
405 let (clicked, changed) = if let Some(Value::Enum { selected: s, .. }) = self.ctx.consume_edit(&id) {
406 let changed = *selected != s;
407 *selected = s;
408 (true, changed)
409 } else {
410 (false, false)
411 };
412 self.ctx
413 .declare(build_dropdown(id, self.name.clone(), label, *selected, options));
414 Response { clicked, changed }
415 }
416
417 pub fn button(&mut self, label: &str) -> Response {
419 let id = self.make_id(label);
420 let clicked = matches!(self.ctx.consume_edit(&id), Some(Value::Button(true)));
421 self.ctx
422 .declare(build_button(id, self.name.clone(), label));
423 Response { clicked, changed: clicked }
424 }
425
426 pub fn label(&mut self, text: &str) {
428 let id = self.make_id(text);
429 self.ctx
430 .declare(build_label(id, self.name.clone(), text));
431 }
432
433 pub fn separator(&mut self) {
435 let id = format!("{}::__sep_{}", self.name, self.ctx.current_frame_len());
436 self.ctx.declare(ElementDecl {
437 id,
438 kind: ElementKind::Separator,
439 label: String::new(),
440 value: Value::Bool(false),
441 meta: ElementMeta::default(),
442 window: self.name.clone(),
443 });
444 }
445
446 pub fn section(&mut self, title: &str) {
448 let id = format!("{}::__sec_{}", self.name, self.ctx.current_frame_len());
449 self.ctx.declare(ElementDecl {
450 id,
451 kind: ElementKind::Section,
452 label: title.to_string(),
453 value: Value::String(title.to_string()),
454 meta: ElementMeta::default(),
455 window: self.name.clone(),
456 });
457 }
458
459 pub fn progress_bar(&mut self, label: &str, value: f64, accent: AccentColor) {
461 let id = self.make_id(label);
462 self.ctx
463 .declare(build_progress_bar(id, self.name.clone(), label, value, accent, None));
464 }
465
466 pub fn progress_bar_with_subtitle(
468 &mut self,
469 label: &str,
470 value: f64,
471 accent: AccentColor,
472 subtitle: &str,
473 ) {
474 let id = self.make_id(label);
475 self.ctx.declare(build_progress_bar(
476 id,
477 self.name.clone(),
478 label,
479 value,
480 accent,
481 Some(subtitle),
482 ));
483 }
484
485 pub fn stat(&mut self, label: &str, value: &str, subvalue: Option<&str>, accent: AccentColor) {
487 let id = self.make_id(label);
488 self.ctx
489 .declare(build_stat(id, self.name.clone(), label, value, subvalue, accent));
490 }
491
492 pub fn status(
494 &mut self,
495 label: &str,
496 active: bool,
497 active_text: Option<&str>,
498 inactive_text: Option<&str>,
499 active_color: AccentColor,
500 inactive_color: AccentColor,
501 ) {
502 let id = self.make_id(label);
503 self.ctx.declare(build_status(
504 id,
505 self.name.clone(),
506 label,
507 active,
508 active_text,
509 inactive_text,
510 active_color,
511 inactive_color,
512 ));
513 }
514
515 pub fn mini_chart(
517 &mut self,
518 label: &str,
519 values: &[f32],
520 unit: Option<&str>,
521 accent: AccentColor,
522 ) {
523 let id = self.make_id(label);
524 self.ctx
525 .declare(build_mini_chart(id, self.name.clone(), label, values, unit, accent));
526 }
527
528 pub fn set_accent(&mut self, accent: AccentColor) {
531 let id = format!("{}::__accent_{}", self.name, accent.as_str());
532 self.ctx.declare(ElementDecl {
533 id,
534 kind: ElementKind::Label,
535 label: String::new(),
536 value: Value::String(String::new()),
537 meta: ElementMeta {
538 accent: Some(accent.as_str().to_string()),
539 ..Default::default()
540 },
541 window: self.name.clone(),
542 });
543 }
544
545 pub fn grid<F>(&mut self, cols: usize, f: F)
548 where
549 F: FnOnce(&mut Grid<'_, 'a>),
550 {
551 let grid_id = format!("{}::__grid_{}", self.name, self.ctx.current_frame_len());
552 let mut grid = Grid::new(&grid_id, self, cols);
553 f(&mut grid);
554 grid.finish();
555 }
556
557 pub fn plot(
559 &mut self,
560 label: &str,
561 series: &[(&str, &[f32], AccentColor)],
562 x_label: Option<&str>,
563 y_label: Option<&str>,
564 ) {
565 let id = self.make_id(label);
566 self.ctx
567 .declare(build_plot(id, self.name.clone(), label, series, x_label, y_label));
568 }
569}
570
571pub struct Grid<'a, 'ctx> {
575 id: String,
576 window: &'a mut Window<'ctx>,
577 cols: usize,
578 children: Vec<String>,
579}
580
581impl<'a, 'ctx> Grid<'a, 'ctx> {
582 fn new(id: &str, window: &'a mut Window<'ctx>, cols: usize) -> Self {
583 Self {
584 id: id.to_string(),
585 window,
586 cols,
587 children: Vec::new(),
588 }
589 }
590
591 fn record_child(&mut self, id: String) {
592 self.children.push(id);
593 }
594
595 fn finish(self) {
596 self.window.ctx.declare(ElementDecl {
597 id: self.id,
598 kind: ElementKind::Grid,
599 label: String::new(),
600 value: Value::GridValue {
601 cols: self.cols,
602 children: self.children,
603 },
604 meta: ElementMeta::default(),
605 window: self.window.name.clone(),
606 });
607 }
608
609 fn make_id(&mut self, _label: &str) -> String {
610 let id = format!("{}::_{}", self.id, self.window.next_idx);
611 self.window.next_idx += 1;
612 id
613 }
614
615 pub fn slider(&mut self, label: &str, value: &mut f32, range: RangeInclusive<f32>) -> Response {
619 let id = self.make_id(label);
620 let (clicked, changed) = if let Some(Value::Float(v)) = self.window.ctx.consume_edit(&id) {
621 let new = v as f32;
622 let changed = *value != new;
623 *value = new;
624 (true, changed)
625 } else {
626 (false, false)
627 };
628 self.record_child(id.clone());
629 self.window
630 .ctx
631 .declare(build_slider(id, self.window.name.clone(), label, *value, &range));
632 Response { clicked, changed }
633 }
634
635 pub fn slider_int(&mut self, label: &str, value: &mut i32, range: RangeInclusive<i32>) -> Response {
637 let id = self.make_id(label);
638 let (clicked, changed) = if let Some(Value::Int(v)) = self.window.ctx.consume_edit(&id) {
639 let new = v as i32;
640 let changed = *value != new;
641 *value = new;
642 (true, changed)
643 } else {
644 (false, false)
645 };
646 self.record_child(id.clone());
647 self.window.ctx.declare(build_slider_int(
648 id,
649 self.window.name.clone(),
650 label,
651 *value,
652 &range,
653 ));
654 Response { clicked, changed }
655 }
656
657 pub fn checkbox(&mut self, label: &str, value: &mut bool) -> Response {
659 let id = self.make_id(label);
660 let (clicked, changed) = if let Some(Value::Bool(v)) = self.window.ctx.consume_edit(&id) {
661 let changed = *value != v;
662 *value = v;
663 (true, changed)
664 } else {
665 (false, false)
666 };
667 self.record_child(id.clone());
668 self.window
669 .ctx
670 .declare(build_checkbox(id, self.window.name.clone(), label, *value));
671 Response { clicked, changed }
672 }
673
674 pub fn color_picker(&mut self, label: &str, value: &mut [f32; 3]) -> Response {
676 let id = self.make_id(label);
677 let (clicked, changed) = if let Some(Value::Color3(c)) = self.window.ctx.consume_edit(&id) {
678 let changed = *value != c;
679 *value = c;
680 (true, changed)
681 } else {
682 (false, false)
683 };
684 self.record_child(id.clone());
685 self.window
686 .ctx
687 .declare(build_color3(id, self.window.name.clone(), label, *value));
688 Response { clicked, changed }
689 }
690
691 pub fn color_picker4(&mut self, label: &str, value: &mut [f32; 4]) -> Response {
693 let id = self.make_id(label);
694 let (clicked, changed) = if let Some(Value::Color4(c)) = self.window.ctx.consume_edit(&id) {
695 let changed = *value != c;
696 *value = c;
697 (true, changed)
698 } else {
699 (false, false)
700 };
701 self.record_child(id.clone());
702 self.window
703 .ctx
704 .declare(build_color4(id, self.window.name.clone(), label, *value));
705 Response { clicked, changed }
706 }
707
708 pub fn text_input(&mut self, label: &str, value: &mut String) -> Response {
710 let id = self.make_id(label);
711 let (clicked, changed) = if let Some(Value::String(s)) = self.window.ctx.consume_edit(&id) {
712 let changed = *value != s;
713 *value = s;
714 (true, changed)
715 } else {
716 (false, false)
717 };
718 self.record_child(id.clone());
719 self.window
720 .ctx
721 .declare(build_text_input(id, self.window.name.clone(), label, value));
722 Response { clicked, changed }
723 }
724
725 pub fn dropdown(&mut self, label: &str, selected: &mut usize, options: &[&str]) -> Response {
727 let id = self.make_id(label);
728 let (clicked, changed) = if let Some(Value::Enum { selected: s, .. }) = self.window.ctx.consume_edit(&id) {
729 let changed = *selected != s;
730 *selected = s;
731 (true, changed)
732 } else {
733 (false, false)
734 };
735 self.record_child(id.clone());
736 self.window.ctx.declare(build_dropdown(
737 id,
738 self.window.name.clone(),
739 label,
740 *selected,
741 options,
742 ));
743 Response { clicked, changed }
744 }
745
746 pub fn button(&mut self, label: &str) -> Response {
748 let id = self.make_id(label);
749 let clicked = matches!(self.window.ctx.consume_edit(&id), Some(Value::Button(true)));
750 self.record_child(id.clone());
751 self.window
752 .ctx
753 .declare(build_button(id, self.window.name.clone(), label));
754 Response { clicked, changed: clicked }
755 }
756
757 pub fn label(&mut self, text: &str) {
761 let id = self.make_id(text);
762 self.record_child(id.clone());
763 self.window
764 .ctx
765 .declare(build_label(id, self.window.name.clone(), text));
766 }
767
768 pub fn progress_bar(&mut self, label: &str, value: f64, accent: AccentColor) {
770 let id = self.make_id(label);
771 self.record_child(id.clone());
772 self.window
773 .ctx
774 .declare(build_progress_bar(id, self.window.name.clone(), label, value, accent, None));
775 }
776
777 pub fn progress_bar_with_subtitle(
779 &mut self,
780 label: &str,
781 value: f64,
782 accent: AccentColor,
783 subtitle: &str,
784 ) {
785 let id = self.make_id(label);
786 self.record_child(id.clone());
787 self.window.ctx.declare(build_progress_bar(
788 id,
789 self.window.name.clone(),
790 label,
791 value,
792 accent,
793 Some(subtitle),
794 ));
795 }
796
797 pub fn stat(&mut self, label: &str, value: &str, subvalue: Option<&str>, accent: AccentColor) {
799 let id = self.make_id(label);
800 self.record_child(id.clone());
801 self.window
802 .ctx
803 .declare(build_stat(id, self.window.name.clone(), label, value, subvalue, accent));
804 }
805
806 pub fn status(
808 &mut self,
809 label: &str,
810 active: bool,
811 active_text: Option<&str>,
812 inactive_text: Option<&str>,
813 active_color: AccentColor,
814 inactive_color: AccentColor,
815 ) {
816 let id = self.make_id(label);
817 self.record_child(id.clone());
818 self.window.ctx.declare(build_status(
819 id,
820 self.window.name.clone(),
821 label,
822 active,
823 active_text,
824 inactive_text,
825 active_color,
826 inactive_color,
827 ));
828 }
829
830 pub fn mini_chart(
832 &mut self,
833 label: &str,
834 values: &[f32],
835 unit: Option<&str>,
836 accent: AccentColor,
837 ) {
838 let id = self.make_id(label);
839 self.record_child(id.clone());
840 self.window.ctx.declare(build_mini_chart(
841 id,
842 self.window.name.clone(),
843 label,
844 values,
845 unit,
846 accent,
847 ));
848 }
849
850 pub fn plot(
852 &mut self,
853 label: &str,
854 series: &[(&str, &[f32], AccentColor)],
855 x_label: Option<&str>,
856 y_label: Option<&str>,
857 ) {
858 let id = self.make_id(label);
859 self.record_child(id.clone());
860 self.window.ctx.declare(build_plot(
861 id,
862 self.window.name.clone(),
863 label,
864 series,
865 x_label,
866 y_label,
867 ));
868 }
869
870 pub fn separator(&mut self) {
872 let id = format!("{}::__sep_{}", self.id, self.window.ctx.current_frame_len());
873 self.record_child(id.clone());
874 self.window.ctx.declare(ElementDecl {
875 id,
876 kind: ElementKind::Separator,
877 label: String::new(),
878 value: Value::Bool(false),
879 meta: ElementMeta::default(),
880 window: self.window.name.clone(),
881 });
882 }
883
884 pub fn grid<F>(&mut self, cols: usize, f: F)
886 where
887 F: FnOnce(&mut Grid<'_, 'ctx>),
888 {
889 let grid_id = format!("{}::__grid_{}", self.id, self.window.ctx.current_frame_len());
890 let mut child_grid = Grid::new(&grid_id, self.window, cols);
891 f(&mut child_grid);
892 let child_id = child_grid.id.clone();
893 child_grid.finish();
894 self.children.push(child_id);
895 }
896}