1use embedded_graphics_core::pixelcolor::Rgb565;
2
3#[cfg(not(feature = "std"))]
4use crate::math::F32Ext as _;
5use crate::{
6 geometry::Rect,
7 haptics::HapticPattern,
8 image::{ImageFit, ImageRef, ReelPlayer},
9 render::TextAlign,
10 style::{Style, WidgetStyle},
11 widget::WidgetId,
12 widgets::{ChartMode, KeyboardLayout, NotificationLevel, SurfaceState, WidgetKind},
13};
14
15use super::*;
16
17impl<'a, const NODES: usize, const EVENTS: usize, const DIRTY: usize>
18 GuiContext<'a, NODES, EVENTS, DIRTY>
19{
20 pub fn add_panel<S>(&mut self, rect: impl Into<Rect>, style: S) -> Result<WidgetId, GuiError>
21 where
22 S: Into<WidgetStyle>,
23 {
24 self.add_widget(rect, WidgetKind::Panel, style)
25 }
26
27 pub fn add_themed_panel(&mut self, rect: impl Into<Rect>) -> Result<WidgetId, GuiError> {
28 self.add_panel(rect, self.theme.panel)
29 }
30
31 pub fn add_label<S>(
32 &mut self,
33 rect: impl Into<Rect>,
34 text: &'a str,
35 style: S,
36 ) -> Result<WidgetId, GuiError>
37 where
38 S: Into<WidgetStyle>,
39 {
40 self.add_widget(rect, WidgetKind::Label(text), style)
41 }
42
43 pub fn add_themed_label(
44 &mut self,
45 rect: impl Into<Rect>,
46 text: &'a str,
47 ) -> Result<WidgetId, GuiError> {
48 self.add_label(rect, text, self.theme.label)
49 }
50
51 pub fn add_button<S>(
52 &mut self,
53 rect: impl Into<Rect>,
54 text: &'a str,
55 style: S,
56 ) -> Result<WidgetId, GuiError>
57 where
58 S: Into<WidgetStyle>,
59 {
60 let id = self.add_widget(rect, WidgetKind::Button(text), style)?;
61 self.ensure_focus();
62 Ok(id)
63 }
64
65 pub fn add_themed_button(
66 &mut self,
67 rect: impl Into<Rect>,
68 text: &'a str,
69 ) -> Result<WidgetId, GuiError> {
70 self.add_button(rect, text, self.theme.button)
71 }
72
73 #[cfg(feature = "rich-widgets")]
74 pub fn add_progress_bar<S>(
75 &mut self,
76 rect: impl Into<Rect>,
77 value: f32,
78 style: S,
79 ) -> Result<WidgetId, GuiError>
80 where
81 S: Into<WidgetStyle>,
82 {
83 self.add_widget(
84 rect,
85 WidgetKind::ProgressBar {
86 value: value.clamp(0.0, 1.0),
87 },
88 style,
89 )
90 }
91
92 #[cfg(feature = "rich-widgets")]
93 pub fn add_themed_progress_bar(
94 &mut self,
95 rect: impl Into<Rect>,
96 value: f32,
97 ) -> Result<WidgetId, GuiError> {
98 self.add_progress_bar(rect, value, self.theme.progress)
99 }
100
101 #[cfg(feature = "rich-widgets")]
102 pub fn add_toggle<S>(
103 &mut self,
104 rect: impl Into<Rect>,
105 label: &'a str,
106 on: bool,
107 style: S,
108 ) -> Result<WidgetId, GuiError>
109 where
110 S: Into<WidgetStyle>,
111 {
112 let id = self.add_widget(rect, WidgetKind::Toggle { label, on }, style)?;
113 self.ensure_focus();
114 Ok(id)
115 }
116
117 #[cfg(feature = "rich-widgets")]
118 pub fn add_themed_toggle(
119 &mut self,
120 rect: impl Into<Rect>,
121 label: &'a str,
122 on: bool,
123 ) -> Result<WidgetId, GuiError> {
124 self.add_toggle(rect, label, on, self.theme.toggle)
125 }
126
127 #[cfg(feature = "rich-widgets")]
128 pub fn add_checkbox<S>(
129 &mut self,
130 rect: impl Into<Rect>,
131 label: &'a str,
132 checked: bool,
133 style: S,
134 ) -> Result<WidgetId, GuiError>
135 where
136 S: Into<WidgetStyle>,
137 {
138 let id = self.add_widget(rect, WidgetKind::Checkbox { label, checked }, style)?;
139 self.ensure_focus();
140 Ok(id)
141 }
142
143 #[cfg(feature = "rich-widgets")]
144 pub fn add_themed_checkbox(
145 &mut self,
146 rect: impl Into<Rect>,
147 label: &'a str,
148 checked: bool,
149 ) -> Result<WidgetId, GuiError> {
150 self.add_checkbox(rect, label, checked, self.theme.checkbox)
151 }
152
153 #[cfg(feature = "rich-widgets")]
154 pub fn add_slider<S>(
155 &mut self,
156 rect: Rect,
157 value: f32,
158 min: f32,
159 max: f32,
160 style: S,
161 ) -> Result<WidgetId, GuiError>
162 where
163 S: Into<WidgetStyle>,
164 {
165 let value = value.clamp(min.min(max), min.max(max));
166 let id = self.add_widget(rect, WidgetKind::Slider { value, min, max }, style)?;
167 self.ensure_focus();
168 Ok(id)
169 }
170
171 #[cfg(feature = "rich-widgets")]
172 pub fn add_themed_slider(
173 &mut self,
174 rect: Rect,
175 value: f32,
176 min: f32,
177 max: f32,
178 ) -> Result<WidgetId, GuiError> {
179 self.add_slider(rect, value, min, max, self.theme.slider)
180 }
181
182 #[cfg(feature = "rich-widgets")]
183 pub fn add_value_label<S>(
184 &mut self,
185 rect: Rect,
186 label: &'a str,
187 value: i32,
188 style: S,
189 ) -> Result<WidgetId, GuiError>
190 where
191 S: Into<WidgetStyle>,
192 {
193 self.add_widget(rect, WidgetKind::ValueLabel { label, value }, style)
194 }
195
196 #[cfg(feature = "rich-widgets")]
197 pub fn add_themed_value_label(
198 &mut self,
199 rect: Rect,
200 label: &'a str,
201 value: i32,
202 ) -> Result<WidgetId, GuiError> {
203 self.add_value_label(rect, label, value, self.theme.value_label)
204 }
205
206 #[cfg(feature = "rich-widgets")]
207 pub fn add_icon_button<S>(
208 &mut self,
209 rect: Rect,
210 icon: char,
211 label: &'a str,
212 style: S,
213 ) -> Result<WidgetId, GuiError>
214 where
215 S: Into<WidgetStyle>,
216 {
217 let id = self.add_widget(rect, WidgetKind::IconButton { icon, label }, style)?;
218 self.ensure_focus();
219 Ok(id)
220 }
221
222 #[cfg(feature = "rich-widgets")]
223 pub fn add_themed_icon_button(
224 &mut self,
225 rect: Rect,
226 icon: char,
227 label: &'a str,
228 ) -> Result<WidgetId, GuiError> {
229 self.add_icon_button(rect, icon, label, self.theme.icon_button)
230 }
231
232 #[cfg(feature = "rich-widgets")]
233 pub fn add_list<S>(
234 &mut self,
235 rect: Rect,
236 items: &'a [&'a str],
237 selected: usize,
238 visible_rows: usize,
239 style: S,
240 ) -> Result<WidgetId, GuiError>
241 where
242 S: Into<WidgetStyle>,
243 {
244 let selected = selected.min(items.len().saturating_sub(1));
245 let id = self.add_widget(
246 rect,
247 WidgetKind::List {
248 items,
249 selected,
250 offset: selected,
251 visible_rows: visible_rows.max(1),
252 },
253 style,
254 )?;
255 self.ensure_focus();
256 Ok(id)
257 }
258
259 #[cfg(feature = "rich-widgets")]
260 pub fn add_feed_timeline<S>(
261 &mut self,
262 rect: Rect,
263 items: &'a [&'a str],
264 selected: usize,
265 visible_rows: usize,
266 expanded: bool,
267 style: S,
268 ) -> Result<WidgetId, GuiError>
269 where
270 S: Into<WidgetStyle>,
271 {
272 let selected = selected.min(items.len().saturating_sub(1));
273 let id = self.add_widget(
274 rect,
275 WidgetKind::FeedTimeline {
276 items,
277 selected,
278 offset: selected,
279 visible_rows: visible_rows.max(1),
280 expanded,
281 },
282 style,
283 )?;
284 self.ensure_focus();
285 Ok(id)
286 }
287
288 #[cfg(feature = "rich-widgets")]
289 pub fn add_themed_list(
290 &mut self,
291 rect: Rect,
292 items: &'a [&'a str],
293 selected: usize,
294 visible_rows: usize,
295 ) -> Result<WidgetId, GuiError> {
296 self.add_list(rect, items, selected, visible_rows, self.theme.list)
297 }
298
299 pub fn add_circular_list<S>(
300 &mut self,
301 rect: Rect,
302 items: &'a [&'a str],
303 selected: usize,
304 visible_rows: usize,
305 style: S,
306 ) -> Result<WidgetId, GuiError>
307 where
308 S: Into<WidgetStyle>,
309 {
310 let selected = selected.min(items.len().saturating_sub(1));
311 let id = self.add_widget(
312 rect,
313 WidgetKind::CircularList {
314 items,
315 selected,
316 offset: selected,
317 visible_rows: visible_rows.max(1),
318 },
319 style,
320 )?;
321 self.ensure_focus();
322 Ok(id)
323 }
324
325 pub fn add_themed_circular_list(
326 &mut self,
327 rect: Rect,
328 items: &'a [&'a str],
329 selected: usize,
330 visible_rows: usize,
331 ) -> Result<WidgetId, GuiError> {
332 self.add_circular_list(rect, items, selected, visible_rows, self.theme.list)
333 }
334
335 #[cfg(feature = "rich-widgets")]
336 pub fn add_scroll_view<S>(
337 &mut self,
338 rect: Rect,
339 offset_y: i32,
340 content_h: u32,
341 style: S,
342 ) -> Result<WidgetId, GuiError>
343 where
344 S: Into<WidgetStyle>,
345 {
346 let id = self.add_widget(
347 rect,
348 WidgetKind::ScrollView {
349 offset_y,
350 content_h,
351 },
352 style,
353 )?;
354 self.ensure_focus();
355 Ok(id)
356 }
357
358 #[cfg(feature = "rich-widgets")]
359 pub fn add_themed_scroll_view(
360 &mut self,
361 rect: Rect,
362 offset_y: i32,
363 content_h: u32,
364 ) -> Result<WidgetId, GuiError> {
365 self.add_scroll_view(rect, offset_y, content_h, self.theme.list)
366 }
367
368 #[cfg(feature = "rich-widgets")]
369 pub fn add_tabs<S>(
370 &mut self,
371 rect: Rect,
372 labels: &'a [&'a str],
373 selected: usize,
374 style: S,
375 ) -> Result<WidgetId, GuiError>
376 where
377 S: Into<WidgetStyle>,
378 {
379 let selected = selected.min(labels.len().saturating_sub(1));
380 let id = self.add_widget(rect, WidgetKind::Tabs { labels, selected }, style)?;
381 self.ensure_focus();
382 Ok(id)
383 }
384
385 #[cfg(feature = "rich-widgets")]
386 pub fn add_themed_tabs(
387 &mut self,
388 rect: Rect,
389 labels: &'a [&'a str],
390 selected: usize,
391 ) -> Result<WidgetId, GuiError> {
392 self.add_tabs(rect, labels, selected, self.theme.tabs)
393 }
394
395 #[cfg(feature = "rich-widgets")]
396 pub fn add_dialog<S>(
397 &mut self,
398 rect: Rect,
399 title: &'a str,
400 body: &'a str,
401 style: S,
402 ) -> Result<WidgetId, GuiError>
403 where
404 S: Into<WidgetStyle>,
405 {
406 let id = self.add_widget(rect, WidgetKind::Dialog { title, body }, style)?;
407 self.play_haptic(HapticPattern::Alert);
408 Ok(id)
409 }
410
411 #[cfg(feature = "rich-widgets")]
412 pub fn add_themed_dialog(
413 &mut self,
414 rect: Rect,
415 title: &'a str,
416 body: &'a str,
417 ) -> Result<WidgetId, GuiError> {
418 self.add_dialog(rect, title, body, self.theme.dialog)
419 }
420
421 #[cfg(feature = "rich-widgets")]
422 pub fn add_toast<S>(
423 &mut self,
424 rect: Rect,
425 text: &'a str,
426 ttl_ms: u32,
427 style: S,
428 ) -> Result<WidgetId, GuiError>
429 where
430 S: Into<WidgetStyle>,
431 {
432 let id = self.add_widget(rect, WidgetKind::Toast { text, ttl_ms }, style)?;
433 self.play_haptic(HapticPattern::Success);
434 Ok(id)
435 }
436
437 #[cfg(feature = "rich-widgets")]
438 pub fn add_themed_toast(
439 &mut self,
440 rect: Rect,
441 text: &'a str,
442 ttl_ms: u32,
443 ) -> Result<WidgetId, GuiError> {
444 self.add_toast(rect, text, ttl_ms, self.theme.toast)
445 }
446
447 #[cfg(feature = "rich-widgets")]
448 pub fn add_meter<S>(
449 &mut self,
450 rect: Rect,
451 value: f32,
452 min: f32,
453 max: f32,
454 style: S,
455 ) -> Result<WidgetId, GuiError>
456 where
457 S: Into<WidgetStyle>,
458 {
459 self.add_widget(rect, WidgetKind::Meter { value, min, max }, style)
460 }
461
462 #[cfg(feature = "rich-widgets")]
463 pub fn add_themed_meter(
464 &mut self,
465 rect: Rect,
466 value: f32,
467 min: f32,
468 max: f32,
469 ) -> Result<WidgetId, GuiError> {
470 self.add_meter(rect, value, min, max, self.theme.meter)
471 }
472
473 #[allow(clippy::too_many_arguments)]
474 #[cfg(feature = "rich-widgets")]
475 pub fn add_arc_gauge<S>(
476 &mut self,
477 rect: Rect,
478 value: f32,
479 min: f32,
480 max: f32,
481 start_deg: i32,
482 end_deg: i32,
483 thickness: u8,
484 antialias: bool,
485 style: S,
486 ) -> Result<WidgetId, GuiError>
487 where
488 S: Into<WidgetStyle>,
489 {
490 self.add_widget(
491 rect,
492 WidgetKind::ArcGauge {
493 value,
494 min,
495 max,
496 start_deg,
497 end_deg,
498 thickness: thickness.max(1),
499 antialias,
500 major_ticks: 6,
501 minor_ticks: 2,
502 show_value: false,
503 },
504 style,
505 )
506 }
507
508 #[cfg(feature = "rich-widgets")]
509 pub fn add_gauge<S>(
510 &mut self,
511 rect: Rect,
512 value: f32,
513 min: f32,
514 max: f32,
515 style: S,
516 ) -> Result<WidgetId, GuiError>
517 where
518 S: Into<WidgetStyle>,
519 {
520 self.add_widget(
521 rect,
522 WidgetKind::Gauge {
523 value,
524 min,
525 max,
526 major_ticks: 6,
527 minor_ticks: 2,
528 show_value: false,
529 },
530 style,
531 )
532 }
533
534 #[allow(clippy::too_many_arguments)]
535 pub fn add_sweeping_arc<S>(
536 &mut self,
537 rect: Rect,
538 progress: f32,
539 clockwise: bool,
540 arc_radius: u32,
541 frame_inset: u16,
542 corner_radius: u8,
543 bg_color: Rgb565,
544 arc_color: Rgb565,
545 frame_color: Rgb565,
546 style: S,
547 ) -> Result<WidgetId, GuiError>
548 where
549 S: Into<WidgetStyle>,
550 {
551 self.add_widget(
552 rect,
553 WidgetKind::SweepingArc {
554 progress: progress.clamp(0.0, 1.0),
555 clockwise,
556 arc_radius,
557 frame_inset,
558 corner_radius,
559 bg_color,
560 arc_color,
561 frame_color,
562 },
563 style,
564 )
565 }
566
567 #[allow(clippy::too_many_arguments)]
568 #[cfg(feature = "rich-widgets")]
569 pub fn add_gauge_needle<S>(
570 &mut self,
571 rect: Rect,
572 value: f32,
573 min: f32,
574 max: f32,
575 start_deg: i32,
576 end_deg: i32,
577 style: S,
578 ) -> Result<WidgetId, GuiError>
579 where
580 S: Into<WidgetStyle>,
581 {
582 self.add_widget(
583 rect,
584 WidgetKind::GaugeNeedle {
585 value,
586 min,
587 max,
588 start_deg,
589 end_deg,
590 },
591 style,
592 )
593 }
594
595 #[cfg(feature = "rich-widgets")]
596 pub fn add_chart<S>(
597 &mut self,
598 rect: Rect,
599 values: &'a [f32],
600 min: f32,
601 max: f32,
602 style: S,
603 ) -> Result<WidgetId, GuiError>
604 where
605 S: Into<WidgetStyle>,
606 {
607 self.add_widget(
608 rect,
609 WidgetKind::Chart {
610 values,
611 min,
612 max,
613 thickness: 1,
614 fill_under: false,
615 markers: false,
616 mode: ChartMode::Line,
617 show_grid: false,
618 show_axes: false,
619 show_labels: false,
620 },
621 style,
622 )
623 }
624
625 pub fn add_plotter<S>(
626 &mut self,
627 rect: Rect,
628 values: &'a [f32],
629 head: usize,
630 min: f32,
631 max: f32,
632 style: S,
633 ) -> Result<WidgetId, GuiError>
634 where
635 S: Into<WidgetStyle>,
636 {
637 self.add_widget(
638 rect,
639 WidgetKind::Plotter {
640 values,
641 head,
642 min,
643 max,
644 thickness: 1,
645 show_grid: false,
646 show_axes: false,
647 },
648 style,
649 )
650 }
651
652 pub fn add_themed_plotter(
653 &mut self,
654 rect: Rect,
655 values: &'a [f32],
656 head: usize,
657 min: f32,
658 max: f32,
659 ) -> Result<WidgetId, GuiError> {
660 self.add_plotter(rect, values, head, min, max, self.theme.panel)
661 }
662
663 #[cfg(feature = "rich-widgets")]
664 pub fn set_plotter_style(&mut self, id: WidgetId, thickness: u8) -> Result<(), GuiError> {
665 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
666 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
667 match node.kind {
668 WidgetKind::Plotter {
669 thickness: ref mut t,
670 ..
671 } => {
672 *t = thickness.max(1);
673 self.dirty.add(rect)?;
674 Ok(())
675 }
676 _ => Err(GuiError::NotFound),
677 }
678 }
679
680 #[cfg(feature = "rich-widgets")]
681 pub fn set_plotter_decoration(
682 &mut self,
683 id: WidgetId,
684 show_grid: bool,
685 show_axes: bool,
686 ) -> Result<(), GuiError> {
687 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
688 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
689 match node.kind {
690 WidgetKind::Plotter {
691 show_grid: ref mut grid,
692 show_axes: ref mut axes,
693 ..
694 } => {
695 *grid = show_grid;
696 *axes = show_axes;
697 self.dirty.add(rect)?;
698 Ok(())
699 }
700 _ => Err(GuiError::NotFound),
701 }
702 }
703
704 #[cfg(feature = "rich-widgets")]
705 pub fn set_chart_style(
706 &mut self,
707 id: WidgetId,
708 thickness: u8,
709 fill_under: bool,
710 markers: bool,
711 ) -> Result<(), GuiError> {
712 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
713 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
714 match node.kind {
715 WidgetKind::Chart {
716 thickness: ref mut t,
717 fill_under: ref mut fill,
718 markers: ref mut mark,
719 ..
720 } => {
721 *t = thickness.max(1);
722 *fill = fill_under;
723 *mark = markers;
724 self.dirty.add(rect)?;
725 Ok(())
726 }
727 _ => Err(GuiError::NotFound),
728 }
729 }
730
731 #[cfg(feature = "rich-widgets")]
732 pub fn set_chart_decoration(
733 &mut self,
734 id: WidgetId,
735 mode: ChartMode,
736 show_grid: bool,
737 show_axes: bool,
738 show_labels: bool,
739 ) -> Result<(), GuiError> {
740 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
741 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
742 match node.kind {
743 WidgetKind::Chart {
744 mode: ref mut chart_mode,
745 show_grid: ref mut grid,
746 show_axes: ref mut axes,
747 show_labels: ref mut labels,
748 ..
749 } => {
750 *chart_mode = mode;
751 *grid = show_grid;
752 *axes = show_axes;
753 *labels = show_labels;
754 self.dirty.add(rect)?;
755 Ok(())
756 }
757 _ => Err(GuiError::NotFound),
758 }
759 }
760
761 #[cfg(feature = "rich-widgets")]
762 pub fn add_spinner<S>(&mut self, rect: Rect, phase: f32, style: S) -> Result<WidgetId, GuiError>
763 where
764 S: Into<WidgetStyle>,
765 {
766 self.add_widget(rect, WidgetKind::Spinner { phase }, style)
767 }
768
769 #[cfg(feature = "rich-widgets")]
770 pub fn add_dropdown<S>(
771 &mut self,
772 rect: Rect,
773 items: &'a [&'a str],
774 selected: usize,
775 style: S,
776 ) -> Result<WidgetId, GuiError>
777 where
778 S: Into<WidgetStyle>,
779 {
780 let selected = selected.min(items.len().saturating_sub(1));
781 let id = self.add_widget(
782 rect,
783 WidgetKind::Dropdown {
784 items,
785 selected,
786 open: false,
787 },
788 style,
789 )?;
790 self.ensure_focus();
791 Ok(id)
792 }
793
794 #[cfg(feature = "rich-widgets")]
795 pub fn add_roller<S>(
796 &mut self,
797 rect: Rect,
798 items: &'a [&'a str],
799 selected: usize,
800 style: S,
801 ) -> Result<WidgetId, GuiError>
802 where
803 S: Into<WidgetStyle>,
804 {
805 let selected = selected.min(items.len().saturating_sub(1));
806 let id = self.add_widget(rect, WidgetKind::Roller { items, selected }, style)?;
807 self.ensure_focus();
808 Ok(id)
809 }
810
811 #[cfg(feature = "rich-widgets")]
812 pub fn add_table<S>(
813 &mut self,
814 rect: Rect,
815 rows: &'a [&'a [&'a str]],
816 style: S,
817 ) -> Result<WidgetId, GuiError>
818 where
819 S: Into<WidgetStyle>,
820 {
821 self.add_widget(
822 rect,
823 WidgetKind::Table {
824 rows,
825 separators: true,
826 cell_padding: 1,
827 align: TextAlign::Left,
828 },
829 style,
830 )
831 }
832
833 #[cfg(feature = "rich-widgets")]
834 pub fn set_table_style(
835 &mut self,
836 id: WidgetId,
837 separators: bool,
838 cell_padding: u8,
839 align: TextAlign,
840 ) -> Result<(), GuiError> {
841 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
842 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
843 match node.kind {
844 WidgetKind::Table {
845 separators: ref mut cell_sep,
846 cell_padding: ref mut pad,
847 align: ref mut table_align,
848 ..
849 } => {
850 *cell_sep = separators;
851 *pad = cell_padding.min(6);
852 *table_align = align;
853 self.dirty.add(rect)?;
854 Ok(())
855 }
856 _ => Err(GuiError::NotFound),
857 }
858 }
859
860 #[cfg(feature = "rich-widgets")]
861 pub fn add_textarea<S>(
862 &mut self,
863 rect: Rect,
864 text: &'a str,
865 placeholder: &'a str,
866 style: S,
867 ) -> Result<WidgetId, GuiError>
868 where
869 S: Into<WidgetStyle>,
870 {
871 let cursor = text.chars().count();
872 let (text_buf, text_len) = textarea_storage_from_str(text);
873 let id = self.add_widget(
874 rect,
875 WidgetKind::TextArea {
876 text_buf,
877 text_len,
878 cursor,
879 placeholder,
880 selection: None,
881 cursor_visible: true,
882 read_only: false,
883 single_line: false,
884 accept_newline: true,
885 },
886 style,
887 )?;
888 self.ensure_focus();
889 Ok(id)
890 }
891
892 #[cfg(feature = "rich-widgets")]
893 pub fn add_keyboard<S>(
894 &mut self,
895 rect: Rect,
896 keys: &'a [char],
897 cols: u8,
898 target: Option<WidgetId>,
899 style: S,
900 ) -> Result<WidgetId, GuiError>
901 where
902 S: Into<WidgetStyle>,
903 {
904 self.add_keyboard_with_alt(rect, keys, None, cols, target, style)
905 }
906
907 #[cfg(feature = "rich-widgets")]
908 pub fn add_keyboard_with_alt<S>(
909 &mut self,
910 rect: Rect,
911 keys: &'a [char],
912 alt_keys: Option<&'a [char]>,
913 cols: u8,
914 target: Option<WidgetId>,
915 style: S,
916 ) -> Result<WidgetId, GuiError>
917 where
918 S: Into<WidgetStyle>,
919 {
920 let id = self.add_widget(
921 rect,
922 WidgetKind::Keyboard {
923 keys,
924 selected: 0,
925 cols: cols.max(1),
926 alt_keys,
927 layout: KeyboardLayout::Normal,
928 target,
929 },
930 style,
931 )?;
932 self.ensure_focus();
933 Ok(id)
934 }
935
936 pub fn add_image<S>(
937 &mut self,
938 rect: Rect,
939 image: ImageRef<'a>,
940 fit: ImageFit,
941 style: S,
942 ) -> Result<WidgetId, GuiError>
943 where
944 S: Into<WidgetStyle>,
945 {
946 self.add_widget(rect, WidgetKind::Image { image, fit }, style)
947 }
948
949 #[cfg(feature = "rich-widgets")]
950 pub fn add_peek_reveal<S>(
951 &mut self,
952 rect: Rect,
953 icon: ImageRef<'a>,
954 title: &'a str,
955 subtitle: &'a str,
956 style: S,
957 ) -> Result<WidgetId, GuiError>
958 where
959 S: Into<WidgetStyle>,
960 {
961 self.add_widget(
962 rect,
963 WidgetKind::PeekReveal {
964 icon,
965 title,
966 subtitle,
967 progress: 0.0,
968 },
969 style,
970 )
971 }
972
973 #[cfg(feature = "rich-widgets")]
974 pub fn add_glance_tile<S>(
975 &mut self,
976 rect: Rect,
977 icon: char,
978 title: &'a str,
979 subtitle: &'a str,
980 style: S,
981 ) -> Result<WidgetId, GuiError>
982 where
983 S: Into<WidgetStyle>,
984 {
985 let id = self.add_widget(
986 rect,
987 WidgetKind::GlanceTile {
988 icon,
989 title,
990 subtitle,
991 highlighted: false,
992 },
993 style,
994 )?;
995 self.ensure_focus();
996 Ok(id)
997 }
998
999 #[cfg(feature = "rich-widgets")]
1000 pub fn add_card_deck<S>(
1001 &mut self,
1002 rect: Rect,
1003 titles: &'a [&'a str],
1004 selected: usize,
1005 style: S,
1006 ) -> Result<WidgetId, GuiError>
1007 where
1008 S: Into<WidgetStyle>,
1009 {
1010 self.add_widget(
1011 rect,
1012 WidgetKind::CardDeck {
1013 titles,
1014 selected: selected.min(titles.len().saturating_sub(1)),
1015 },
1016 style,
1017 )
1018 }
1019
1020 #[cfg(feature = "rich-widgets")]
1021 pub fn add_reel<S>(
1022 &mut self,
1023 rect: Rect,
1024 player: ReelPlayer<'a>,
1025 fit: ImageFit,
1026 style: S,
1027 ) -> Result<WidgetId, GuiError>
1028 where
1029 S: Into<WidgetStyle>,
1030 {
1031 self.add_widget(rect, WidgetKind::Reel { player, fit }, style)
1032 }
1033
1034 #[cfg(feature = "rich-widgets")]
1035 pub fn add_state_surface<S>(
1036 &mut self,
1037 rect: Rect,
1038 state: SurfaceState,
1039 title: &'a str,
1040 message: &'a str,
1041 action: Option<&'a str>,
1042 style: S,
1043 ) -> Result<WidgetId, GuiError>
1044 where
1045 S: Into<WidgetStyle>,
1046 {
1047 self.add_widget(
1048 rect,
1049 WidgetKind::StateSurface {
1050 state,
1051 title,
1052 message,
1053 action,
1054 busy_phase: 0.0,
1055 },
1056 style,
1057 )
1058 }
1059
1060 #[cfg(feature = "rich-widgets")]
1061 pub fn add_heads_up_banner<S>(
1062 &mut self,
1063 rect: Rect,
1064 level: NotificationLevel,
1065 text: &'a str,
1066 ttl_ms: u32,
1067 style: S,
1068 ) -> Result<WidgetId, GuiError>
1069 where
1070 S: Into<WidgetStyle>,
1071 {
1072 self.add_widget(
1073 rect,
1074 WidgetKind::HeadsUpBanner {
1075 level,
1076 text,
1077 ttl_ms,
1078 },
1079 style,
1080 )
1081 }
1082
1083 #[allow(clippy::too_many_arguments)]
1084 #[cfg(feature = "rich-widgets")]
1085 pub fn add_notification_action_sheet<S>(
1086 &mut self,
1087 rect: Rect,
1088 level: NotificationLevel,
1089 title: &'a str,
1090 body: &'a str,
1091 actions: &'a [&'a str],
1092 selected: usize,
1093 open: bool,
1094 style: S,
1095 ) -> Result<WidgetId, GuiError>
1096 where
1097 S: Into<WidgetStyle>,
1098 {
1099 self.add_widget(
1100 rect,
1101 WidgetKind::NotificationActionSheet {
1102 level,
1103 title,
1104 body,
1105 actions,
1106 selected: selected.min(actions.len().saturating_sub(1)),
1107 open,
1108 },
1109 style,
1110 )
1111 }
1112
1113 pub fn add_border<S>(&mut self, rect: Rect, style: S) -> Result<WidgetId, GuiError>
1114 where
1115 S: Into<WidgetStyle>,
1116 {
1117 self.add_widget(rect, WidgetKind::Border, style)
1118 }
1119
1120 pub fn add_spacer(&mut self, rect: Rect) -> Result<WidgetId, GuiError> {
1121 self.add_widget(rect, WidgetKind::Spacer, Style::default())
1122 }
1123
1124 #[cfg(feature = "rich-widgets")]
1125 pub fn add_menu<S>(
1126 &mut self,
1127 rect: Rect,
1128 items: &'a [&'a str],
1129 selected: usize,
1130 style: S,
1131 ) -> Result<WidgetId, GuiError>
1132 where
1133 S: Into<WidgetStyle>,
1134 {
1135 let selected = selected.min(items.len().saturating_sub(1));
1136 let id = self.add_widget(rect, WidgetKind::Menu { items, selected }, style)?;
1137 self.ensure_focus();
1138 Ok(id)
1139 }
1140
1141 pub fn add_dial<S>(
1142 &mut self,
1143 rect: Rect,
1144 value: f32,
1145 min: f32,
1146 max: f32,
1147 style: S,
1148 ) -> Result<WidgetId, GuiError>
1149 where
1150 S: Into<WidgetStyle>,
1151 {
1152 let value = value.clamp(min, max);
1153 let id = self.add_widget(rect, WidgetKind::Dial { value, min, max }, style)?;
1154 self.ensure_focus();
1155 Ok(id)
1156 }
1157
1158 pub fn add_themed_dial(
1159 &mut self,
1160 rect: Rect,
1161 value: f32,
1162 min: f32,
1163 max: f32,
1164 ) -> Result<WidgetId, GuiError> {
1165 self.add_dial(rect, value, min, max, self.theme.button)
1166 }
1167
1168 #[allow(clippy::too_many_arguments)]
1169 pub fn add_rle_player<S>(
1170 &mut self,
1171 rect: impl Into<Rect>,
1172 rle_data: &'static [u8],
1173 frame_width: u16,
1174 frame_height: u16,
1175 total_frames: usize,
1176 frame_duration_ms: u32,
1177 style: S,
1178 ) -> Result<WidgetId, GuiError>
1179 where
1180 S: Into<WidgetStyle>,
1181 {
1182 self.add_widget(
1183 rect,
1184 WidgetKind::RlePlayer {
1185 rle_data,
1186 frame_width,
1187 frame_height,
1188 total_frames,
1189 current_frame: 0,
1190 elapsed_ms: 0,
1191 frame_duration_ms,
1192 },
1193 style,
1194 )
1195 }
1196
1197 pub fn add_themed_rle_player(
1198 &mut self,
1199 rect: Rect,
1200 rle_data: &'static [u8],
1201 frame_width: u16,
1202 frame_height: u16,
1203 total_frames: usize,
1204 frame_duration_ms: u32,
1205 ) -> Result<WidgetId, GuiError> {
1206 self.add_rle_player(
1207 rect,
1208 rle_data,
1209 frame_width,
1210 frame_height,
1211 total_frames,
1212 frame_duration_ms,
1213 self.theme.panel,
1214 )
1215 }
1216
1217 pub fn add_autocomplete_widget<S>(
1218 &mut self,
1219 rect: Rect,
1220 suggestions: &'a [&'a str],
1221 style: S,
1222 ) -> Result<WidgetId, GuiError>
1223 where
1224 S: Into<WidgetStyle>,
1225 {
1226 let id = self.add_widget(
1227 rect,
1228 WidgetKind::AutoComplete {
1229 text_buf: [0; 32],
1230 text_len: 0,
1231 suggestions,
1232 filtered: [None; 8],
1233 filter_count: 0,
1234 selected: None,
1235 expanded: false,
1236 },
1237 style,
1238 )?;
1239 self.ensure_focus();
1240 Ok(id)
1241 }
1242
1243 pub fn add_themed_autocomplete(
1244 &mut self,
1245 rect: Rect,
1246 suggestions: &'a [&'a str],
1247 ) -> Result<WidgetId, GuiError> {
1248 self.add_autocomplete_widget(rect, suggestions, self.theme.panel)
1249 }
1250}