Skip to main content

embedded_gui/context/
mutators.rs

1use embedded_graphics_core::pixelcolor::Rgb565;
2
3#[cfg(not(feature = "std"))]
4use crate::math::F32Ext as _;
5use crate::{
6    geometry::Rect,
7    input::{UiEvent, WidgetEvent, WidgetEventKind},
8    layout::{Axis, LayoutItem, LinearLayout},
9    state::{FeedTimelineState, ListState, ScrollState, SliderState, TabsState},
10    widget::{EventContext, EventPhase, EventPolicy, FocusGroupId, WidgetFlags, WidgetId},
11    widgets::{KeyboardLayout, SurfaceState, TEXTAREA_CAPACITY, WidgetKind, WidgetNode},
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 set_progress(&mut self, id: WidgetId, value: f32) -> Result<(), GuiError> {
20        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
21        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
22        match node.kind {
23            WidgetKind::ProgressBar { value: ref mut v } => {
24                *v = value.clamp(0.0, 1.0);
25                self.dirty.add(rect)?;
26                Ok(())
27            }
28            #[cfg(feature = "rich-widgets")]
29            WidgetKind::PeekReveal {
30                progress: ref mut v,
31                ..
32            } => {
33                *v = value.clamp(0.0, 1.0);
34                self.dirty.add(rect)?;
35                Ok(())
36            }
37            WidgetKind::SweepingArc {
38                progress: ref mut v,
39                ..
40            } => {
41                *v = value.clamp(0.0, 1.0);
42                self.dirty.add(rect)?;
43                Ok(())
44            }
45            _ => Err(GuiError::NotFound),
46        }
47    }
48
49    #[cfg(feature = "rich-widgets")]
50    pub fn set_glance_highlighted(
51        &mut self,
52        id: WidgetId,
53        highlighted: bool,
54    ) -> Result<(), GuiError> {
55        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
56        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
57        match node.kind {
58            WidgetKind::GlanceTile {
59                highlighted: ref mut h,
60                ..
61            } => {
62                *h = highlighted;
63                self.dirty.add(rect)?;
64                Ok(())
65            }
66            _ => Err(GuiError::NotFound),
67        }
68    }
69
70    #[cfg(feature = "rich-widgets")]
71    pub fn set_card_deck_selected(
72        &mut self,
73        id: WidgetId,
74        selected: usize,
75    ) -> Result<(), GuiError> {
76        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
77        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
78        match node.kind {
79            WidgetKind::CardDeck {
80                titles,
81                selected: ref mut current,
82            } => {
83                *current = selected.min(titles.len().saturating_sub(1));
84                self.dirty.add(rect)?;
85                Ok(())
86            }
87            _ => Err(GuiError::NotFound),
88        }
89    }
90
91    #[cfg(feature = "rich-widgets")]
92    pub fn tick_reel(&mut self, id: WidgetId, dt_ms: u32) -> Result<(), GuiError> {
93        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
94        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
95        match node.kind {
96            WidgetKind::Reel {
97                player: ref mut reel,
98                ..
99            } => {
100                reel.tick(dt_ms);
101                self.dirty.add(rect)?;
102                Ok(())
103            }
104            _ => Err(GuiError::NotFound),
105        }
106    }
107
108    #[cfg(feature = "rich-widgets")]
109    pub fn set_state_surface_state(
110        &mut self,
111        id: WidgetId,
112        state: SurfaceState,
113    ) -> Result<(), GuiError> {
114        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
115        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
116        match node.kind {
117            WidgetKind::StateSurface {
118                state: ref mut current,
119                ..
120            } => {
121                *current = state;
122                self.dirty.add(rect)?;
123                Ok(())
124            }
125            _ => Err(GuiError::NotFound),
126        }
127    }
128
129    #[cfg(feature = "rich-widgets")]
130    pub fn set_state_surface_message(
131        &mut self,
132        id: WidgetId,
133        message: &'a str,
134    ) -> Result<(), GuiError> {
135        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
136        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
137        match node.kind {
138            WidgetKind::StateSurface {
139                message: ref mut current,
140                ..
141            } => {
142                *current = message;
143                self.dirty.add(rect)?;
144                Ok(())
145            }
146            _ => Err(GuiError::NotFound),
147        }
148    }
149
150    #[cfg(feature = "rich-widgets")]
151    pub fn set_state_surface_action(
152        &mut self,
153        id: WidgetId,
154        action: Option<&'a str>,
155    ) -> Result<(), GuiError> {
156        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
157        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
158        match node.kind {
159            WidgetKind::StateSurface {
160                action: ref mut current,
161                ..
162            } => {
163                *current = action;
164                self.dirty.add(rect)?;
165                Ok(())
166            }
167            _ => Err(GuiError::NotFound),
168        }
169    }
170
171    #[cfg(feature = "rich-widgets")]
172    pub fn set_state_surface_busy_phase(
173        &mut self,
174        id: WidgetId,
175        phase: f32,
176    ) -> Result<(), GuiError> {
177        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
178        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
179        match node.kind {
180            WidgetKind::StateSurface {
181                busy_phase: ref mut current,
182                ..
183            } => {
184                *current = phase;
185                self.dirty.add(rect)?;
186                Ok(())
187            }
188            _ => Err(GuiError::NotFound),
189        }
190    }
191
192    #[cfg(feature = "rich-widgets")]
193    pub fn tick_state_surface(
194        &mut self,
195        id: WidgetId,
196        dt_ms: u32,
197        cycles_per_sec: f32,
198    ) -> Result<(), GuiError> {
199        let phase = match self.node(id).ok_or(GuiError::NotFound)?.kind {
200            WidgetKind::StateSurface { busy_phase, .. } => {
201                busy_phase + (dt_ms as f32 / 1000.0) * cycles_per_sec
202            }
203            _ => return Err(GuiError::NotFound),
204        };
205        self.set_state_surface_busy_phase(id, phase)
206    }
207
208    #[cfg(feature = "rich-widgets")]
209    pub fn set_heads_up_ttl(&mut self, id: WidgetId, ttl_ms: u32) -> Result<(), GuiError> {
210        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
211        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
212        match node.kind {
213            WidgetKind::HeadsUpBanner {
214                ttl_ms: ref mut current,
215                ..
216            } => {
217                *current = ttl_ms;
218                self.dirty.add(rect)?;
219                Ok(())
220            }
221            _ => Err(GuiError::NotFound),
222        }
223    }
224
225    #[cfg(feature = "rich-widgets")]
226    pub fn tick_heads_up(&mut self, id: WidgetId, dt_ms: u32) -> Result<(), GuiError> {
227        let ttl = match self.node(id).ok_or(GuiError::NotFound)?.kind {
228            WidgetKind::HeadsUpBanner { ttl_ms, .. } => ttl_ms.saturating_sub(dt_ms),
229            _ => return Err(GuiError::NotFound),
230        };
231        self.set_heads_up_ttl(id, ttl)
232    }
233
234    #[cfg(feature = "rich-widgets")]
235    pub fn set_notification_sheet_open(
236        &mut self,
237        id: WidgetId,
238        open: bool,
239    ) -> Result<(), GuiError> {
240        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
241        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
242        match node.kind {
243            WidgetKind::NotificationActionSheet {
244                open: ref mut current,
245                ..
246            } => {
247                *current = open;
248                self.dirty.add(rect)?;
249                Ok(())
250            }
251            _ => Err(GuiError::NotFound),
252        }
253    }
254
255    #[cfg(feature = "rich-widgets")]
256    pub fn set_notification_sheet_selected(
257        &mut self,
258        id: WidgetId,
259        selected: usize,
260    ) -> Result<(), GuiError> {
261        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
262        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
263        match node.kind {
264            WidgetKind::NotificationActionSheet {
265                actions,
266                selected: ref mut current,
267                ..
268            } => {
269                *current = selected.min(actions.len().saturating_sub(1));
270                self.dirty.add(rect)?;
271                Ok(())
272            }
273            _ => Err(GuiError::NotFound),
274        }
275    }
276
277    #[cfg(feature = "rich-widgets")]
278    pub fn set_menu_selected(&mut self, id: WidgetId, selected: usize) -> Result<(), GuiError> {
279        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
280        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
281        match node.kind {
282            WidgetKind::Menu {
283                items,
284                selected: ref mut current,
285            } => {
286                *current = selected.min(items.len().saturating_sub(1));
287                self.dirty.add(rect)?;
288                Ok(())
289            }
290            _ => Err(GuiError::NotFound),
291        }
292    }
293
294    #[cfg(feature = "rich-widgets")]
295    pub fn menu_selected(&self, id: WidgetId) -> Option<usize> {
296        match self.node(id)?.kind {
297            WidgetKind::Menu { selected, .. } => Some(selected),
298            _ => None,
299        }
300    }
301
302    #[cfg(feature = "rich-widgets")]
303    pub fn list_selected(&self, id: WidgetId) -> Option<usize> {
304        match self.node(id)?.kind {
305            WidgetKind::List { selected, .. } | WidgetKind::CircularList { selected, .. } => {
306                Some(selected)
307            }
308            _ => None,
309        }
310    }
311
312    #[cfg(feature = "rich-widgets")]
313    pub fn set_list_selected(&mut self, id: WidgetId, selected: usize) -> Result<(), GuiError> {
314        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
315        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
316        match node.kind {
317            WidgetKind::List {
318                items,
319                selected: ref mut current,
320                ref mut offset,
321                visible_rows,
322            }
323            | WidgetKind::CircularList {
324                items,
325                selected: ref mut current,
326                ref mut offset,
327                visible_rows,
328            } => {
329                let mut state = ListState::new(*current, *offset, visible_rows);
330                state.set_selected(selected, items.len());
331                *current = state.selected;
332                *offset = state.offset;
333                self.dirty.add(rect)?;
334                Ok(())
335            }
336            _ => Err(GuiError::NotFound),
337        }
338    }
339
340    pub fn set_plotter_head(&mut self, id: WidgetId, head: usize) -> Result<(), GuiError> {
341        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
342        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
343        match node.kind {
344            WidgetKind::Plotter {
345                head: ref mut h, ..
346            } => {
347                *h = head;
348                self.dirty.add(rect)?;
349                Ok(())
350            }
351            _ => Err(GuiError::NotFound),
352        }
353    }
354
355    #[cfg(feature = "rich-widgets")]
356    pub fn set_plotter_values(&mut self, id: WidgetId, values: &'a [f32]) -> Result<(), GuiError> {
357        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
358        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
359        match node.kind {
360            WidgetKind::Plotter {
361                values: ref mut v, ..
362            } => {
363                *v = values;
364                self.dirty.add(rect)?;
365                Ok(())
366            }
367            _ => Err(GuiError::NotFound),
368        }
369    }
370
371    #[cfg(feature = "rich-widgets")]
372    pub fn feed_selected(&self, id: WidgetId) -> Option<usize> {
373        match self.node(id)?.kind {
374            WidgetKind::FeedTimeline { selected, .. } => Some(selected),
375            _ => None,
376        }
377    }
378
379    #[cfg(feature = "rich-widgets")]
380    pub fn set_feed_selected(&mut self, id: WidgetId, selected: usize) -> Result<(), GuiError> {
381        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
382        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
383        match node.kind {
384            WidgetKind::FeedTimeline {
385                items,
386                selected: ref mut current,
387                ref mut offset,
388                visible_rows,
389                ..
390            } => {
391                let mut state = FeedTimelineState::new(*current, *offset, visible_rows, false);
392                state.set_selected(selected, items.len());
393                *current = state.selected;
394                *offset = state.offset;
395                self.dirty.add(rect)?;
396                Ok(())
397            }
398            _ => Err(GuiError::NotFound),
399        }
400    }
401
402    #[cfg(feature = "rich-widgets")]
403    pub fn set_feed_expanded(&mut self, id: WidgetId, expanded: bool) -> Result<(), GuiError> {
404        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
405        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
406        match node.kind {
407            WidgetKind::FeedTimeline {
408                expanded: ref mut current,
409                ..
410            } => {
411                *current = expanded;
412                self.dirty.add(rect)?;
413                Ok(())
414            }
415            _ => Err(GuiError::NotFound),
416        }
417    }
418
419    #[cfg(feature = "rich-widgets")]
420    pub fn set_toggle(&mut self, id: WidgetId, on: bool) -> Result<(), GuiError> {
421        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
422        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
423        match node.kind {
424            WidgetKind::Toggle { on: ref mut v, .. } => {
425                *v = on;
426                self.dirty.add(rect)?;
427                Ok(())
428            }
429            _ => Err(GuiError::NotFound),
430        }
431    }
432
433    #[cfg(feature = "rich-widgets")]
434    pub fn toggle_value(&self, id: WidgetId) -> Option<bool> {
435        match self.node(id)?.kind {
436            WidgetKind::Toggle { on, .. } => Some(on),
437            _ => None,
438        }
439    }
440
441    #[cfg(feature = "rich-widgets")]
442    pub fn set_checked(&mut self, id: WidgetId, checked: bool) -> Result<(), GuiError> {
443        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
444        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
445        match node.kind {
446            WidgetKind::Checkbox {
447                checked: ref mut v, ..
448            } => {
449                *v = checked;
450                self.dirty.add(rect)?;
451                Ok(())
452            }
453            _ => Err(GuiError::NotFound),
454        }
455    }
456
457    #[cfg(feature = "rich-widgets")]
458    pub fn checked_value(&self, id: WidgetId) -> Option<bool> {
459        match self.node(id)?.kind {
460            WidgetKind::Checkbox { checked, .. } => Some(checked),
461            _ => None,
462        }
463    }
464
465    #[cfg(feature = "rich-widgets")]
466    pub fn set_slider_value(&mut self, id: WidgetId, value: f32) -> Result<(), GuiError> {
467        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
468        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
469        match node.kind {
470            WidgetKind::Slider {
471                value: ref mut v,
472                min,
473                max,
474            } => {
475                let mut state = SliderState::new(*v, min, max);
476                state.set_value(value);
477                *v = state.value;
478                self.dirty.add(rect)?;
479                Ok(())
480            }
481            _ => Err(GuiError::NotFound),
482        }
483    }
484
485    #[cfg(feature = "rich-widgets")]
486    pub fn slider_value(&self, id: WidgetId) -> Option<f32> {
487        match self.node(id)?.kind {
488            WidgetKind::Slider { value, .. } => Some(value),
489            _ => None,
490        }
491    }
492
493    #[cfg(feature = "rich-widgets")]
494    pub fn set_value_label(&mut self, id: WidgetId, value: i32) -> Result<(), GuiError> {
495        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
496        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
497        match node.kind {
498            WidgetKind::ValueLabel {
499                value: ref mut v, ..
500            } => {
501                *v = value;
502                self.dirty.add(rect)?;
503                Ok(())
504            }
505            _ => Err(GuiError::NotFound),
506        }
507    }
508
509    #[cfg(feature = "rich-widgets")]
510    pub fn set_scroll_offset(&mut self, id: WidgetId, offset_y: i32) -> Result<(), GuiError> {
511        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
512        match node.kind {
513            WidgetKind::ScrollView {
514                offset_y: ref mut v,
515                content_h,
516            } => {
517                let mut state = ScrollState::new(*v, content_h);
518                state.set_offset(offset_y);
519                *v = state.offset_y;
520                self.mark_subtree_dirty(id)?;
521                Ok(())
522            }
523            _ => Err(GuiError::NotFound),
524        }
525    }
526
527    #[cfg(feature = "rich-widgets")]
528    pub fn scroll_offset(&self, id: WidgetId) -> Option<i32> {
529        match self.node(id)?.kind {
530            WidgetKind::ScrollView { offset_y, .. } => Some(offset_y),
531            _ => None,
532        }
533    }
534
535    #[cfg(feature = "rich-widgets")]
536    pub fn set_tab_selected(&mut self, id: WidgetId, selected: usize) -> Result<(), GuiError> {
537        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
538        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
539        match node.kind {
540            WidgetKind::Tabs {
541                labels,
542                selected: ref mut v,
543            } => {
544                let mut state = TabsState::new(*v);
545                state.set_selected(selected, labels.len());
546                *v = state.selected;
547                self.dirty.add(rect)?;
548                Ok(())
549            }
550            _ => Err(GuiError::NotFound),
551        }
552    }
553
554    #[cfg(feature = "rich-widgets")]
555    pub fn tab_selected(&self, id: WidgetId) -> Option<usize> {
556        match self.node(id)?.kind {
557            WidgetKind::Tabs { selected, .. } => Some(selected),
558            _ => None,
559        }
560    }
561
562    #[cfg(feature = "rich-widgets")]
563    pub fn set_toast_ttl(&mut self, id: WidgetId, ttl_ms: u32) -> Result<(), GuiError> {
564        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
565        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
566        match node.kind {
567            WidgetKind::Toast {
568                ttl_ms: ref mut v, ..
569            } => {
570                *v = ttl_ms;
571                self.dirty.add(rect)?;
572                Ok(())
573            }
574            _ => Err(GuiError::NotFound),
575        }
576    }
577
578    #[cfg(feature = "rich-widgets")]
579    pub fn tick_toast(&mut self, id: WidgetId, dt_ms: u32) -> Result<(), GuiError> {
580        let ttl = match self.node(id).ok_or(GuiError::NotFound)?.kind {
581            WidgetKind::Toast { ttl_ms, .. } => ttl_ms.saturating_sub(dt_ms),
582            _ => return Err(GuiError::NotFound),
583        };
584        self.set_toast_ttl(id, ttl)
585    }
586
587    #[cfg(feature = "rich-widgets")]
588    pub fn set_meter_value(&mut self, id: WidgetId, value: f32) -> Result<(), GuiError> {
589        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
590        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
591        match node.kind {
592            WidgetKind::Meter {
593                value: ref mut v,
594                min,
595                max,
596            } => {
597                *v = value.clamp(min.min(max), min.max(max));
598                self.dirty.add(rect)?;
599                Ok(())
600            }
601            _ => Err(GuiError::NotFound),
602        }
603    }
604
605    #[cfg(feature = "rich-widgets")]
606    pub fn set_spinner_phase(&mut self, id: WidgetId, phase: f32) -> Result<(), GuiError> {
607        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
608        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
609        match node.kind {
610            WidgetKind::Spinner { phase: ref mut v } => {
611                *v = phase;
612                self.dirty.add(rect)?;
613                Ok(())
614            }
615            _ => Err(GuiError::NotFound),
616        }
617    }
618
619    #[cfg(feature = "rich-widgets")]
620    pub fn tick_spinner(
621        &mut self,
622        id: WidgetId,
623        dt_ms: u32,
624        cycles_per_sec: f32,
625    ) -> Result<(), GuiError> {
626        let phase = match self.node(id).ok_or(GuiError::NotFound)?.kind {
627            WidgetKind::Spinner { phase } => phase + (dt_ms as f32 / 1000.0) * cycles_per_sec,
628            _ => return Err(GuiError::NotFound),
629        };
630        self.set_spinner_phase(id, phase)
631    }
632
633    #[cfg(feature = "rich-widgets")]
634    pub fn set_dropdown_selected(&mut self, id: WidgetId, selected: usize) -> Result<(), GuiError> {
635        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
636        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
637        match node.kind {
638            WidgetKind::Dropdown {
639                items,
640                selected: ref mut current,
641                ..
642            } => {
643                *current = selected.min(items.len().saturating_sub(1));
644                self.dirty.add(rect)?;
645                Ok(())
646            }
647            _ => Err(GuiError::NotFound),
648        }
649    }
650
651    #[cfg(feature = "rich-widgets")]
652    pub fn dropdown_selected(&self, id: WidgetId) -> Option<usize> {
653        match self.node(id)?.kind {
654            WidgetKind::Dropdown { selected, .. } => Some(selected),
655            _ => None,
656        }
657    }
658
659    #[cfg(feature = "rich-widgets")]
660    pub fn set_dropdown_open(&mut self, id: WidgetId, open: bool) -> Result<(), GuiError> {
661        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
662        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
663        match node.kind {
664            WidgetKind::Dropdown {
665                open: ref mut is_open,
666                ..
667            } => {
668                if *is_open != open {
669                    *is_open = open;
670                    self.dirty.add(rect)?;
671                    self.push_event(if open {
672                        UiEvent::Opened(id)
673                    } else {
674                        UiEvent::Closed(id)
675                    })?;
676                }
677                Ok(())
678            }
679            _ => Err(GuiError::NotFound),
680        }
681    }
682
683    #[cfg(feature = "rich-widgets")]
684    pub fn dropdown_open(&self, id: WidgetId) -> Option<bool> {
685        match self.node(id)?.kind {
686            WidgetKind::Dropdown { open, .. } => Some(open),
687            _ => None,
688        }
689    }
690
691    #[cfg(feature = "rich-widgets")]
692    pub fn set_roller_selected(&mut self, id: WidgetId, selected: usize) -> Result<(), GuiError> {
693        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
694        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
695        match node.kind {
696            WidgetKind::Roller {
697                items,
698                selected: ref mut current,
699            } => {
700                *current = selected.min(items.len().saturating_sub(1));
701                self.dirty.add(rect)?;
702                Ok(())
703            }
704            _ => Err(GuiError::NotFound),
705        }
706    }
707
708    #[cfg(feature = "rich-widgets")]
709    pub fn roller_selected(&self, id: WidgetId) -> Option<usize> {
710        match self.node(id)?.kind {
711            WidgetKind::Roller { selected, .. } => Some(selected),
712            _ => None,
713        }
714    }
715
716    #[cfg(feature = "rich-widgets")]
717    pub fn set_textarea_text(&mut self, id: WidgetId, text: &'a str) -> Result<(), GuiError> {
718        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
719        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
720        match node.kind {
721            WidgetKind::TextArea {
722                text_buf: ref mut buf,
723                text_len: ref mut len,
724                cursor: ref mut c,
725                ..
726            } => {
727                let (next_buf, next_len) = textarea_storage_from_str(text);
728                *buf = next_buf;
729                *len = next_len;
730                *c = (*c).min(textarea_text(buf, *len).chars().count());
731                self.dirty.add(rect)?;
732                Ok(())
733            }
734            _ => Err(GuiError::NotFound),
735        }
736    }
737
738    #[cfg(feature = "rich-widgets")]
739    pub fn textarea_text(&self, id: WidgetId) -> Option<&str> {
740        match &self.node(id)?.kind {
741            WidgetKind::TextArea {
742                text_buf, text_len, ..
743            } => Some(textarea_text(text_buf, *text_len)),
744            _ => None,
745        }
746    }
747
748    #[cfg(feature = "rich-widgets")]
749    pub fn set_textarea_cursor(&mut self, id: WidgetId, cursor: usize) -> Result<(), GuiError> {
750        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
751        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
752        match node.kind {
753            WidgetKind::TextArea {
754                text_buf,
755                text_len,
756                cursor: ref mut current,
757                ..
758            } => {
759                let text = textarea_text(&text_buf, text_len);
760                *current = cursor.min(text.chars().count());
761                self.dirty.add(rect)?;
762                Ok(())
763            }
764            _ => Err(GuiError::NotFound),
765        }
766    }
767
768    #[cfg(feature = "rich-widgets")]
769    pub fn move_textarea_cursor(&mut self, id: WidgetId, delta: i8) -> Result<(), GuiError> {
770        let next = self.textarea_cursor(id).ok_or(GuiError::NotFound)? as i32 + delta as i32;
771        self.set_textarea_cursor_with_extend(id, next.max(0) as usize, false)
772    }
773
774    #[cfg(feature = "rich-widgets")]
775    pub fn move_textarea_cursor_select(&mut self, id: WidgetId, delta: i8) -> Result<(), GuiError> {
776        let next = self.textarea_cursor(id).ok_or(GuiError::NotFound)? as i32 + delta as i32;
777        self.set_textarea_cursor_with_extend(id, next.max(0) as usize, true)
778    }
779
780    #[cfg(feature = "rich-widgets")]
781    pub fn move_textarea_cursor_word(&mut self, id: WidgetId, delta: i8) -> Result<(), GuiError> {
782        let (text, cursor) = match &self.node(id).ok_or(GuiError::NotFound)?.kind {
783            WidgetKind::TextArea {
784                text_buf,
785                text_len,
786                cursor,
787                ..
788            } => (textarea_text(text_buf, *text_len), *cursor),
789            _ => return Err(GuiError::NotFound),
790        };
791        let next = if delta >= 0 {
792            next_word_boundary(text, cursor)
793        } else {
794            prev_word_boundary(text, cursor)
795        };
796        self.set_textarea_cursor_with_extend(id, next, false)
797    }
798
799    #[cfg(feature = "rich-widgets")]
800    pub fn move_textarea_cursor_word_select(
801        &mut self,
802        id: WidgetId,
803        delta: i8,
804    ) -> Result<(), GuiError> {
805        let (text, cursor) = match &self.node(id).ok_or(GuiError::NotFound)?.kind {
806            WidgetKind::TextArea {
807                text_buf,
808                text_len,
809                cursor,
810                ..
811            } => (textarea_text(text_buf, *text_len), *cursor),
812            _ => return Err(GuiError::NotFound),
813        };
814        let next = if delta >= 0 {
815            next_word_boundary(text, cursor)
816        } else {
817            prev_word_boundary(text, cursor)
818        };
819        self.set_textarea_cursor_with_extend(id, next, true)
820    }
821
822    #[cfg(feature = "rich-widgets")]
823    pub fn set_textarea_cursor_home(&mut self, id: WidgetId) -> Result<(), GuiError> {
824        self.set_textarea_cursor(id, 0)
825    }
826
827    #[cfg(feature = "rich-widgets")]
828    pub fn set_textarea_cursor_end(&mut self, id: WidgetId) -> Result<(), GuiError> {
829        let len = self
830            .textarea_text(id)
831            .map(|text| text.chars().count())
832            .ok_or(GuiError::NotFound)?;
833        self.set_textarea_cursor(id, len)
834    }
835
836    #[cfg(feature = "rich-widgets")]
837    pub fn set_textarea_cursor_line_home(&mut self, id: WidgetId) -> Result<(), GuiError> {
838        let (text, cursor, wrap_cols) = self.textarea_line_context(id)?;
839        let (row, _) = textarea_row_col_at_cursor(text, cursor, wrap_cols);
840        let next = textarea_cursor_from_row_col(text, row, 0, wrap_cols);
841        self.set_textarea_cursor_with_extend(id, next, false)
842    }
843
844    #[cfg(feature = "rich-widgets")]
845    pub fn set_textarea_cursor_line_home_select(&mut self, id: WidgetId) -> Result<(), GuiError> {
846        let (text, cursor, wrap_cols) = self.textarea_line_context(id)?;
847        let (row, _) = textarea_row_col_at_cursor(text, cursor, wrap_cols);
848        let next = textarea_cursor_from_row_col(text, row, 0, wrap_cols);
849        self.set_textarea_cursor_with_extend(id, next, true)
850    }
851
852    #[cfg(feature = "rich-widgets")]
853    pub fn set_textarea_cursor_line_end(&mut self, id: WidgetId) -> Result<(), GuiError> {
854        let (text, cursor, wrap_cols) = self.textarea_line_context(id)?;
855        let (row, _) = textarea_row_col_at_cursor(text, cursor, wrap_cols);
856        let row_end = textarea_row_end_col(text, row, wrap_cols);
857        let next = textarea_cursor_from_row_col(text, row, row_end, wrap_cols);
858        self.set_textarea_cursor_with_extend(id, next, false)
859    }
860
861    #[cfg(feature = "rich-widgets")]
862    pub fn set_textarea_cursor_line_end_select(&mut self, id: WidgetId) -> Result<(), GuiError> {
863        let (text, cursor, wrap_cols) = self.textarea_line_context(id)?;
864        let (row, _) = textarea_row_col_at_cursor(text, cursor, wrap_cols);
865        let row_end = textarea_row_end_col(text, row, wrap_cols);
866        let next = textarea_cursor_from_row_col(text, row, row_end, wrap_cols);
867        self.set_textarea_cursor_with_extend(id, next, true)
868    }
869
870    #[cfg(feature = "rich-widgets")]
871    pub fn textarea_cursor(&self, id: WidgetId) -> Option<usize> {
872        match self.node(id)?.kind {
873            WidgetKind::TextArea { cursor, .. } => Some(cursor),
874            _ => None,
875        }
876    }
877
878    #[cfg(feature = "rich-widgets")]
879    pub fn set_textarea_selection(
880        &mut self,
881        id: WidgetId,
882        start: usize,
883        end: usize,
884    ) -> Result<(), GuiError> {
885        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
886        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
887        match node.kind {
888            WidgetKind::TextArea {
889                text_buf,
890                text_len,
891                selection: ref mut current,
892                ..
893            } => {
894                let text = textarea_text(&text_buf, text_len);
895                let len = text.chars().count();
896                let start = start.min(len);
897                let end = end.min(len);
898                *current = Some((start.min(end), start.max(end)));
899                self.dirty.add(rect)?;
900                Ok(())
901            }
902            _ => Err(GuiError::NotFound),
903        }
904    }
905
906    #[cfg(feature = "rich-widgets")]
907    pub fn clear_textarea_selection(&mut self, id: WidgetId) -> Result<(), GuiError> {
908        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
909        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
910        match node.kind {
911            WidgetKind::TextArea {
912                selection: ref mut current,
913                ..
914            } => {
915                *current = None;
916                self.dirty.add(rect)?;
917                Ok(())
918            }
919            _ => Err(GuiError::NotFound),
920        }
921    }
922
923    #[cfg(feature = "rich-widgets")]
924    pub fn textarea_selection(&self, id: WidgetId) -> Option<(usize, usize)> {
925        match self.node(id)?.kind {
926            WidgetKind::TextArea { selection, .. } => selection,
927            _ => None,
928        }
929    }
930
931    #[cfg(feature = "rich-widgets")]
932    pub fn textarea_cursor_visible(&self, id: WidgetId) -> Option<bool> {
933        match self.node(id)?.kind {
934            WidgetKind::TextArea { cursor_visible, .. } => Some(cursor_visible),
935            _ => None,
936        }
937    }
938
939    #[cfg(feature = "rich-widgets")]
940    pub fn set_textarea_capabilities(
941        &mut self,
942        id: WidgetId,
943        read_only: bool,
944        single_line: bool,
945        accept_newline: bool,
946    ) -> Result<(), GuiError> {
947        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
948        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
949        match node.kind {
950            WidgetKind::TextArea {
951                read_only: ref mut ro,
952                single_line: ref mut sl,
953                accept_newline: ref mut an,
954                ..
955            } => {
956                *ro = read_only;
957                *sl = single_line;
958                *an = accept_newline && !single_line;
959                self.dirty.add(rect)?;
960                Ok(())
961            }
962            _ => Err(GuiError::NotFound),
963        }
964    }
965
966    #[cfg(feature = "rich-widgets")]
967    pub fn textarea_insert_char(&mut self, id: WidgetId, ch: char) -> Result<(), GuiError> {
968        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
969        let before = self.capture_textarea_snapshot(id)?;
970        let mut emit = false;
971        if let Some(node) = self.node_mut(id) {
972            if let WidgetKind::TextArea {
973                text_buf,
974                text_len,
975                cursor,
976                selection,
977                read_only,
978                single_line,
979                accept_newline,
980                ..
981            } = &mut node.kind
982            {
983                if *read_only {
984                    return Ok(());
985                }
986                if ch == '\n' && (*single_line || !*accept_newline) {
987                    return Ok(());
988                }
989                let mut chars: heapless::Vec<char, TEXTAREA_CAPACITY> = heapless::Vec::new();
990                for c in textarea_text(text_buf, *text_len).chars() {
991                    let _ = chars.push(c);
992                }
993                let original_len = chars.len();
994                let original_cursor = *cursor;
995
996                if ch == '\u{8}' {
997                    let removed_selection = delete_selection_if_any(&mut chars, cursor, selection);
998                    if !removed_selection && *cursor > 0 && *cursor <= chars.len() {
999                        chars.remove(*cursor - 1);
1000                        *cursor -= 1;
1001                    }
1002                    if removed_selection
1003                        || *cursor != original_cursor
1004                        || chars.len() != original_len
1005                    {
1006                        *selection = None;
1007                        let (next_buf, next_len) = textarea_storage_from_chars(&chars);
1008                        *text_buf = next_buf;
1009                        *text_len = next_len;
1010                        emit = true;
1011                    }
1012                } else if ch == '\u{7f}' {
1013                    let removed_selection = delete_selection_if_any(&mut chars, cursor, selection);
1014                    if !removed_selection && *cursor < chars.len() {
1015                        chars.remove(*cursor);
1016                    }
1017                    if removed_selection || chars.len() != original_len {
1018                        *selection = None;
1019                        let (next_buf, next_len) = textarea_storage_from_chars(&chars);
1020                        *text_buf = next_buf;
1021                        *text_len = next_len;
1022                        emit = true;
1023                    }
1024                } else if ch != '\n' || *cursor < TEXTAREA_CAPACITY {
1025                    if delete_selection_if_any(&mut chars, cursor, selection) {
1026                        *selection = None;
1027                    }
1028                    if chars.len() < TEXTAREA_CAPACITY && *cursor <= chars.len() {
1029                        let _ = chars.insert(*cursor, ch);
1030                        *cursor += 1;
1031                        *selection = None;
1032                        let (next_buf, next_len) = textarea_storage_from_chars(&chars);
1033                        *text_buf = next_buf;
1034                        *text_len = next_len;
1035                        emit = true;
1036                    }
1037                }
1038            } else {
1039                return Err(GuiError::NotFound);
1040            }
1041        }
1042        if emit {
1043            self.push_textarea_undo(id, before);
1044            self.clear_textarea_redo_for(id);
1045            self.dirty.add(rect)?;
1046            self.push_event(UiEvent::TextInput { id, ch })?;
1047            self.push_event(UiEvent::ValueChanged(id))?;
1048        }
1049        Ok(())
1050    }
1051
1052    #[cfg(feature = "rich-widgets")]
1053    pub(crate) fn textarea_line_context(
1054        &self,
1055        id: WidgetId,
1056    ) -> Result<(&str, usize, usize), GuiError> {
1057        let node = self.node(id).ok_or(GuiError::NotFound)?;
1058        match &node.kind {
1059            WidgetKind::TextArea {
1060                text_buf,
1061                text_len,
1062                cursor,
1063                ..
1064            } => {
1065                let font = node.style.normal.font;
1066                let inner_w = node.rect.w.saturating_sub(2);
1067                let cols = (inner_w / font.advance()).max(1) as usize;
1068                Ok((textarea_text(text_buf, *text_len), *cursor, cols))
1069            }
1070            _ => Err(GuiError::NotFound),
1071        }
1072    }
1073
1074    #[cfg(feature = "rich-widgets")]
1075    pub(crate) fn set_textarea_cursor_with_extend(
1076        &mut self,
1077        id: WidgetId,
1078        cursor: usize,
1079        extend_selection: bool,
1080    ) -> Result<(), GuiError> {
1081        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1082        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1083        match node.kind {
1084            WidgetKind::TextArea {
1085                text_buf,
1086                text_len,
1087                cursor: ref mut current_cursor,
1088                ref mut selection,
1089                ..
1090            } => {
1091                let len = textarea_text(&text_buf, text_len).chars().count();
1092                let next = cursor.min(len);
1093                if extend_selection {
1094                    let anchor = match *selection {
1095                        Some((start, end)) => {
1096                            if *current_cursor == start {
1097                                end
1098                            } else {
1099                                start
1100                            }
1101                        }
1102                        None => *current_cursor,
1103                    };
1104                    if anchor == next {
1105                        *selection = None;
1106                    } else {
1107                        *selection = Some((anchor.min(next), anchor.max(next)));
1108                    }
1109                } else {
1110                    *selection = None;
1111                }
1112                *current_cursor = next;
1113                self.dirty.add(rect)?;
1114                Ok(())
1115            }
1116            _ => Err(GuiError::NotFound),
1117        }
1118    }
1119
1120    #[cfg(feature = "rich-widgets")]
1121    pub(crate) fn capture_textarea_snapshot(
1122        &self,
1123        id: WidgetId,
1124    ) -> Result<TextareaSnapshot, GuiError> {
1125        match self.node(id).ok_or(GuiError::NotFound)?.kind {
1126            WidgetKind::TextArea {
1127                text_buf,
1128                text_len,
1129                cursor,
1130                selection,
1131                ..
1132            } => Ok(TextareaSnapshot {
1133                text_buf,
1134                text_len,
1135                cursor,
1136                selection,
1137            }),
1138            _ => Err(GuiError::NotFound),
1139        }
1140    }
1141
1142    #[cfg(feature = "rich-widgets")]
1143    pub(crate) fn apply_textarea_snapshot(
1144        &mut self,
1145        id: WidgetId,
1146        snap: TextareaSnapshot,
1147    ) -> Result<(), GuiError> {
1148        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1149        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1150        match node.kind {
1151            WidgetKind::TextArea {
1152                text_buf: ref mut buf,
1153                text_len: ref mut len,
1154                cursor: ref mut c,
1155                selection: ref mut sel,
1156                ..
1157            } => {
1158                *buf = snap.text_buf;
1159                *len = snap.text_len;
1160                *c = snap.cursor;
1161                *sel = snap.selection;
1162                self.dirty.add(rect)?;
1163                self.push_event(UiEvent::ValueChanged(id))
1164            }
1165            _ => Err(GuiError::NotFound),
1166        }
1167    }
1168
1169    #[cfg(feature = "rich-widgets")]
1170    pub(crate) fn push_textarea_undo(&mut self, id: WidgetId, snapshot: TextareaSnapshot) {
1171        if self.textarea_undo.len() == self.textarea_undo.capacity() {
1172            self.textarea_undo.remove(0);
1173        }
1174        let _ = self
1175            .textarea_undo
1176            .push(TextareaHistoryEntry { id, snapshot });
1177    }
1178
1179    #[cfg(feature = "rich-widgets")]
1180    pub(crate) fn push_textarea_redo(&mut self, id: WidgetId, snapshot: TextareaSnapshot) {
1181        if self.textarea_redo.len() == self.textarea_redo.capacity() {
1182            self.textarea_redo.remove(0);
1183        }
1184        let _ = self
1185            .textarea_redo
1186            .push(TextareaHistoryEntry { id, snapshot });
1187    }
1188
1189    #[cfg(feature = "rich-widgets")]
1190    pub(crate) fn clear_textarea_redo_for(&mut self, id: WidgetId) {
1191        let mut i = 0usize;
1192        while i < self.textarea_redo.len() {
1193            if self.textarea_redo[i].id == id {
1194                self.textarea_redo.remove(i);
1195            } else {
1196                i += 1;
1197            }
1198        }
1199    }
1200
1201    #[cfg(feature = "rich-widgets")]
1202    pub(crate) fn textarea_undo(&mut self, id: WidgetId) -> Result<(), GuiError> {
1203        let Some(pos) = self.textarea_undo.iter().rposition(|entry| entry.id == id) else {
1204            return Ok(());
1205        };
1206        let current = self.capture_textarea_snapshot(id)?;
1207        let prior = self.textarea_undo.remove(pos).snapshot;
1208        self.push_textarea_redo(id, current);
1209        self.apply_textarea_snapshot(id, prior)
1210    }
1211
1212    #[cfg(feature = "rich-widgets")]
1213    pub(crate) fn textarea_redo(&mut self, id: WidgetId) -> Result<(), GuiError> {
1214        let Some(pos) = self.textarea_redo.iter().rposition(|entry| entry.id == id) else {
1215            return Ok(());
1216        };
1217        let current = self.capture_textarea_snapshot(id)?;
1218        let next = self.textarea_redo.remove(pos).snapshot;
1219        self.push_textarea_undo(id, current);
1220        self.apply_textarea_snapshot(id, next)
1221    }
1222
1223    #[cfg(feature = "rich-widgets")]
1224    pub fn textarea_backspace(&mut self, id: WidgetId) -> Result<(), GuiError> {
1225        self.textarea_insert_char(id, '\u{8}')
1226    }
1227
1228    #[cfg(feature = "rich-widgets")]
1229    pub fn textarea_delete_forward(&mut self, id: WidgetId) -> Result<(), GuiError> {
1230        self.textarea_insert_char(id, '\u{7f}')
1231    }
1232
1233    #[cfg(feature = "rich-widgets")]
1234    pub fn keyboard_selected_key(&self, id: WidgetId) -> Option<char> {
1235        match self.node(id)?.kind {
1236            WidgetKind::Keyboard {
1237                keys,
1238                alt_keys,
1239                selected,
1240                layout,
1241                ..
1242            } => keyboard_char_for_layout(keys, alt_keys, selected, layout),
1243            _ => None,
1244        }
1245    }
1246
1247    #[cfg(feature = "rich-widgets")]
1248    pub fn keyboard_layout(&self, id: WidgetId) -> Option<KeyboardLayout> {
1249        match self.node(id)?.kind {
1250            WidgetKind::Keyboard { layout, .. } => Some(layout),
1251            _ => None,
1252        }
1253    }
1254
1255    #[cfg(feature = "rich-widgets")]
1256    pub fn set_keyboard_layout(
1257        &mut self,
1258        id: WidgetId,
1259        layout: KeyboardLayout,
1260    ) -> Result<(), GuiError> {
1261        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1262        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1263        match node.kind {
1264            WidgetKind::Keyboard {
1265                layout: ref mut current,
1266                ..
1267            } => {
1268                *current = layout;
1269                self.dirty.add(rect)?;
1270                Ok(())
1271            }
1272            _ => Err(GuiError::NotFound),
1273        }
1274    }
1275
1276    #[cfg(feature = "rich-widgets")]
1277    pub fn set_keyboard_target(
1278        &mut self,
1279        id: WidgetId,
1280        target: Option<WidgetId>,
1281    ) -> Result<(), GuiError> {
1282        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1283        match node.kind {
1284            WidgetKind::Keyboard {
1285                target: ref mut current,
1286                ..
1287            } => {
1288                *current = target;
1289                Ok(())
1290            }
1291            _ => Err(GuiError::NotFound),
1292        }
1293    }
1294
1295    #[cfg(feature = "rich-widgets")]
1296    pub fn set_gauge_value(&mut self, id: WidgetId, value: f32) -> Result<(), GuiError> {
1297        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1298        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1299        match node.kind {
1300            WidgetKind::Gauge {
1301                value: ref mut v,
1302                min,
1303                max,
1304                ..
1305            }
1306            | WidgetKind::ArcGauge {
1307                value: ref mut v,
1308                min,
1309                max,
1310                ..
1311            }
1312            | WidgetKind::GaugeNeedle {
1313                value: ref mut v,
1314                min,
1315                max,
1316                ..
1317            } => {
1318                *v = value.clamp(min.min(max), min.max(max));
1319                self.dirty.add(rect)?;
1320                Ok(())
1321            }
1322            _ => Err(GuiError::NotFound),
1323        }
1324    }
1325
1326    #[cfg(feature = "rich-widgets")]
1327    pub fn set_gauge_ticks(
1328        &mut self,
1329        id: WidgetId,
1330        major_ticks: u8,
1331        minor_ticks: u8,
1332        show_value: bool,
1333    ) -> Result<(), GuiError> {
1334        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1335        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1336        match node.kind {
1337            WidgetKind::Gauge {
1338                major_ticks: ref mut major,
1339                minor_ticks: ref mut minor,
1340                show_value: ref mut show,
1341                ..
1342            }
1343            | WidgetKind::ArcGauge {
1344                major_ticks: ref mut major,
1345                minor_ticks: ref mut minor,
1346                show_value: ref mut show,
1347                ..
1348            } => {
1349                *major = major_ticks.max(1);
1350                *minor = minor_ticks.max(1);
1351                *show = show_value;
1352                self.dirty.add(rect)?;
1353                Ok(())
1354            }
1355            _ => Err(GuiError::NotFound),
1356        }
1357    }
1358
1359    pub fn set_widget_rect(&mut self, id: WidgetId, rect: Rect) -> Result<(), GuiError> {
1360        let old = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1361        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1362        node.rect = rect;
1363        self.dirty.add(old)?;
1364        self.mark_subtree_dirty(id)?;
1365        Ok(())
1366    }
1367
1368    pub fn set_widget_x(&mut self, id: WidgetId, x: i32) -> Result<(), GuiError> {
1369        let mut rect = self.node(id).ok_or(GuiError::NotFound)?.rect;
1370        rect.x = x;
1371        self.set_widget_rect(id, rect)
1372    }
1373
1374    pub fn set_widget_y(&mut self, id: WidgetId, y: i32) -> Result<(), GuiError> {
1375        let mut rect = self.node(id).ok_or(GuiError::NotFound)?.rect;
1376        rect.y = y;
1377        self.set_widget_rect(id, rect)
1378    }
1379
1380    pub fn set_widget_width(&mut self, id: WidgetId, w: u32) -> Result<(), GuiError> {
1381        let mut rect = self.node(id).ok_or(GuiError::NotFound)?.rect;
1382        rect.w = w.max(1);
1383        self.set_widget_rect(id, rect)
1384    }
1385
1386    pub fn set_widget_height(&mut self, id: WidgetId, h: u32) -> Result<(), GuiError> {
1387        let mut rect = self.node(id).ok_or(GuiError::NotFound)?.rect;
1388        rect.h = h.max(1);
1389        self.set_widget_rect(id, rect)
1390    }
1391
1392    pub fn set_widget_opacity(&mut self, id: WidgetId, opacity: u8) -> Result<(), GuiError> {
1393        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1394        node.style.normal.opacity = opacity;
1395        node.style.focused.opacity = opacity;
1396        node.style.pressed.opacity = opacity;
1397        node.style.disabled.opacity = opacity;
1398        self.mark_subtree_dirty(id)
1399    }
1400
1401    pub fn set_widget_corner_radius(&mut self, id: WidgetId, radius: u8) -> Result<(), GuiError> {
1402        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1403        node.style.normal.corner_radius = radius;
1404        node.style.focused.corner_radius = radius;
1405        node.style.pressed.corner_radius = radius;
1406        node.style.disabled.corner_radius = radius;
1407        self.mark_subtree_dirty(id)
1408    }
1409
1410    pub fn set_widget_accent(&mut self, id: WidgetId, accent: Rgb565) -> Result<(), GuiError> {
1411        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1412        node.style.normal.accent = accent;
1413        node.style.focused.accent = accent;
1414        node.style.pressed.accent = accent;
1415        node.style.disabled.accent = accent;
1416        self.mark_subtree_dirty(id)
1417    }
1418
1419    pub fn set_widget_parent(
1420        &mut self,
1421        id: WidgetId,
1422        parent: Option<WidgetId>,
1423    ) -> Result<(), GuiError> {
1424        if let Some(parent) = parent {
1425            self.node(parent).ok_or(GuiError::NotFound)?;
1426        }
1427        self.node_mut(id).ok_or(GuiError::NotFound)?.parent = parent;
1428        self.mark_subtree_dirty(id)?;
1429        Ok(())
1430    }
1431
1432    pub fn add_child(&mut self, parent: WidgetId, child: WidgetId) -> Result<(), GuiError> {
1433        self.set_widget_parent(child, Some(parent))
1434    }
1435
1436    pub fn children_of(&self, parent: WidgetId) -> impl Iterator<Item = &WidgetNode<'a>> + '_ {
1437        self.widgets
1438            .iter()
1439            .filter(move |node| node.parent == Some(parent))
1440    }
1441
1442    #[inline]
1443    pub fn absolute_rect(&self, id: WidgetId) -> Option<Rect> {
1444        let node = self.node(id)?;
1445        if node.parent.is_none() {
1446            return Some(node.rect);
1447        }
1448        let mut rect = node.rect;
1449        let mut parent = node.parent;
1450        let mut depth = 0;
1451        while let Some(parent_id) = parent {
1452            if depth >= NODES {
1453                return None;
1454            }
1455            let parent_node = self.node(parent_id)?;
1456            rect.x += parent_node.rect.x;
1457            rect.y += parent_node.rect.y;
1458            parent = parent_node.parent;
1459            depth += 1;
1460        }
1461        Some(rect)
1462    }
1463
1464    pub fn set_flag(
1465        &mut self,
1466        id: WidgetId,
1467        flag: WidgetFlags,
1468        enabled: bool,
1469    ) -> Result<(), GuiError> {
1470        let was_set = self.has_flag(id, flag)?;
1471        let before_state = self.current_visual_state(id);
1472        self.mark_subtree_dirty(id)?;
1473        self.node_mut(id)
1474            .ok_or(GuiError::NotFound)?
1475            .flags
1476            .set(flag, enabled);
1477        if flag == WidgetFlags::DISABLED
1478            && enabled
1479            && self.pressed.is_some_and(|pressed| pressed.id == id)
1480        {
1481            self.pressed = None;
1482        }
1483        self.mark_subtree_dirty(id)?;
1484        if self
1485            .focus
1486            .is_some_and(|focus| !self.effective_focusable(focus))
1487        {
1488            self.focus = None;
1489            self.ensure_focus();
1490        }
1491        if flag == WidgetFlags::DISABLED && was_set != enabled {
1492            let after_state = self.current_visual_state(id);
1493            self.start_state_transition(id, before_state, after_state);
1494        }
1495        Ok(())
1496    }
1497
1498    pub fn has_flag(&self, id: WidgetId, flag: WidgetFlags) -> Result<bool, GuiError> {
1499        Ok(self
1500            .node(id)
1501            .ok_or(GuiError::NotFound)?
1502            .flags
1503            .contains(flag))
1504    }
1505
1506    pub fn insert_flag(&mut self, id: WidgetId, flag: WidgetFlags) -> Result<(), GuiError> {
1507        self.set_flag(id, flag, true)
1508    }
1509
1510    pub fn remove_flag(&mut self, id: WidgetId, flag: WidgetFlags) -> Result<(), GuiError> {
1511        self.set_flag(id, flag, false)
1512    }
1513
1514    pub fn set_hidden(&mut self, id: WidgetId, hidden: bool) -> Result<(), GuiError> {
1515        self.set_flag(id, WidgetFlags::HIDDEN, hidden)
1516    }
1517
1518    pub fn set_disabled(&mut self, id: WidgetId, disabled: bool) -> Result<(), GuiError> {
1519        self.set_flag(id, WidgetFlags::DISABLED, disabled)
1520    }
1521
1522    pub fn set_clickable(&mut self, id: WidgetId, clickable: bool) -> Result<(), GuiError> {
1523        self.set_flag(id, WidgetFlags::CLICKABLE, clickable)
1524    }
1525
1526    pub fn set_scrollable(&mut self, id: WidgetId, scrollable: bool) -> Result<(), GuiError> {
1527        self.set_flag(id, WidgetFlags::SCROLLABLE, scrollable)
1528    }
1529
1530    pub fn set_visible(&mut self, id: WidgetId, visible: bool) -> Result<(), GuiError> {
1531        self.set_hidden(id, !visible)
1532    }
1533
1534    pub fn set_enabled(&mut self, id: WidgetId, enabled: bool) -> Result<(), GuiError> {
1535        self.set_disabled(id, !enabled)
1536    }
1537
1538    pub fn event_path<const M: usize>(
1539        &self,
1540        target: WidgetId,
1541        out: &mut heapless::Vec<EventContext, M>,
1542    ) -> Result<usize, GuiError> {
1543        self.node(target).ok_or(GuiError::NotFound)?;
1544        out.clear();
1545
1546        let mut chain = heapless::Vec::<WidgetId, NODES>::new();
1547        let mut current = Some(target);
1548        while let Some(id) = current {
1549            chain.push(id).map_err(|_| GuiError::WidgetsFull)?;
1550            current = self.node(id).ok_or(GuiError::NotFound)?.parent;
1551        }
1552
1553        for id in chain.iter().rev().copied().filter(|&id| id != target) {
1554            out.push(EventContext {
1555                target,
1556                current: id,
1557                phase: EventPhase::Capture,
1558            })
1559            .map_err(|_| GuiError::EventsFull)?;
1560        }
1561
1562        out.push(EventContext {
1563            target,
1564            current: target,
1565            phase: EventPhase::Target,
1566        })
1567        .map_err(|_| GuiError::EventsFull)?;
1568
1569        for id in chain.iter().copied().skip(1) {
1570            out.push(EventContext {
1571                target,
1572                current: id,
1573                phase: EventPhase::Bubble,
1574            })
1575            .map_err(|_| GuiError::EventsFull)?;
1576        }
1577
1578        Ok(out.len())
1579    }
1580
1581    pub fn widget_event_path<const M: usize>(
1582        &self,
1583        target: WidgetId,
1584        kind: WidgetEventKind,
1585        out: &mut heapless::Vec<WidgetEvent, M>,
1586    ) -> Result<usize, GuiError> {
1587        self.node(target).ok_or(GuiError::NotFound)?;
1588        out.clear();
1589
1590        let mut chain = heapless::Vec::<WidgetId, NODES>::new();
1591        let mut current = Some(target);
1592        while let Some(id) = current {
1593            chain.push(id).map_err(|_| GuiError::WidgetsFull)?;
1594            current = self.node(id).ok_or(GuiError::NotFound)?.parent;
1595        }
1596
1597        for id in chain.iter().rev().copied().filter(|&id| id != target) {
1598            out.push(WidgetEvent {
1599                target,
1600                current: id,
1601                phase: EventPhase::Capture,
1602                kind,
1603            })
1604            .map_err(|_| GuiError::EventsFull)?;
1605        }
1606
1607        out.push(WidgetEvent {
1608            target,
1609            current: target,
1610            phase: EventPhase::Target,
1611            kind,
1612        })
1613        .map_err(|_| GuiError::EventsFull)?;
1614
1615        if self.has_flag(target, WidgetFlags::EVENT_BUBBLE)? {
1616            for id in chain.iter().copied().skip(1) {
1617                out.push(WidgetEvent {
1618                    target,
1619                    current: id,
1620                    phase: EventPhase::Bubble,
1621                    kind,
1622                })
1623                .map_err(|_| GuiError::EventsFull)?;
1624            }
1625        }
1626
1627        Ok(out.len())
1628    }
1629
1630    pub fn dispatch_widget_event<const M: usize, F>(
1631        &self,
1632        target: WidgetId,
1633        kind: WidgetEventKind,
1634        scratch: &mut heapless::Vec<WidgetEvent, M>,
1635        mut handler: F,
1636    ) -> Result<(), GuiError>
1637    where
1638        F: FnMut(WidgetEvent) -> EventPolicy,
1639    {
1640        self.widget_event_path(target, kind, scratch)?;
1641        for event in scratch.iter().copied() {
1642            let handler_policy = handler(event);
1643            if matches!(handler_policy, EventPolicy::Stop)
1644                || self.stop_due_to_builtin_widget_behavior(event)
1645                || self.stop_due_to_registered_policy(event)
1646            {
1647                break;
1648            }
1649        }
1650        Ok(())
1651    }
1652
1653    pub fn mark_subtree_dirty(&mut self, id: WidgetId) -> Result<(), GuiError> {
1654        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1655        self.dirty.add(rect)?;
1656        let child_ids: heapless::Vec<WidgetId, NODES> = self
1657            .widgets
1658            .iter()
1659            .filter(|node| node.parent == Some(id))
1660            .map(|node| node.id)
1661            .collect();
1662        for child in child_ids {
1663            self.mark_subtree_dirty(child)?;
1664        }
1665        Ok(())
1666    }
1667
1668    pub fn set_focus_group(&mut self, id: WidgetId, group: FocusGroupId) -> Result<(), GuiError> {
1669        self.node_mut(id).ok_or(GuiError::NotFound)?.focus_group = group;
1670        Ok(())
1671    }
1672
1673    pub fn set_active_focus_group(&mut self, group: Option<FocusGroupId>) {
1674        self.active_focus_group = group;
1675        if let Some(focus) = self.focus {
1676            let still_valid = self.node(focus).is_some_and(|node| {
1677                group.is_none_or(|active| node.focus_group == active)
1678                    && self.effective_focusable(focus)
1679            });
1680            if !still_valid {
1681                self.focus = None;
1682                self.ensure_focus();
1683            }
1684        }
1685    }
1686
1687    pub fn apply_layout(
1688        &mut self,
1689        layout: LinearLayout,
1690        area: Rect,
1691        ids: &[WidgetId],
1692    ) -> Result<usize, GuiError> {
1693        let mut rects = [Rect::empty(); 16];
1694        let count = layout.arrange(area, ids.len().min(rects.len()), &mut rects);
1695        for (id, rect) in ids.iter().copied().zip(rects).take(count) {
1696            self.set_widget_rect(id, rect)?;
1697        }
1698        Ok(count)
1699    }
1700
1701    pub fn apply_layout_flex(
1702        &mut self,
1703        layout: LinearLayout,
1704        area: Rect,
1705        ids: &[WidgetId],
1706        items: &[LayoutItem],
1707        enable_grow: bool,
1708        enable_shrink: bool,
1709    ) -> Result<usize, GuiError> {
1710        let mut rects = [Rect::empty(); 16];
1711        let count = ids.len().min(items.len()).min(rects.len());
1712        let laid_out = layout.arrange_items_flex(
1713            area,
1714            &items[..count],
1715            &mut rects,
1716            enable_grow,
1717            enable_shrink,
1718        );
1719        for (id, rect) in ids.iter().copied().zip(rects).take(laid_out) {
1720            self.set_widget_rect(id, rect)?;
1721        }
1722        Ok(laid_out)
1723    }
1724
1725    pub fn apply_layout_intrinsic(
1726        &mut self,
1727        layout: LinearLayout,
1728        area: Rect,
1729        ids: &[WidgetId],
1730    ) -> Result<usize, GuiError> {
1731        self.apply_layout_intrinsic_with_cross(layout, area, ids, false)
1732    }
1733
1734    pub fn apply_layout_intrinsic_with_cross(
1735        &mut self,
1736        layout: LinearLayout,
1737        area: Rect,
1738        ids: &[WidgetId],
1739        preserve_cross: bool,
1740    ) -> Result<usize, GuiError> {
1741        let mut specs = [LayoutItem::fill(); 16];
1742        let mut rects = [Rect::empty(); 16];
1743        let count = ids.len().min(specs.len()).min(rects.len());
1744
1745        for (idx, id) in ids.iter().copied().take(count).enumerate() {
1746            let (w, h) = self.intrinsic_size(id).ok_or(GuiError::NotFound)?;
1747            specs[idx] = match layout.axis {
1748                Axis::Horizontal => LayoutItem::length(w).with_cross(if preserve_cross {
1749                    crate::layout::Constraint::Length(h)
1750                } else {
1751                    crate::layout::Constraint::Fill(1)
1752                }),
1753                Axis::Vertical => LayoutItem::length(h).with_cross(if preserve_cross {
1754                    crate::layout::Constraint::Length(w)
1755                } else {
1756                    crate::layout::Constraint::Fill(1)
1757                }),
1758            };
1759        }
1760
1761        let laid_out = layout.arrange_items(area, &specs[..count], &mut rects);
1762        for (id, rect) in ids.iter().copied().zip(rects).take(laid_out) {
1763            self.set_widget_rect(id, rect)?;
1764        }
1765        Ok(laid_out)
1766    }
1767
1768    pub fn set_dial_value(&mut self, id: WidgetId, value: f32) -> Result<(), GuiError> {
1769        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1770        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1771        match node.kind {
1772            WidgetKind::Dial {
1773                value: ref mut v,
1774                min,
1775                max,
1776            } => {
1777                *v = value.clamp(min, max);
1778                self.dirty.add(rect)?;
1779                Ok(())
1780            }
1781            _ => Err(GuiError::NotFound),
1782        }
1783    }
1784
1785    pub fn dial_value(&self, id: WidgetId) -> Option<f32> {
1786        match self.node(id)?.kind {
1787            WidgetKind::Dial { value, .. } => Some(value),
1788            _ => None,
1789        }
1790    }
1791
1792    pub fn autocomplete_text(&self, id: WidgetId) -> Option<&str> {
1793        match &self.node(id)?.kind {
1794            WidgetKind::AutoComplete {
1795                text_buf, text_len, ..
1796            } => core::str::from_utf8(&text_buf[..*text_len as usize]).ok(),
1797            _ => None,
1798        }
1799    }
1800
1801    pub fn set_autocomplete_text(&mut self, id: WidgetId, text: &str) -> Result<(), GuiError> {
1802        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1803        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1804        if let WidgetKind::AutoComplete {
1805            text_buf,
1806            text_len,
1807            suggestions,
1808            filtered,
1809            filter_count,
1810            selected,
1811            expanded,
1812        } = &mut node.kind
1813        {
1814            let len = text.len().min(text_buf.len());
1815            text_buf[..len].copy_from_slice(&text.as_bytes()[..len]);
1816            *text_len = len as u8;
1817            *selected = None;
1818            *expanded = false;
1819            filter_suggestions(text, suggestions, filtered, filter_count);
1820            self.dirty.add(rect)?;
1821            self.push_event(UiEvent::ValueChanged(id))?;
1822        }
1823        Ok(())
1824    }
1825
1826    pub fn insert_autocomplete_char(&mut self, id: WidgetId, ch: char) -> Result<(), GuiError> {
1827        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1828        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1829        if let WidgetKind::AutoComplete {
1830            text_buf,
1831            text_len,
1832            suggestions,
1833            filtered,
1834            filter_count,
1835            selected,
1836            expanded,
1837        } = &mut node.kind
1838        {
1839            if (*text_len as usize) < text_buf.len() {
1840                text_buf[*text_len as usize] = ch as u8;
1841                *text_len += 1;
1842                *expanded = true;
1843                *selected = None;
1844                if let Ok(current_text) = core::str::from_utf8(&text_buf[..*text_len as usize]) {
1845                    filter_suggestions(current_text, suggestions, filtered, filter_count);
1846                }
1847                self.dirty.add(rect)?;
1848                self.push_event(UiEvent::ValueChanged(id))?;
1849            }
1850        }
1851        Ok(())
1852    }
1853
1854    pub fn delete_autocomplete_char(&mut self, id: WidgetId) -> Result<(), GuiError> {
1855        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1856        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1857        if let WidgetKind::AutoComplete {
1858            text_buf,
1859            text_len,
1860            suggestions,
1861            filtered,
1862            filter_count,
1863            selected,
1864            expanded,
1865        } = &mut node.kind
1866        {
1867            if *text_len > 0 {
1868                *text_len -= 1;
1869                *expanded = true;
1870                *selected = None;
1871                if let Ok(current_text) = core::str::from_utf8(&text_buf[..*text_len as usize]) {
1872                    filter_suggestions(current_text, suggestions, filtered, filter_count);
1873                }
1874                self.dirty.add(rect)?;
1875                self.push_event(UiEvent::ValueChanged(id))?;
1876            }
1877        }
1878        Ok(())
1879    }
1880
1881    pub fn autocomplete_confirm_selection(&mut self, id: WidgetId) -> Result<(), GuiError> {
1882        let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1883        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1884        if let WidgetKind::AutoComplete {
1885            text_buf,
1886            text_len,
1887            filtered,
1888            filter_count,
1889            selected,
1890            expanded,
1891            ..
1892        } = &mut node.kind
1893        {
1894            if *expanded {
1895                if let Some(sel_idx) = *selected {
1896                    if sel_idx < *filter_count as usize {
1897                        if let Some(selected_text) = filtered[sel_idx] {
1898                            let len = selected_text.len().min(text_buf.len());
1899                            text_buf[..len].copy_from_slice(&selected_text.as_bytes()[..len]);
1900                            *text_len = len as u8;
1901                            *expanded = false;
1902                            *selected = None;
1903                            self.dirty.add(rect)?;
1904                            self.push_event(UiEvent::ValueChanged(id))?;
1905                        }
1906                    }
1907                }
1908            }
1909        }
1910        Ok(())
1911    }
1912}
1913
1914fn contains_ignore_ascii_case(s: &str, needle: &str) -> bool {
1915    if needle.is_empty() {
1916        return true;
1917    }
1918    let s_bytes = s.as_bytes();
1919    let n_bytes = needle.as_bytes();
1920    if n_bytes.len() > s_bytes.len() {
1921        return false;
1922    }
1923    s_bytes.windows(n_bytes.len()).any(|window| {
1924        window
1925            .iter()
1926            .zip(n_bytes.iter())
1927            .all(|(a, b)| a.eq_ignore_ascii_case(b))
1928    })
1929}
1930
1931fn filter_suggestions<'a>(
1932    text: &str,
1933    suggestions: &'a [&'a str],
1934    filtered: &mut [Option<&'a str>; 8],
1935    filter_count: &mut u8,
1936) {
1937    *filter_count = 0;
1938    for slot in filtered.iter_mut() {
1939        *slot = None;
1940    }
1941    if text.is_empty() {
1942        return;
1943    }
1944    for &s in suggestions {
1945        if contains_ignore_ascii_case(s, text) {
1946            filtered[*filter_count as usize] = Some(s);
1947            *filter_count += 1;
1948            if *filter_count == 8 {
1949                break;
1950            }
1951        }
1952    }
1953}