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, .. } => Some(selected),
306 _ => None,
307 }
308 }
309
310 #[cfg(feature = "rich-widgets")]
311 pub fn set_list_selected(&mut self, id: WidgetId, selected: usize) -> Result<(), GuiError> {
312 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
313 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
314 match node.kind {
315 WidgetKind::List {
316 items,
317 selected: ref mut current,
318 ref mut offset,
319 visible_rows,
320 } => {
321 let mut state = ListState::new(*current, *offset, visible_rows);
322 state.set_selected(selected, items.len());
323 *current = state.selected;
324 *offset = state.offset;
325 self.dirty.add(rect)?;
326 Ok(())
327 }
328 _ => Err(GuiError::NotFound),
329 }
330 }
331
332 #[cfg(feature = "rich-widgets")]
333 pub fn feed_selected(&self, id: WidgetId) -> Option<usize> {
334 match self.node(id)?.kind {
335 WidgetKind::FeedTimeline { selected, .. } => Some(selected),
336 _ => None,
337 }
338 }
339
340 #[cfg(feature = "rich-widgets")]
341 pub fn set_feed_selected(&mut self, id: WidgetId, selected: usize) -> Result<(), GuiError> {
342 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
343 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
344 match node.kind {
345 WidgetKind::FeedTimeline {
346 items,
347 selected: ref mut current,
348 ref mut offset,
349 visible_rows,
350 ..
351 } => {
352 let mut state = FeedTimelineState::new(*current, *offset, visible_rows, false);
353 state.set_selected(selected, items.len());
354 *current = state.selected;
355 *offset = state.offset;
356 self.dirty.add(rect)?;
357 Ok(())
358 }
359 _ => Err(GuiError::NotFound),
360 }
361 }
362
363 #[cfg(feature = "rich-widgets")]
364 pub fn set_feed_expanded(&mut self, id: WidgetId, expanded: bool) -> Result<(), GuiError> {
365 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
366 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
367 match node.kind {
368 WidgetKind::FeedTimeline {
369 expanded: ref mut current,
370 ..
371 } => {
372 *current = expanded;
373 self.dirty.add(rect)?;
374 Ok(())
375 }
376 _ => Err(GuiError::NotFound),
377 }
378 }
379
380 #[cfg(feature = "rich-widgets")]
381 pub fn set_toggle(&mut self, id: WidgetId, on: bool) -> Result<(), GuiError> {
382 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
383 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
384 match node.kind {
385 WidgetKind::Toggle { on: ref mut v, .. } => {
386 *v = on;
387 self.dirty.add(rect)?;
388 Ok(())
389 }
390 _ => Err(GuiError::NotFound),
391 }
392 }
393
394 #[cfg(feature = "rich-widgets")]
395 pub fn toggle_value(&self, id: WidgetId) -> Option<bool> {
396 match self.node(id)?.kind {
397 WidgetKind::Toggle { on, .. } => Some(on),
398 _ => None,
399 }
400 }
401
402 #[cfg(feature = "rich-widgets")]
403 pub fn set_checked(&mut self, id: WidgetId, checked: 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::Checkbox {
408 checked: ref mut v, ..
409 } => {
410 *v = checked;
411 self.dirty.add(rect)?;
412 Ok(())
413 }
414 _ => Err(GuiError::NotFound),
415 }
416 }
417
418 #[cfg(feature = "rich-widgets")]
419 pub fn checked_value(&self, id: WidgetId) -> Option<bool> {
420 match self.node(id)?.kind {
421 WidgetKind::Checkbox { checked, .. } => Some(checked),
422 _ => None,
423 }
424 }
425
426 #[cfg(feature = "rich-widgets")]
427 pub fn set_slider_value(&mut self, id: WidgetId, value: f32) -> Result<(), GuiError> {
428 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
429 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
430 match node.kind {
431 WidgetKind::Slider {
432 value: ref mut v,
433 min,
434 max,
435 } => {
436 let mut state = SliderState::new(*v, min, max);
437 state.set_value(value);
438 *v = state.value;
439 self.dirty.add(rect)?;
440 Ok(())
441 }
442 _ => Err(GuiError::NotFound),
443 }
444 }
445
446 #[cfg(feature = "rich-widgets")]
447 pub fn slider_value(&self, id: WidgetId) -> Option<f32> {
448 match self.node(id)?.kind {
449 WidgetKind::Slider { value, .. } => Some(value),
450 _ => None,
451 }
452 }
453
454 #[cfg(feature = "rich-widgets")]
455 pub fn set_value_label(&mut self, id: WidgetId, value: i32) -> Result<(), GuiError> {
456 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
457 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
458 match node.kind {
459 WidgetKind::ValueLabel {
460 value: ref mut v, ..
461 } => {
462 *v = value;
463 self.dirty.add(rect)?;
464 Ok(())
465 }
466 _ => Err(GuiError::NotFound),
467 }
468 }
469
470 #[cfg(feature = "rich-widgets")]
471 pub fn set_scroll_offset(&mut self, id: WidgetId, offset_y: i32) -> Result<(), GuiError> {
472 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
473 match node.kind {
474 WidgetKind::ScrollView {
475 offset_y: ref mut v,
476 content_h,
477 } => {
478 let mut state = ScrollState::new(*v, content_h);
479 state.set_offset(offset_y);
480 *v = state.offset_y;
481 self.mark_subtree_dirty(id)?;
482 Ok(())
483 }
484 _ => Err(GuiError::NotFound),
485 }
486 }
487
488 #[cfg(feature = "rich-widgets")]
489 pub fn scroll_offset(&self, id: WidgetId) -> Option<i32> {
490 match self.node(id)?.kind {
491 WidgetKind::ScrollView { offset_y, .. } => Some(offset_y),
492 _ => None,
493 }
494 }
495
496 #[cfg(feature = "rich-widgets")]
497 pub fn set_tab_selected(&mut self, id: WidgetId, selected: usize) -> Result<(), GuiError> {
498 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
499 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
500 match node.kind {
501 WidgetKind::Tabs {
502 labels,
503 selected: ref mut v,
504 } => {
505 let mut state = TabsState::new(*v);
506 state.set_selected(selected, labels.len());
507 *v = state.selected;
508 self.dirty.add(rect)?;
509 Ok(())
510 }
511 _ => Err(GuiError::NotFound),
512 }
513 }
514
515 #[cfg(feature = "rich-widgets")]
516 pub fn tab_selected(&self, id: WidgetId) -> Option<usize> {
517 match self.node(id)?.kind {
518 WidgetKind::Tabs { selected, .. } => Some(selected),
519 _ => None,
520 }
521 }
522
523 #[cfg(feature = "rich-widgets")]
524 pub fn set_toast_ttl(&mut self, id: WidgetId, ttl_ms: u32) -> Result<(), GuiError> {
525 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
526 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
527 match node.kind {
528 WidgetKind::Toast {
529 ttl_ms: ref mut v, ..
530 } => {
531 *v = ttl_ms;
532 self.dirty.add(rect)?;
533 Ok(())
534 }
535 _ => Err(GuiError::NotFound),
536 }
537 }
538
539 #[cfg(feature = "rich-widgets")]
540 pub fn tick_toast(&mut self, id: WidgetId, dt_ms: u32) -> Result<(), GuiError> {
541 let ttl = match self.node(id).ok_or(GuiError::NotFound)?.kind {
542 WidgetKind::Toast { ttl_ms, .. } => ttl_ms.saturating_sub(dt_ms),
543 _ => return Err(GuiError::NotFound),
544 };
545 self.set_toast_ttl(id, ttl)
546 }
547
548 #[cfg(feature = "rich-widgets")]
549 pub fn set_meter_value(&mut self, id: WidgetId, value: f32) -> Result<(), GuiError> {
550 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
551 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
552 match node.kind {
553 WidgetKind::Meter {
554 value: ref mut v,
555 min,
556 max,
557 } => {
558 *v = value.clamp(min.min(max), min.max(max));
559 self.dirty.add(rect)?;
560 Ok(())
561 }
562 _ => Err(GuiError::NotFound),
563 }
564 }
565
566 #[cfg(feature = "rich-widgets")]
567 pub fn set_spinner_phase(&mut self, id: WidgetId, phase: f32) -> Result<(), GuiError> {
568 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
569 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
570 match node.kind {
571 WidgetKind::Spinner { phase: ref mut v } => {
572 *v = phase;
573 self.dirty.add(rect)?;
574 Ok(())
575 }
576 _ => Err(GuiError::NotFound),
577 }
578 }
579
580 #[cfg(feature = "rich-widgets")]
581 pub fn tick_spinner(
582 &mut self,
583 id: WidgetId,
584 dt_ms: u32,
585 cycles_per_sec: f32,
586 ) -> Result<(), GuiError> {
587 let phase = match self.node(id).ok_or(GuiError::NotFound)?.kind {
588 WidgetKind::Spinner { phase } => phase + (dt_ms as f32 / 1000.0) * cycles_per_sec,
589 _ => return Err(GuiError::NotFound),
590 };
591 self.set_spinner_phase(id, phase)
592 }
593
594 #[cfg(feature = "rich-widgets")]
595 pub fn set_dropdown_selected(&mut self, id: WidgetId, selected: usize) -> Result<(), GuiError> {
596 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
597 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
598 match node.kind {
599 WidgetKind::Dropdown {
600 items,
601 selected: ref mut current,
602 ..
603 } => {
604 *current = selected.min(items.len().saturating_sub(1));
605 self.dirty.add(rect)?;
606 Ok(())
607 }
608 _ => Err(GuiError::NotFound),
609 }
610 }
611
612 #[cfg(feature = "rich-widgets")]
613 pub fn dropdown_selected(&self, id: WidgetId) -> Option<usize> {
614 match self.node(id)?.kind {
615 WidgetKind::Dropdown { selected, .. } => Some(selected),
616 _ => None,
617 }
618 }
619
620 #[cfg(feature = "rich-widgets")]
621 pub fn set_dropdown_open(&mut self, id: WidgetId, open: bool) -> Result<(), GuiError> {
622 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
623 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
624 match node.kind {
625 WidgetKind::Dropdown {
626 open: ref mut is_open,
627 ..
628 } => {
629 if *is_open != open {
630 *is_open = open;
631 self.dirty.add(rect)?;
632 self.push_event(if open {
633 UiEvent::Opened(id)
634 } else {
635 UiEvent::Closed(id)
636 })?;
637 }
638 Ok(())
639 }
640 _ => Err(GuiError::NotFound),
641 }
642 }
643
644 #[cfg(feature = "rich-widgets")]
645 pub fn dropdown_open(&self, id: WidgetId) -> Option<bool> {
646 match self.node(id)?.kind {
647 WidgetKind::Dropdown { open, .. } => Some(open),
648 _ => None,
649 }
650 }
651
652 #[cfg(feature = "rich-widgets")]
653 pub fn set_roller_selected(&mut self, id: WidgetId, selected: usize) -> Result<(), GuiError> {
654 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
655 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
656 match node.kind {
657 WidgetKind::Roller {
658 items,
659 selected: ref mut current,
660 } => {
661 *current = selected.min(items.len().saturating_sub(1));
662 self.dirty.add(rect)?;
663 Ok(())
664 }
665 _ => Err(GuiError::NotFound),
666 }
667 }
668
669 #[cfg(feature = "rich-widgets")]
670 pub fn roller_selected(&self, id: WidgetId) -> Option<usize> {
671 match self.node(id)?.kind {
672 WidgetKind::Roller { selected, .. } => Some(selected),
673 _ => None,
674 }
675 }
676
677 #[cfg(feature = "rich-widgets")]
678 pub fn set_textarea_text(&mut self, id: WidgetId, text: &'a str) -> Result<(), GuiError> {
679 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
680 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
681 match node.kind {
682 WidgetKind::TextArea {
683 text_buf: ref mut buf,
684 text_len: ref mut len,
685 cursor: ref mut c,
686 ..
687 } => {
688 let (next_buf, next_len) = textarea_storage_from_str(text);
689 *buf = next_buf;
690 *len = next_len;
691 *c = (*c).min(textarea_text(buf, *len).chars().count());
692 self.dirty.add(rect)?;
693 Ok(())
694 }
695 _ => Err(GuiError::NotFound),
696 }
697 }
698
699 #[cfg(feature = "rich-widgets")]
700 pub fn textarea_text(&self, id: WidgetId) -> Option<&str> {
701 match &self.node(id)?.kind {
702 WidgetKind::TextArea {
703 text_buf, text_len, ..
704 } => Some(textarea_text(text_buf, *text_len)),
705 _ => None,
706 }
707 }
708
709 #[cfg(feature = "rich-widgets")]
710 pub fn set_textarea_cursor(&mut self, id: WidgetId, cursor: usize) -> Result<(), GuiError> {
711 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
712 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
713 match node.kind {
714 WidgetKind::TextArea {
715 text_buf,
716 text_len,
717 cursor: ref mut current,
718 ..
719 } => {
720 let text = textarea_text(&text_buf, text_len);
721 *current = cursor.min(text.chars().count());
722 self.dirty.add(rect)?;
723 Ok(())
724 }
725 _ => Err(GuiError::NotFound),
726 }
727 }
728
729 #[cfg(feature = "rich-widgets")]
730 pub fn move_textarea_cursor(&mut self, id: WidgetId, delta: i8) -> Result<(), GuiError> {
731 let next = self.textarea_cursor(id).ok_or(GuiError::NotFound)? as i32 + delta as i32;
732 self.set_textarea_cursor_with_extend(id, next.max(0) as usize, false)
733 }
734
735 #[cfg(feature = "rich-widgets")]
736 pub fn move_textarea_cursor_select(&mut self, id: WidgetId, delta: i8) -> Result<(), GuiError> {
737 let next = self.textarea_cursor(id).ok_or(GuiError::NotFound)? as i32 + delta as i32;
738 self.set_textarea_cursor_with_extend(id, next.max(0) as usize, true)
739 }
740
741 #[cfg(feature = "rich-widgets")]
742 pub fn move_textarea_cursor_word(&mut self, id: WidgetId, delta: i8) -> Result<(), GuiError> {
743 let (text, cursor) = match &self.node(id).ok_or(GuiError::NotFound)?.kind {
744 WidgetKind::TextArea {
745 text_buf,
746 text_len,
747 cursor,
748 ..
749 } => (textarea_text(text_buf, *text_len), *cursor),
750 _ => return Err(GuiError::NotFound),
751 };
752 let next = if delta >= 0 {
753 next_word_boundary(text, cursor)
754 } else {
755 prev_word_boundary(text, cursor)
756 };
757 self.set_textarea_cursor_with_extend(id, next, false)
758 }
759
760 #[cfg(feature = "rich-widgets")]
761 pub fn move_textarea_cursor_word_select(
762 &mut self,
763 id: WidgetId,
764 delta: i8,
765 ) -> Result<(), GuiError> {
766 let (text, cursor) = match &self.node(id).ok_or(GuiError::NotFound)?.kind {
767 WidgetKind::TextArea {
768 text_buf,
769 text_len,
770 cursor,
771 ..
772 } => (textarea_text(text_buf, *text_len), *cursor),
773 _ => return Err(GuiError::NotFound),
774 };
775 let next = if delta >= 0 {
776 next_word_boundary(text, cursor)
777 } else {
778 prev_word_boundary(text, cursor)
779 };
780 self.set_textarea_cursor_with_extend(id, next, true)
781 }
782
783 #[cfg(feature = "rich-widgets")]
784 pub fn set_textarea_cursor_home(&mut self, id: WidgetId) -> Result<(), GuiError> {
785 self.set_textarea_cursor(id, 0)
786 }
787
788 #[cfg(feature = "rich-widgets")]
789 pub fn set_textarea_cursor_end(&mut self, id: WidgetId) -> Result<(), GuiError> {
790 let len = self
791 .textarea_text(id)
792 .map(|text| text.chars().count())
793 .ok_or(GuiError::NotFound)?;
794 self.set_textarea_cursor(id, len)
795 }
796
797 #[cfg(feature = "rich-widgets")]
798 pub fn set_textarea_cursor_line_home(&mut self, id: WidgetId) -> Result<(), GuiError> {
799 let (text, cursor, wrap_cols) = self.textarea_line_context(id)?;
800 let (row, _) = textarea_row_col_at_cursor(text, cursor, wrap_cols);
801 let next = textarea_cursor_from_row_col(text, row, 0, wrap_cols);
802 self.set_textarea_cursor_with_extend(id, next, false)
803 }
804
805 #[cfg(feature = "rich-widgets")]
806 pub fn set_textarea_cursor_line_home_select(&mut self, id: WidgetId) -> Result<(), GuiError> {
807 let (text, cursor, wrap_cols) = self.textarea_line_context(id)?;
808 let (row, _) = textarea_row_col_at_cursor(text, cursor, wrap_cols);
809 let next = textarea_cursor_from_row_col(text, row, 0, wrap_cols);
810 self.set_textarea_cursor_with_extend(id, next, true)
811 }
812
813 #[cfg(feature = "rich-widgets")]
814 pub fn set_textarea_cursor_line_end(&mut self, id: WidgetId) -> Result<(), GuiError> {
815 let (text, cursor, wrap_cols) = self.textarea_line_context(id)?;
816 let (row, _) = textarea_row_col_at_cursor(text, cursor, wrap_cols);
817 let row_end = textarea_row_end_col(text, row, wrap_cols);
818 let next = textarea_cursor_from_row_col(text, row, row_end, wrap_cols);
819 self.set_textarea_cursor_with_extend(id, next, false)
820 }
821
822 #[cfg(feature = "rich-widgets")]
823 pub fn set_textarea_cursor_line_end_select(&mut self, id: WidgetId) -> Result<(), GuiError> {
824 let (text, cursor, wrap_cols) = self.textarea_line_context(id)?;
825 let (row, _) = textarea_row_col_at_cursor(text, cursor, wrap_cols);
826 let row_end = textarea_row_end_col(text, row, wrap_cols);
827 let next = textarea_cursor_from_row_col(text, row, row_end, wrap_cols);
828 self.set_textarea_cursor_with_extend(id, next, true)
829 }
830
831 #[cfg(feature = "rich-widgets")]
832 pub fn textarea_cursor(&self, id: WidgetId) -> Option<usize> {
833 match self.node(id)?.kind {
834 WidgetKind::TextArea { cursor, .. } => Some(cursor),
835 _ => None,
836 }
837 }
838
839 #[cfg(feature = "rich-widgets")]
840 pub fn set_textarea_selection(
841 &mut self,
842 id: WidgetId,
843 start: usize,
844 end: usize,
845 ) -> Result<(), GuiError> {
846 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
847 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
848 match node.kind {
849 WidgetKind::TextArea {
850 text_buf,
851 text_len,
852 selection: ref mut current,
853 ..
854 } => {
855 let text = textarea_text(&text_buf, text_len);
856 let len = text.chars().count();
857 let start = start.min(len);
858 let end = end.min(len);
859 *current = Some((start.min(end), start.max(end)));
860 self.dirty.add(rect)?;
861 Ok(())
862 }
863 _ => Err(GuiError::NotFound),
864 }
865 }
866
867 #[cfg(feature = "rich-widgets")]
868 pub fn clear_textarea_selection(&mut self, id: WidgetId) -> Result<(), GuiError> {
869 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
870 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
871 match node.kind {
872 WidgetKind::TextArea {
873 selection: ref mut current,
874 ..
875 } => {
876 *current = None;
877 self.dirty.add(rect)?;
878 Ok(())
879 }
880 _ => Err(GuiError::NotFound),
881 }
882 }
883
884 #[cfg(feature = "rich-widgets")]
885 pub fn textarea_selection(&self, id: WidgetId) -> Option<(usize, usize)> {
886 match self.node(id)?.kind {
887 WidgetKind::TextArea { selection, .. } => selection,
888 _ => None,
889 }
890 }
891
892 #[cfg(feature = "rich-widgets")]
893 pub fn textarea_cursor_visible(&self, id: WidgetId) -> Option<bool> {
894 match self.node(id)?.kind {
895 WidgetKind::TextArea { cursor_visible, .. } => Some(cursor_visible),
896 _ => None,
897 }
898 }
899
900 #[cfg(feature = "rich-widgets")]
901 pub fn set_textarea_capabilities(
902 &mut self,
903 id: WidgetId,
904 read_only: bool,
905 single_line: bool,
906 accept_newline: bool,
907 ) -> 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 read_only: ref mut ro,
913 single_line: ref mut sl,
914 accept_newline: ref mut an,
915 ..
916 } => {
917 *ro = read_only;
918 *sl = single_line;
919 *an = accept_newline && !single_line;
920 self.dirty.add(rect)?;
921 Ok(())
922 }
923 _ => Err(GuiError::NotFound),
924 }
925 }
926
927 #[cfg(feature = "rich-widgets")]
928 pub fn textarea_insert_char(&mut self, id: WidgetId, ch: char) -> Result<(), GuiError> {
929 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
930 let before = self.capture_textarea_snapshot(id)?;
931 let mut emit = false;
932 if let Some(node) = self.node_mut(id) {
933 if let WidgetKind::TextArea {
934 text_buf,
935 text_len,
936 cursor,
937 selection,
938 read_only,
939 single_line,
940 accept_newline,
941 ..
942 } = &mut node.kind
943 {
944 if *read_only {
945 return Ok(());
946 }
947 if ch == '\n' && (*single_line || !*accept_newline) {
948 return Ok(());
949 }
950 let mut chars: heapless::Vec<char, TEXTAREA_CAPACITY> = heapless::Vec::new();
951 for c in textarea_text(text_buf, *text_len).chars() {
952 let _ = chars.push(c);
953 }
954 let original_len = chars.len();
955 let original_cursor = *cursor;
956
957 if ch == '\u{8}' {
958 let removed_selection = delete_selection_if_any(&mut chars, cursor, selection);
959 if !removed_selection && *cursor > 0 && *cursor <= chars.len() {
960 chars.remove(*cursor - 1);
961 *cursor -= 1;
962 }
963 if removed_selection
964 || *cursor != original_cursor
965 || chars.len() != original_len
966 {
967 *selection = None;
968 let (next_buf, next_len) = textarea_storage_from_chars(&chars);
969 *text_buf = next_buf;
970 *text_len = next_len;
971 emit = true;
972 }
973 } else if ch == '\u{7f}' {
974 let removed_selection = delete_selection_if_any(&mut chars, cursor, selection);
975 if !removed_selection && *cursor < chars.len() {
976 chars.remove(*cursor);
977 }
978 if removed_selection || chars.len() != original_len {
979 *selection = None;
980 let (next_buf, next_len) = textarea_storage_from_chars(&chars);
981 *text_buf = next_buf;
982 *text_len = next_len;
983 emit = true;
984 }
985 } else if ch != '\n' || *cursor < TEXTAREA_CAPACITY {
986 if delete_selection_if_any(&mut chars, cursor, selection) {
987 *selection = None;
988 }
989 if chars.len() < TEXTAREA_CAPACITY && *cursor <= chars.len() {
990 let _ = chars.insert(*cursor, ch);
991 *cursor += 1;
992 *selection = None;
993 let (next_buf, next_len) = textarea_storage_from_chars(&chars);
994 *text_buf = next_buf;
995 *text_len = next_len;
996 emit = true;
997 }
998 }
999 } else {
1000 return Err(GuiError::NotFound);
1001 }
1002 }
1003 if emit {
1004 self.push_textarea_undo(id, before);
1005 self.clear_textarea_redo_for(id);
1006 self.dirty.add(rect)?;
1007 self.push_event(UiEvent::TextInput { id, ch })?;
1008 self.push_event(UiEvent::ValueChanged(id))?;
1009 }
1010 Ok(())
1011 }
1012
1013 #[cfg(feature = "rich-widgets")]
1014 pub(crate) fn textarea_line_context(
1015 &self,
1016 id: WidgetId,
1017 ) -> Result<(&str, usize, usize), GuiError> {
1018 let node = self.node(id).ok_or(GuiError::NotFound)?;
1019 match &node.kind {
1020 WidgetKind::TextArea {
1021 text_buf,
1022 text_len,
1023 cursor,
1024 ..
1025 } => {
1026 let font = node.style.normal.font;
1027 let inner_w = node.rect.w.saturating_sub(2);
1028 let cols = (inner_w / font.advance()).max(1) as usize;
1029 Ok((textarea_text(text_buf, *text_len), *cursor, cols))
1030 }
1031 _ => Err(GuiError::NotFound),
1032 }
1033 }
1034
1035 #[cfg(feature = "rich-widgets")]
1036 pub(crate) fn set_textarea_cursor_with_extend(
1037 &mut self,
1038 id: WidgetId,
1039 cursor: usize,
1040 extend_selection: bool,
1041 ) -> Result<(), GuiError> {
1042 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1043 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1044 match node.kind {
1045 WidgetKind::TextArea {
1046 text_buf,
1047 text_len,
1048 cursor: ref mut current_cursor,
1049 ref mut selection,
1050 ..
1051 } => {
1052 let len = textarea_text(&text_buf, text_len).chars().count();
1053 let next = cursor.min(len);
1054 if extend_selection {
1055 let anchor = match *selection {
1056 Some((start, end)) => {
1057 if *current_cursor == start {
1058 end
1059 } else {
1060 start
1061 }
1062 }
1063 None => *current_cursor,
1064 };
1065 if anchor == next {
1066 *selection = None;
1067 } else {
1068 *selection = Some((anchor.min(next), anchor.max(next)));
1069 }
1070 } else {
1071 *selection = None;
1072 }
1073 *current_cursor = next;
1074 self.dirty.add(rect)?;
1075 Ok(())
1076 }
1077 _ => Err(GuiError::NotFound),
1078 }
1079 }
1080
1081 #[cfg(feature = "rich-widgets")]
1082 pub(crate) fn capture_textarea_snapshot(
1083 &self,
1084 id: WidgetId,
1085 ) -> Result<TextareaSnapshot, GuiError> {
1086 match self.node(id).ok_or(GuiError::NotFound)?.kind {
1087 WidgetKind::TextArea {
1088 text_buf,
1089 text_len,
1090 cursor,
1091 selection,
1092 ..
1093 } => Ok(TextareaSnapshot {
1094 text_buf,
1095 text_len,
1096 cursor,
1097 selection,
1098 }),
1099 _ => Err(GuiError::NotFound),
1100 }
1101 }
1102
1103 #[cfg(feature = "rich-widgets")]
1104 pub(crate) fn apply_textarea_snapshot(
1105 &mut self,
1106 id: WidgetId,
1107 snap: TextareaSnapshot,
1108 ) -> Result<(), GuiError> {
1109 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1110 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1111 match node.kind {
1112 WidgetKind::TextArea {
1113 text_buf: ref mut buf,
1114 text_len: ref mut len,
1115 cursor: ref mut c,
1116 selection: ref mut sel,
1117 ..
1118 } => {
1119 *buf = snap.text_buf;
1120 *len = snap.text_len;
1121 *c = snap.cursor;
1122 *sel = snap.selection;
1123 self.dirty.add(rect)?;
1124 self.push_event(UiEvent::ValueChanged(id))
1125 }
1126 _ => Err(GuiError::NotFound),
1127 }
1128 }
1129
1130 #[cfg(feature = "rich-widgets")]
1131 pub(crate) fn push_textarea_undo(&mut self, id: WidgetId, snapshot: TextareaSnapshot) {
1132 if self.textarea_undo.len() == self.textarea_undo.capacity() {
1133 self.textarea_undo.remove(0);
1134 }
1135 let _ = self
1136 .textarea_undo
1137 .push(TextareaHistoryEntry { id, snapshot });
1138 }
1139
1140 #[cfg(feature = "rich-widgets")]
1141 pub(crate) fn push_textarea_redo(&mut self, id: WidgetId, snapshot: TextareaSnapshot) {
1142 if self.textarea_redo.len() == self.textarea_redo.capacity() {
1143 self.textarea_redo.remove(0);
1144 }
1145 let _ = self
1146 .textarea_redo
1147 .push(TextareaHistoryEntry { id, snapshot });
1148 }
1149
1150 #[cfg(feature = "rich-widgets")]
1151 pub(crate) fn clear_textarea_redo_for(&mut self, id: WidgetId) {
1152 let mut i = 0usize;
1153 while i < self.textarea_redo.len() {
1154 if self.textarea_redo[i].id == id {
1155 self.textarea_redo.remove(i);
1156 } else {
1157 i += 1;
1158 }
1159 }
1160 }
1161
1162 #[cfg(feature = "rich-widgets")]
1163 pub(crate) fn textarea_undo(&mut self, id: WidgetId) -> Result<(), GuiError> {
1164 let Some(pos) = self.textarea_undo.iter().rposition(|entry| entry.id == id) else {
1165 return Ok(());
1166 };
1167 let current = self.capture_textarea_snapshot(id)?;
1168 let prior = self.textarea_undo.remove(pos).snapshot;
1169 self.push_textarea_redo(id, current);
1170 self.apply_textarea_snapshot(id, prior)
1171 }
1172
1173 #[cfg(feature = "rich-widgets")]
1174 pub(crate) fn textarea_redo(&mut self, id: WidgetId) -> Result<(), GuiError> {
1175 let Some(pos) = self.textarea_redo.iter().rposition(|entry| entry.id == id) else {
1176 return Ok(());
1177 };
1178 let current = self.capture_textarea_snapshot(id)?;
1179 let next = self.textarea_redo.remove(pos).snapshot;
1180 self.push_textarea_undo(id, current);
1181 self.apply_textarea_snapshot(id, next)
1182 }
1183
1184 #[cfg(feature = "rich-widgets")]
1185 pub fn textarea_backspace(&mut self, id: WidgetId) -> Result<(), GuiError> {
1186 self.textarea_insert_char(id, '\u{8}')
1187 }
1188
1189 #[cfg(feature = "rich-widgets")]
1190 pub fn textarea_delete_forward(&mut self, id: WidgetId) -> Result<(), GuiError> {
1191 self.textarea_insert_char(id, '\u{7f}')
1192 }
1193
1194 #[cfg(feature = "rich-widgets")]
1195 pub fn keyboard_selected_key(&self, id: WidgetId) -> Option<char> {
1196 match self.node(id)?.kind {
1197 WidgetKind::Keyboard {
1198 keys,
1199 alt_keys,
1200 selected,
1201 layout,
1202 ..
1203 } => keyboard_char_for_layout(keys, alt_keys, selected, layout),
1204 _ => None,
1205 }
1206 }
1207
1208 #[cfg(feature = "rich-widgets")]
1209 pub fn keyboard_layout(&self, id: WidgetId) -> Option<KeyboardLayout> {
1210 match self.node(id)?.kind {
1211 WidgetKind::Keyboard { layout, .. } => Some(layout),
1212 _ => None,
1213 }
1214 }
1215
1216 #[cfg(feature = "rich-widgets")]
1217 pub fn set_keyboard_layout(
1218 &mut self,
1219 id: WidgetId,
1220 layout: KeyboardLayout,
1221 ) -> Result<(), GuiError> {
1222 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1223 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1224 match node.kind {
1225 WidgetKind::Keyboard {
1226 layout: ref mut current,
1227 ..
1228 } => {
1229 *current = layout;
1230 self.dirty.add(rect)?;
1231 Ok(())
1232 }
1233 _ => Err(GuiError::NotFound),
1234 }
1235 }
1236
1237 #[cfg(feature = "rich-widgets")]
1238 pub fn set_keyboard_target(
1239 &mut self,
1240 id: WidgetId,
1241 target: Option<WidgetId>,
1242 ) -> Result<(), GuiError> {
1243 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1244 match node.kind {
1245 WidgetKind::Keyboard {
1246 target: ref mut current,
1247 ..
1248 } => {
1249 *current = target;
1250 Ok(())
1251 }
1252 _ => Err(GuiError::NotFound),
1253 }
1254 }
1255
1256 #[cfg(feature = "rich-widgets")]
1257 pub fn set_gauge_value(&mut self, id: WidgetId, value: f32) -> Result<(), GuiError> {
1258 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1259 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1260 match node.kind {
1261 WidgetKind::Gauge {
1262 value: ref mut v,
1263 min,
1264 max,
1265 ..
1266 }
1267 | WidgetKind::ArcGauge {
1268 value: ref mut v,
1269 min,
1270 max,
1271 ..
1272 }
1273 | WidgetKind::GaugeNeedle {
1274 value: ref mut v,
1275 min,
1276 max,
1277 ..
1278 } => {
1279 *v = value.clamp(min.min(max), min.max(max));
1280 self.dirty.add(rect)?;
1281 Ok(())
1282 }
1283 _ => Err(GuiError::NotFound),
1284 }
1285 }
1286
1287 #[cfg(feature = "rich-widgets")]
1288 pub fn set_gauge_ticks(
1289 &mut self,
1290 id: WidgetId,
1291 major_ticks: u8,
1292 minor_ticks: u8,
1293 show_value: bool,
1294 ) -> Result<(), GuiError> {
1295 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1296 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1297 match node.kind {
1298 WidgetKind::Gauge {
1299 major_ticks: ref mut major,
1300 minor_ticks: ref mut minor,
1301 show_value: ref mut show,
1302 ..
1303 }
1304 | WidgetKind::ArcGauge {
1305 major_ticks: ref mut major,
1306 minor_ticks: ref mut minor,
1307 show_value: ref mut show,
1308 ..
1309 } => {
1310 *major = major_ticks.max(1);
1311 *minor = minor_ticks.max(1);
1312 *show = show_value;
1313 self.dirty.add(rect)?;
1314 Ok(())
1315 }
1316 _ => Err(GuiError::NotFound),
1317 }
1318 }
1319
1320 pub fn set_widget_rect(&mut self, id: WidgetId, rect: Rect) -> Result<(), GuiError> {
1321 let old = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1322 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1323 node.rect = rect;
1324 self.dirty.add(old)?;
1325 self.mark_subtree_dirty(id)?;
1326 Ok(())
1327 }
1328
1329 pub fn set_widget_x(&mut self, id: WidgetId, x: i32) -> Result<(), GuiError> {
1330 let mut rect = self.node(id).ok_or(GuiError::NotFound)?.rect;
1331 rect.x = x;
1332 self.set_widget_rect(id, rect)
1333 }
1334
1335 pub fn set_widget_y(&mut self, id: WidgetId, y: i32) -> Result<(), GuiError> {
1336 let mut rect = self.node(id).ok_or(GuiError::NotFound)?.rect;
1337 rect.y = y;
1338 self.set_widget_rect(id, rect)
1339 }
1340
1341 pub fn set_widget_width(&mut self, id: WidgetId, w: u32) -> Result<(), GuiError> {
1342 let mut rect = self.node(id).ok_or(GuiError::NotFound)?.rect;
1343 rect.w = w.max(1);
1344 self.set_widget_rect(id, rect)
1345 }
1346
1347 pub fn set_widget_height(&mut self, id: WidgetId, h: u32) -> Result<(), GuiError> {
1348 let mut rect = self.node(id).ok_or(GuiError::NotFound)?.rect;
1349 rect.h = h.max(1);
1350 self.set_widget_rect(id, rect)
1351 }
1352
1353 pub fn set_widget_opacity(&mut self, id: WidgetId, opacity: u8) -> Result<(), GuiError> {
1354 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1355 node.style.normal.opacity = opacity;
1356 node.style.focused.opacity = opacity;
1357 node.style.pressed.opacity = opacity;
1358 node.style.disabled.opacity = opacity;
1359 self.mark_subtree_dirty(id)
1360 }
1361
1362 pub fn set_widget_corner_radius(&mut self, id: WidgetId, radius: u8) -> Result<(), GuiError> {
1363 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1364 node.style.normal.corner_radius = radius;
1365 node.style.focused.corner_radius = radius;
1366 node.style.pressed.corner_radius = radius;
1367 node.style.disabled.corner_radius = radius;
1368 self.mark_subtree_dirty(id)
1369 }
1370
1371 pub fn set_widget_accent(&mut self, id: WidgetId, accent: Rgb565) -> Result<(), GuiError> {
1372 let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
1373 node.style.normal.accent = accent;
1374 node.style.focused.accent = accent;
1375 node.style.pressed.accent = accent;
1376 node.style.disabled.accent = accent;
1377 self.mark_subtree_dirty(id)
1378 }
1379
1380 pub fn set_widget_parent(
1381 &mut self,
1382 id: WidgetId,
1383 parent: Option<WidgetId>,
1384 ) -> Result<(), GuiError> {
1385 if let Some(parent) = parent {
1386 self.node(parent).ok_or(GuiError::NotFound)?;
1387 }
1388 self.node_mut(id).ok_or(GuiError::NotFound)?.parent = parent;
1389 self.mark_subtree_dirty(id)?;
1390 Ok(())
1391 }
1392
1393 pub fn add_child(&mut self, parent: WidgetId, child: WidgetId) -> Result<(), GuiError> {
1394 self.set_widget_parent(child, Some(parent))
1395 }
1396
1397 pub fn children_of(&self, parent: WidgetId) -> impl Iterator<Item = &WidgetNode<'a>> + '_ {
1398 self.widgets
1399 .iter()
1400 .filter(move |node| node.parent == Some(parent))
1401 }
1402
1403 #[inline]
1404 pub fn absolute_rect(&self, id: WidgetId) -> Option<Rect> {
1405 let node = self.node(id)?;
1406 if node.parent.is_none() {
1407 return Some(node.rect);
1408 }
1409 let mut rect = node.rect;
1410 let mut parent = node.parent;
1411 let mut depth = 0;
1412 while let Some(parent_id) = parent {
1413 if depth >= NODES {
1414 return None;
1415 }
1416 let parent_node = self.node(parent_id)?;
1417 rect.x += parent_node.rect.x;
1418 rect.y += parent_node.rect.y;
1419 parent = parent_node.parent;
1420 depth += 1;
1421 }
1422 Some(rect)
1423 }
1424
1425 pub fn set_flag(
1426 &mut self,
1427 id: WidgetId,
1428 flag: WidgetFlags,
1429 enabled: bool,
1430 ) -> Result<(), GuiError> {
1431 let was_set = self.has_flag(id, flag)?;
1432 let before_state = self.current_visual_state(id);
1433 self.mark_subtree_dirty(id)?;
1434 self.node_mut(id)
1435 .ok_or(GuiError::NotFound)?
1436 .flags
1437 .set(flag, enabled);
1438 if flag == WidgetFlags::DISABLED
1439 && enabled
1440 && self.pressed.is_some_and(|pressed| pressed.id == id)
1441 {
1442 self.pressed = None;
1443 }
1444 self.mark_subtree_dirty(id)?;
1445 if self
1446 .focus
1447 .is_some_and(|focus| !self.effective_focusable(focus))
1448 {
1449 self.focus = None;
1450 self.ensure_focus();
1451 }
1452 if flag == WidgetFlags::DISABLED && was_set != enabled {
1453 let after_state = self.current_visual_state(id);
1454 self.start_state_transition(id, before_state, after_state);
1455 }
1456 Ok(())
1457 }
1458
1459 pub fn has_flag(&self, id: WidgetId, flag: WidgetFlags) -> Result<bool, GuiError> {
1460 Ok(self
1461 .node(id)
1462 .ok_or(GuiError::NotFound)?
1463 .flags
1464 .contains(flag))
1465 }
1466
1467 pub fn insert_flag(&mut self, id: WidgetId, flag: WidgetFlags) -> Result<(), GuiError> {
1468 self.set_flag(id, flag, true)
1469 }
1470
1471 pub fn remove_flag(&mut self, id: WidgetId, flag: WidgetFlags) -> Result<(), GuiError> {
1472 self.set_flag(id, flag, false)
1473 }
1474
1475 pub fn set_hidden(&mut self, id: WidgetId, hidden: bool) -> Result<(), GuiError> {
1476 self.set_flag(id, WidgetFlags::HIDDEN, hidden)
1477 }
1478
1479 pub fn set_disabled(&mut self, id: WidgetId, disabled: bool) -> Result<(), GuiError> {
1480 self.set_flag(id, WidgetFlags::DISABLED, disabled)
1481 }
1482
1483 pub fn set_clickable(&mut self, id: WidgetId, clickable: bool) -> Result<(), GuiError> {
1484 self.set_flag(id, WidgetFlags::CLICKABLE, clickable)
1485 }
1486
1487 pub fn set_scrollable(&mut self, id: WidgetId, scrollable: bool) -> Result<(), GuiError> {
1488 self.set_flag(id, WidgetFlags::SCROLLABLE, scrollable)
1489 }
1490
1491 pub fn set_visible(&mut self, id: WidgetId, visible: bool) -> Result<(), GuiError> {
1492 self.set_hidden(id, !visible)
1493 }
1494
1495 pub fn set_enabled(&mut self, id: WidgetId, enabled: bool) -> Result<(), GuiError> {
1496 self.set_disabled(id, !enabled)
1497 }
1498
1499 pub fn event_path<const M: usize>(
1500 &self,
1501 target: WidgetId,
1502 out: &mut heapless::Vec<EventContext, M>,
1503 ) -> Result<usize, GuiError> {
1504 self.node(target).ok_or(GuiError::NotFound)?;
1505 out.clear();
1506
1507 let mut chain = heapless::Vec::<WidgetId, NODES>::new();
1508 let mut current = Some(target);
1509 while let Some(id) = current {
1510 chain.push(id).map_err(|_| GuiError::WidgetsFull)?;
1511 current = self.node(id).ok_or(GuiError::NotFound)?.parent;
1512 }
1513
1514 for id in chain.iter().rev().copied().filter(|&id| id != target) {
1515 out.push(EventContext {
1516 target,
1517 current: id,
1518 phase: EventPhase::Capture,
1519 })
1520 .map_err(|_| GuiError::EventsFull)?;
1521 }
1522
1523 out.push(EventContext {
1524 target,
1525 current: target,
1526 phase: EventPhase::Target,
1527 })
1528 .map_err(|_| GuiError::EventsFull)?;
1529
1530 for id in chain.iter().copied().skip(1) {
1531 out.push(EventContext {
1532 target,
1533 current: id,
1534 phase: EventPhase::Bubble,
1535 })
1536 .map_err(|_| GuiError::EventsFull)?;
1537 }
1538
1539 Ok(out.len())
1540 }
1541
1542 pub fn widget_event_path<const M: usize>(
1543 &self,
1544 target: WidgetId,
1545 kind: WidgetEventKind,
1546 out: &mut heapless::Vec<WidgetEvent, M>,
1547 ) -> Result<usize, GuiError> {
1548 self.node(target).ok_or(GuiError::NotFound)?;
1549 out.clear();
1550
1551 let mut chain = heapless::Vec::<WidgetId, NODES>::new();
1552 let mut current = Some(target);
1553 while let Some(id) = current {
1554 chain.push(id).map_err(|_| GuiError::WidgetsFull)?;
1555 current = self.node(id).ok_or(GuiError::NotFound)?.parent;
1556 }
1557
1558 for id in chain.iter().rev().copied().filter(|&id| id != target) {
1559 out.push(WidgetEvent {
1560 target,
1561 current: id,
1562 phase: EventPhase::Capture,
1563 kind,
1564 })
1565 .map_err(|_| GuiError::EventsFull)?;
1566 }
1567
1568 out.push(WidgetEvent {
1569 target,
1570 current: target,
1571 phase: EventPhase::Target,
1572 kind,
1573 })
1574 .map_err(|_| GuiError::EventsFull)?;
1575
1576 if self.has_flag(target, WidgetFlags::EVENT_BUBBLE)? {
1577 for id in chain.iter().copied().skip(1) {
1578 out.push(WidgetEvent {
1579 target,
1580 current: id,
1581 phase: EventPhase::Bubble,
1582 kind,
1583 })
1584 .map_err(|_| GuiError::EventsFull)?;
1585 }
1586 }
1587
1588 Ok(out.len())
1589 }
1590
1591 pub fn dispatch_widget_event<const M: usize, F>(
1592 &self,
1593 target: WidgetId,
1594 kind: WidgetEventKind,
1595 scratch: &mut heapless::Vec<WidgetEvent, M>,
1596 mut handler: F,
1597 ) -> Result<(), GuiError>
1598 where
1599 F: FnMut(WidgetEvent) -> EventPolicy,
1600 {
1601 self.widget_event_path(target, kind, scratch)?;
1602 for event in scratch.iter().copied() {
1603 let handler_policy = handler(event);
1604 if matches!(handler_policy, EventPolicy::Stop)
1605 || self.stop_due_to_builtin_widget_behavior(event)
1606 || self.stop_due_to_registered_policy(event)
1607 {
1608 break;
1609 }
1610 }
1611 Ok(())
1612 }
1613
1614 pub fn mark_subtree_dirty(&mut self, id: WidgetId) -> Result<(), GuiError> {
1615 let rect = self.absolute_rect(id).ok_or(GuiError::NotFound)?;
1616 self.dirty.add(rect)?;
1617 let child_ids: heapless::Vec<WidgetId, NODES> = self
1618 .widgets
1619 .iter()
1620 .filter(|node| node.parent == Some(id))
1621 .map(|node| node.id)
1622 .collect();
1623 for child in child_ids {
1624 self.mark_subtree_dirty(child)?;
1625 }
1626 Ok(())
1627 }
1628
1629 pub fn set_focus_group(&mut self, id: WidgetId, group: FocusGroupId) -> Result<(), GuiError> {
1630 self.node_mut(id).ok_or(GuiError::NotFound)?.focus_group = group;
1631 Ok(())
1632 }
1633
1634 pub fn set_active_focus_group(&mut self, group: Option<FocusGroupId>) {
1635 self.active_focus_group = group;
1636 if let Some(focus) = self.focus {
1637 let still_valid = self.node(focus).is_some_and(|node| {
1638 group.is_none_or(|active| node.focus_group == active)
1639 && self.effective_focusable(focus)
1640 });
1641 if !still_valid {
1642 self.focus = None;
1643 self.ensure_focus();
1644 }
1645 }
1646 }
1647
1648 pub fn apply_layout(
1649 &mut self,
1650 layout: LinearLayout,
1651 area: Rect,
1652 ids: &[WidgetId],
1653 ) -> Result<usize, GuiError> {
1654 let mut rects = [Rect::empty(); 16];
1655 let count = layout.arrange(area, ids.len().min(rects.len()), &mut rects);
1656 for (id, rect) in ids.iter().copied().zip(rects).take(count) {
1657 self.set_widget_rect(id, rect)?;
1658 }
1659 Ok(count)
1660 }
1661
1662 pub fn apply_layout_flex(
1663 &mut self,
1664 layout: LinearLayout,
1665 area: Rect,
1666 ids: &[WidgetId],
1667 items: &[LayoutItem],
1668 enable_grow: bool,
1669 enable_shrink: bool,
1670 ) -> Result<usize, GuiError> {
1671 let mut rects = [Rect::empty(); 16];
1672 let count = ids.len().min(items.len()).min(rects.len());
1673 let laid_out = layout.arrange_items_flex(
1674 area,
1675 &items[..count],
1676 &mut rects,
1677 enable_grow,
1678 enable_shrink,
1679 );
1680 for (id, rect) in ids.iter().copied().zip(rects).take(laid_out) {
1681 self.set_widget_rect(id, rect)?;
1682 }
1683 Ok(laid_out)
1684 }
1685
1686 pub fn apply_layout_intrinsic(
1687 &mut self,
1688 layout: LinearLayout,
1689 area: Rect,
1690 ids: &[WidgetId],
1691 ) -> Result<usize, GuiError> {
1692 self.apply_layout_intrinsic_with_cross(layout, area, ids, false)
1693 }
1694
1695 pub fn apply_layout_intrinsic_with_cross(
1696 &mut self,
1697 layout: LinearLayout,
1698 area: Rect,
1699 ids: &[WidgetId],
1700 preserve_cross: bool,
1701 ) -> Result<usize, GuiError> {
1702 let mut specs = [LayoutItem::fill(); 16];
1703 let mut rects = [Rect::empty(); 16];
1704 let count = ids.len().min(specs.len()).min(rects.len());
1705
1706 for (idx, id) in ids.iter().copied().take(count).enumerate() {
1707 let (w, h) = self.intrinsic_size(id).ok_or(GuiError::NotFound)?;
1708 specs[idx] = match layout.axis {
1709 Axis::Horizontal => LayoutItem::length(w).with_cross(if preserve_cross {
1710 crate::layout::Constraint::Length(h)
1711 } else {
1712 crate::layout::Constraint::Fill(1)
1713 }),
1714 Axis::Vertical => LayoutItem::length(h).with_cross(if preserve_cross {
1715 crate::layout::Constraint::Length(w)
1716 } else {
1717 crate::layout::Constraint::Fill(1)
1718 }),
1719 };
1720 }
1721
1722 let laid_out = layout.arrange_items(area, &specs[..count], &mut rects);
1723 for (id, rect) in ids.iter().copied().zip(rects).take(laid_out) {
1724 self.set_widget_rect(id, rect)?;
1725 }
1726 Ok(laid_out)
1727 }
1728}