Skip to main content

embedded_gui/context/
builders.rs

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