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    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}