matrix_gui/ui.rs
1//! User Interface (UI) module for the matrix_gui framework.
2//!
3//! This module provides the core UI infrastructure including:
4//! - Error handling for GUI operations
5//! - Widget rendering and state management
6//! - Interaction handling (touch/mouse input)
7//! - Focus management for keyboard navigation
8//! - Drawing primitives and utilities
9//! - Alignment and layout helpers
10//!
11//! # Core Components
12//!
13//! - [`Ui`]: Main UI context that manages drawing, state, and interactions
14//! - [`Widget`]: Trait for drawable UI components
15//! - [`Response`]: Result type for widget operations
16//! - [`Interaction`]: Input event types (pressed, drag, release)
17//!
18//! # Example
19//!
20//! ```ignore
21//! use matrix_gui::prelude::*;
22//! use embedded_graphics::primitives::Rectangle;
23//!
24//! // Create UI context
25//! let mut ui = Ui::new_fullscreen(&mut display, &widget_states, &style);
26//!
27//! // Add widgets
28//! ui.add(my_widget);
29//!
30//! // Handle interactions
31//! ui.interact(Interaction::Pressed(point));
32//! ```
33
34#[cfg(feature = "interaction")]
35use crate::region::Region;
36use crate::style::Style;
37use crate::widget_state::{RenderState, WidgetId, WidgetStates};
38use core::fmt::Debug;
39use embedded_graphics::draw_target::DrawTarget;
40use embedded_graphics::geometry::Dimensions;
41use embedded_graphics::pixelcolor::PixelColor;
42#[cfg(feature = "clipped_draw")]
43use embedded_graphics::prelude::DrawTargetExt;
44#[cfg(feature = "interaction")]
45use embedded_graphics::prelude::Point;
46use embedded_graphics::primitives::Rectangle;
47use embedded_graphics::{Drawable, Pixel};
48
49#[cfg(feature = "debug-color")]
50static DBG_CNT: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
51
52/// Error types that can occur during GUI operations.
53///
54/// This enum represents the various error conditions that can arise
55/// when performing GUI operations such as drawing or widget management.
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
57#[non_exhaustive]
58pub enum GuiError {
59 /// An error occurred during a drawing operation.
60 DrawError,
61 /// An invalid widget ID was provided.
62 InvalidWidgetId,
63 /// An invalid animation ID was provided.
64 InvalidAnimId,
65 /// A modal widget is already active.
66 ModalActive,
67}
68
69/// Result type alias for GUI operations.
70///
71/// This type alias provides a convenient way to handle results from
72/// GUI operations that can fail with [`GuiError`].
73pub type GuiResult<T> = Result<T, GuiError>;
74
75/// User interaction events.
76///
77/// This enum represents various types of user input events that can be
78/// processed by the UI system, such as touch presses, drags, and releases.
79#[cfg(feature = "interaction")]
80#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
81#[non_exhaustive]
82pub enum Interaction {
83 /// A press event at the given point (e.g., finger down or mouse click).
84 Pressed(Point),
85 /// A drag event at the given point (e.g., finger movement while pressed).
86 Drag(Point),
87 /// A release event at the given point (e.g., finger up or mouse release).
88 Release(Point),
89 /// A click event at the given point
90 Clicked(Point),
91 /// No interaction event.
92 #[default]
93 None,
94}
95
96#[cfg(feature = "interaction")]
97impl Interaction {
98 /// Returns the point associated with this interaction, if any.
99 ///
100 /// # Returns
101 ///
102 /// `Some(point)` if the interaction has an associated point,
103 /// `None` for `Interaction::None`.
104 fn get_point(&self) -> Option<Point> {
105 match self {
106 Interaction::Pressed(p) => Some(*p),
107 Interaction::Drag(p) => Some(*p),
108 Interaction::Release(p) => Some(*p),
109 Interaction::Clicked(p) => Some(*p),
110 Interaction::None => None,
111 }
112 }
113}
114
115/// Response from a widget operation.
116///
117/// This enum represents the various responses that a widget can return
118/// after processing an operation, such as drawing or handling interactions.
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120#[non_exhaustive]
121pub enum Response {
122 /// No significant response, widget is idle.
123 Idle,
124 /// The widget was clicked (e.g., checkbox toggled).
125 Clicked,
126 /// The widget is currently being pressed down.
127 Pressed,
128 /// The widget was released.
129 Released,
130 /// The widget gained focus.
131 Focused,
132 /// The widget lost focus.
133 Defocused,
134 /// The widget was selected (e.g., radio button, list item).
135 Selected,
136 /// The widget was triggered (e.g., button activated).
137 Trigger,
138 /// The widget's content was edited.
139 Edited,
140 /// The widget's value changed.
141 ValueChanged,
142 /// The widget is being hovered over.
143 Hovered,
144 /// The widget was scrolled.
145 Scrolled,
146 /// The widget was disabled.
147 Disabled,
148 /// User-defined response with custom code.
149 User(u8),
150 /// An error occurred during the operation.
151 Error(GuiError),
152}
153
154// builder pattern
155impl Response {
156 /// Returns `true` if the response is idle.
157 pub const fn is_idle(&self) -> bool {
158 matches!(self, Response::Idle)
159 }
160
161 /// Returns `true` if the response is clicked.
162 pub const fn is_clicked(&self) -> bool {
163 matches!(self, Response::Clicked)
164 }
165
166 /// Returns `true` if the response is pressed.
167 pub const fn is_pressed(&self) -> bool {
168 matches!(self, Response::Pressed)
169 }
170
171 pub const fn is_released(&self) -> bool {
172 matches!(self, Response::Released)
173 }
174
175 /// Returns `true` if the response is focused.
176 pub const fn is_focused(&self) -> bool {
177 matches!(self, Response::Focused)
178 }
179
180 /// Returns `true` if the response is defocused.
181 pub const fn is_defocused(&self) -> bool {
182 matches!(self, Response::Defocused)
183 }
184
185 /// Returns `true` if the response is selected.
186 pub const fn is_selected(&self) -> bool {
187 matches!(self, Response::Selected)
188 }
189
190 /// Returns `true` if the response is trigger.
191 pub const fn is_trigger(&self) -> bool {
192 matches!(self, Response::Trigger)
193 }
194
195 /// Returns `true` if the response is edited.
196 pub const fn is_edited(&self) -> bool {
197 matches!(self, Response::Edited)
198 }
199
200 /// Returns `true` if the response indicates a value change.
201 pub const fn is_value_changed(&self) -> bool {
202 matches!(self, Response::ValueChanged)
203 }
204
205 /// Returns `true` if the response is hovered.
206 pub const fn is_hovered(&self) -> bool {
207 matches!(self, Response::Hovered)
208 }
209
210 /// Returns `true` if the response is scrolled.
211 pub const fn is_scrolled(&self) -> bool {
212 matches!(self, Response::Scrolled)
213 }
214
215 /// Returns `true` if the response is disabled.
216 pub const fn is_disabled(&self) -> bool {
217 matches!(self, Response::Disabled)
218 }
219
220 /// Returns `true` if the response is a user-defined response.
221 pub const fn is_user(&self) -> bool {
222 matches!(self, Response::User(_))
223 }
224
225 /// Returns the user code if the response is a user-defined response.
226 pub fn user_code(&self) -> Option<u8> {
227 match self {
228 Response::User(code) => Some(*code),
229 _ => None,
230 }
231 }
232
233 /// Returns the error if the response is an error.
234 pub fn error(&self) -> Option<GuiError> {
235 match self {
236 Response::Error(e) => Some(*e),
237 _ => None,
238 }
239 }
240}
241
242impl Response {
243 /// Creates a response from a GUI error.
244 ///
245 /// # Arguments
246 ///
247 /// * `error` - The error to convert into a response.
248 ///
249 /// # Returns
250 ///
251 /// `Response::Error(error)`.
252 pub fn from_error(error: GuiError) -> Response {
253 Response::Error(error)
254 }
255
256 /// Creates a response based on a change flag.
257 ///
258 /// # Arguments
259 ///
260 /// * `change` - `true` if a change occurred, `false` otherwise.
261 ///
262 /// # Returns
263 ///
264 /// `Response::ValueChanged` if `change` is `true`, `Response::Idle` otherwise.
265 pub fn from_change(change: bool) -> Self {
266 if change {
267 Response::ValueChanged
268 } else {
269 Response::Idle
270 }
271 }
272}
273
274/// Converts an interaction event into a response.
275///
276/// This implementation maps interaction events to widget responses:
277/// - `Interaction::None` → `Response::Idle`
278/// - `Interaction::Pressed` or `Interaction::Drag` → `Response::Pressed`
279/// - `Interaction::Release` → `Response::Trigger`
280#[cfg(feature = "interaction")]
281impl From<Interaction> for Response {
282 fn from(interaction: Interaction) -> Self {
283 match interaction {
284 Interaction::None => Response::Idle,
285 Interaction::Pressed(_) | Interaction::Drag(_) => Response::Pressed,
286 Interaction::Release(_) => Response::Released,
287 Interaction::Clicked(_) => Response::Clicked,
288 }
289 }
290}
291
292/// Trait for drawable UI widgets.
293///
294/// This trait defines the interface that all widgets must implement to be
295/// rendered within the UI system. Widgets are responsible for their own
296/// rendering and can return responses to indicate the result of operations.
297///
298/// # Type Parameters
299///
300/// * `DRAW` - The draw target type that implements [`DrawTarget`]
301/// * `COL` - The pixel color type that implements [`PixelColor`]
302///
303/// # Example
304///
305/// ```rust
306/// use matrix_gui::prelude::*;
307///
308/// struct MyWidget;
309///
310/// impl<DRAW, COL> Widget<DRAW, COL> for MyWidget
311/// where
312/// DRAW: DrawTarget<Color = COL>,
313/// COL: PixelColor,
314/// {
315/// fn draw(&mut self, ui: &mut Ui<DRAW, COL>) -> GuiResult<Response> {
316/// // Widget rendering logic here
317/// Ok(Response::Idle)
318/// }
319/// }
320/// ```
321pub trait Widget<DRAW: DrawTarget<Color = COL>, COL: PixelColor> {
322 /// Draws the widget to the UI context.
323 ///
324 /// This method is called by the UI system to render the widget.
325 /// The widget can use the provided UI context to access drawing
326 /// primitives, style information, and handle interactions.
327 ///
328 /// # Arguments
329 ///
330 /// * `ui` - Mutable reference to the UI context.
331 ///
332 /// # Returns
333 ///
334 /// A [`GuiResult<Response>`] indicating the result of the draw operation.
335 ///
336 /// # Errors
337 ///
338 /// Returns [`GuiError::DrawError`] if drawing fails.
339 fn draw(&mut self, ui: &mut Ui<DRAW, COL>) -> GuiResult<Response>;
340}
341
342/// Horizontal alignment options for UI elements.
343///
344/// This enum defines how content should be aligned horizontally within
345/// a container or bounding box.
346#[derive(Clone, Copy, Debug, PartialEq)]
347pub enum HorizontalAlign {
348 /// Align content to the left edge.
349 Left,
350 /// Align content to the center.
351 Center,
352 /// Align content to the right edge.
353 Right,
354 /// Justify content to fill the available width.
355 Justify,
356}
357
358/// Vertical alignment options for UI elements.
359///
360/// This enum defines how content should be aligned vertically within
361/// a container or bounding box.
362#[derive(Clone, Copy, Debug)]
363pub enum VerticalAlign {
364 /// Align content to the top edge.
365 Top,
366 /// Align content to the center.
367 Center,
368 /// Align content to the bottom edge.
369 Bottom,
370}
371
372/// Combined horizontal and vertical alignment.
373///
374/// This struct combines horizontal and vertical alignment into a single
375/// convenient type for positioning UI elements.
376///
377/// # Example
378///
379/// ```rust
380/// use matrix_gui::ui::{Align, HorizontalAlign, VerticalAlign};
381///
382/// // Center alignment
383/// let centered = Align(HorizontalAlign::Center, VerticalAlign::Center);
384///
385/// // Top-left alignment (default)
386/// let top_left = Align::default();
387/// ```
388#[derive(Clone, Copy, Debug)]
389pub struct Align(pub HorizontalAlign, pub VerticalAlign);
390
391/// Default alignment is top-left.
392impl Default for Align {
393 fn default() -> Self {
394 Align(HorizontalAlign::Left, VerticalAlign::Top)
395 }
396}
397
398/// Internal drawing helper that wraps a draw target.
399///
400/// This struct provides a thin wrapper around a draw target to simplify
401/// drawing operations within the UI system. It implements both `Drawable`
402/// and `DrawTarget` traits for flexibility.
403struct Painter<'a, COL: PixelColor, DRAW: DrawTarget<Color = COL>> {
404 /// The underlying draw target.
405 target: &'a mut DRAW,
406
407 /// The area to draw in, if clipping is enabled.
408 #[cfg(feature = "clipped_draw")]
409 clipped_area: Option<Rectangle>,
410}
411
412#[cfg(not(feature = "clipped_draw"))]
413impl<'a, COL: PixelColor, DRAW: DrawTarget<Color = COL>> Painter<'a, COL, DRAW> {
414 /// Creates a new painter wrapping the given draw target.
415 ///
416 /// # Arguments
417 ///
418 /// * `target` - Mutable reference to the draw target to wrap.
419 ///
420 /// # Returns
421 ///
422 /// A new `Painter` instance.
423 fn new(target: &'a mut DRAW) -> Self {
424 Self { target }
425 }
426
427 /// Draws a drawable item to the target.
428 ///
429 /// # Arguments
430 ///
431 /// * `item` - Reference to an item implementing `Drawable`.
432 ///
433 /// # Returns
434 ///
435 /// `Ok(())` if drawing succeeds, `Err(GuiError::DrawError)` otherwise.
436 fn draw(&mut self, item: &impl Drawable<Color = COL>) -> GuiResult<()> {
437 item.draw(self.target).map_err(|_| GuiError::DrawError)?;
438 Ok(())
439 }
440}
441
442#[cfg(feature = "clipped_draw")]
443impl<'a, COL: PixelColor, DRAW: DrawTarget<Color = COL>> Painter<'a, COL, DRAW> {
444 /// Creates a new painter wrapping the given draw target.
445 ///
446 /// # Arguments
447 ///
448 /// * `target` - Mutable reference to the draw target to wrap.
449 ///
450 /// # Returns
451 ///
452 /// A new `Painter` instance.
453 fn new(target: &'a mut DRAW) -> Self {
454 Self {
455 target,
456 clipped_area: None,
457 }
458 }
459
460 /// Draws a drawable item to the target.
461 ///
462 /// # Arguments
463 ///
464 /// * `item` - Reference to an item implementing `Drawable`.
465 ///
466 /// # Returns
467 ///
468 /// `Ok(())` if drawing succeeds, `Err(GuiError::DrawError)` otherwise.
469 fn draw(&mut self, item: &impl Drawable<Color = COL>) -> GuiResult<()> {
470 if let Some(area) = &self.clipped_area {
471 let mut clipped = self.target.clipped(area);
472 item.draw(&mut clipped).map_err(|_| GuiError::DrawError)?;
473 } else {
474 item.draw(self.target).map_err(|_| GuiError::DrawError)?;
475 }
476 Ok(())
477 }
478
479 /// Sets the area to draw in, if clipping is enabled.
480 ///
481 /// # Arguments
482 ///
483 /// * `area` - The area to draw in.
484 ///
485 fn set_clipped_area(&mut self, area: Option<Rectangle>) {
486 self.clipped_area = area;
487 }
488}
489
490/// Provides dimensions information for the painter.
491impl<COL: PixelColor, DRAW: DrawTarget<Color = COL, Error = ERR>, ERR> Dimensions
492 for Painter<'_, COL, DRAW>
493{
494 /// Returns the bounding box of the underlying draw target.
495 ///
496 /// # Returns
497 ///
498 /// A `Rectangle` representing the bounds of the draw target.
499 fn bounding_box(&self) -> Rectangle {
500 self.target.bounding_box()
501 }
502}
503
504/// Allows the painter to act as a draw target.
505impl<COL: PixelColor, DRAW: DrawTarget<Color = COL, Error = ERR>, ERR> DrawTarget
506 for Painter<'_, COL, DRAW>
507{
508 type Color = COL;
509 type Error = ERR;
510
511 /// Draws an iterator of pixels to the target.
512 ///
513 /// # Arguments
514 ///
515 /// * `pixels` - An iterator of pixels to draw.
516 ///
517 /// # Returns
518 ///
519 /// `Ok(())` if drawing succeeds, `Err(Self::Error)` otherwise.
520 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
521 where
522 I: IntoIterator<Item = Pixel<Self::Color>>,
523 {
524 self.target.draw_iter(pixels)
525 }
526}
527
528/// Main UI context for managing widgets, drawing, and interactions.
529///
530/// This struct is the central hub for all UI operations. It manages the
531/// draw target, widget states, styling, interactions, and focus management.
532/// All widgets are rendered through this context.
533///
534/// # Type Parameters
535///
536/// * `'a` - Lifetime of the borrowed resources
537/// * `DRAW` - The draw target type implementing [`DrawTarget`]
538/// * `COL` - The pixel color type implementing [`PixelColor`]
539///
540/// # Example
541///
542/// ```ignore
543/// use matrix_gui::prelude::*;
544/// use embedded_graphics::primitives::Rectangle;
545///
546/// // Create UI context with custom bounds
547/// let bounds = Rectangle::new(Point::new(0, 0), Size::new(320, 240));
548/// let mut ui = Ui::new(&mut display, bounds, &widget_states, &style);
549///
550/// // Or create fullscreen UI
551/// let mut ui = Ui::new_fullscreen(&mut display, &widget_states, &style);
552/// ```
553pub struct Ui<'a, DRAW, COL>
554where
555 DRAW: DrawTarget<Color = COL>,
556 COL: PixelColor,
557{
558 /// The bounding rectangle for the UI area.
559 bounds: Rectangle,
560 /// Internal painter for drawing operations.
561 painter: Painter<'a, COL, DRAW>,
562 /// Shared widget state storage.
563 widget_states: &'a WidgetStates<'a>,
564 /// Current styling configuration.
565 style: &'a Style<COL>,
566 /// Flag indicating if the background has been cleared.
567 cleared: bool,
568 /// Current interaction event (if interaction feature is enabled).
569 #[cfg(feature = "interaction")]
570 interact: Interaction,
571 /// Focus management state (if focus feature is enabled).
572 #[cfg(feature = "focus")]
573 focus: Option<crate::focus::Focus<'a>>,
574 /// Debug color for drawing widget bounds (if debug-color feature is enabled).
575 #[cfg(feature = "debug-color")]
576 debug_color: Option<COL>,
577}
578
579impl<DRAW, COL> Ui<'_, DRAW, COL>
580where
581 DRAW: DrawTarget<Color = COL>,
582 COL: PixelColor,
583{
584 /// Returns the screen width in pixels.
585 ///
586 /// # Returns
587 ///
588 /// The width of the UI bounds in pixels.
589 pub const fn get_screen_width(&self) -> u32 {
590 self.bounds.size.width
591 }
592
593 /// Returns the screen height in pixels.
594 ///
595 /// # Returns
596 ///
597 /// The height of the UI bounds in pixels.
598 pub const fn get_screen_height(&self) -> u32 {
599 self.bounds.size.height
600 }
601
602 /// Returns the screen bounding rectangle.
603 ///
604 /// # Returns
605 ///
606 /// A `Rectangle` representing the UI bounds.
607 pub const fn get_screen_bounds(&self) -> Rectangle {
608 self.bounds
609 }
610}
611
612impl<'a, COL, DRAW> Ui<'a, DRAW, COL>
613where
614 DRAW: DrawTarget<Color = COL>,
615 COL: PixelColor,
616{
617 /// Creates a new UI context with the specified bounds.
618 ///
619 /// # Arguments
620 ///
621 /// * `drawable` - Mutable reference to the draw target.
622 /// * `bounds` - The bounding rectangle for the UI area.
623 /// * `widget_states` - Reference to the widget state storage.
624 /// * `style` - Reference to the styling configuration.
625 ///
626 /// # Returns
627 ///
628 /// A new `Ui` instance.
629 pub fn new(
630 drawable: &'a mut DRAW,
631 bounds: Rectangle,
632 widget_states: &'a WidgetStates<'a>,
633 style: &'a Style<COL>,
634 ) -> Self {
635 Self {
636 bounds,
637 painter: Painter::new(drawable),
638 widget_states,
639 style,
640 cleared: false,
641 #[cfg(feature = "interaction")]
642 interact: Interaction::None,
643 #[cfg(feature = "focus")]
644 focus: None,
645 #[cfg(feature = "debug-color")]
646 debug_color: None,
647 }
648 }
649
650 /// Creates a new UI context covering the entire draw target.
651 ///
652 /// This is a convenience method that automatically determines the bounds
653 /// from the draw target's bounding box.
654 ///
655 /// # Arguments
656 ///
657 /// * `drawable` - Mutable reference to the draw target.
658 /// * `widget_states` - Reference to the widget state storage.
659 /// * `style` - Reference to the styling configuration.
660 ///
661 /// # Returns
662 ///
663 /// A new `Ui` instance with bounds covering the entire draw target.
664 pub fn new_fullscreen(
665 drawable: &'a mut DRAW,
666 widget_states: &'a WidgetStates<'a>,
667 style: &'a Style<COL>,
668 ) -> Self {
669 let bounds = drawable.bounding_box();
670 Ui::new(drawable, bounds, widget_states, style)
671 }
672
673 /// Adds a widget to the UI and draws it.
674 ///
675 /// This method calls the widget's `draw` method and returns the response.
676 /// If drawing fails, it returns an error response.
677 ///
678 /// # Arguments
679 ///
680 /// * `widget` - The widget to add and draw.
681 ///
682 /// # Returns
683 ///
684 /// The widget's response, or an error response if drawing fails.
685 pub fn add(&mut self, mut widget: impl Widget<DRAW, COL>) -> Response {
686 widget.draw(self).unwrap_or_else(Response::from_error)
687 }
688
689 /// Conditionally draws a widget based on its redraw state.
690 ///
691 /// This method only calls the drawing closure if the widget needs to be
692 /// redrawn (based on its state). This is useful for optimizing rendering
693 /// performance by avoiding unnecessary redraws.
694 ///
695 /// # Type Parameters
696 ///
697 /// * `ID` - The widget ID type implementing [`WidgetId`]
698 /// * `F` - The closure type that performs the drawing
699 ///
700 /// # Arguments
701 ///
702 /// * `id` - The widget's identifier.
703 /// * `f` - A closure that performs the drawing operation.
704 ///
705 /// # Returns
706 ///
707 /// The response from the drawing closure, or `Response::Idle` if no redraw was needed.
708 pub fn lazy_draw<F, ID: WidgetId>(&mut self, id: ID, f: F) -> Response
709 where
710 F: FnOnce(&mut Ui<DRAW, COL>) -> Response,
711 {
712 if self.widget_states.should_redraw(id) {
713 f(self)
714 } else {
715 Response::Idle
716 }
717 }
718
719 /// Returns a reference to the current style configuration.
720 ///
721 /// # Returns
722 ///
723 /// A reference to the [`Style`] configuration.
724 pub fn style(&self) -> &Style<COL> {
725 self.style
726 }
727
728 /// Sets the current interaction event.
729 ///
730 /// This method updates the interaction state that widgets can query
731 /// to handle user input events.
732 ///
733 /// # Arguments
734 ///
735 /// * `interaction` - The interaction event to set.
736 #[cfg(feature = "interaction")]
737 pub fn interact(&mut self, interaction: Interaction) {
738 self.interact = interaction;
739 }
740
741 /// Checks if the current interaction is within a widget's region.
742 ///
743 /// This method determines if the current interaction point (if any) is
744 /// contained within the specified region. It also marks the widget as
745 /// having received interaction.
746 ///
747 /// # Type Parameters
748 ///
749 /// * `ID` - The widget ID type implementing [`WidgetId`]
750 ///
751 /// # Arguments
752 ///
753 /// * `region` - The region to check for interaction.
754 ///
755 /// # Returns
756 ///
757 /// The interaction if it's within the region, `Interaction::None` otherwise.
758 #[cfg(feature = "interaction")]
759 pub fn check_interact<ID: WidgetId>(&mut self, region: &Region<ID>) -> Interaction {
760 self.widget_states.mark_as_interact(region.id());
761
762 #[cfg(feature = "popup")]
763 if self.is_modal_active() {
764 return Interaction::None;
765 }
766
767 let area = ®ion.rectangle();
768 if self
769 .interact
770 .get_point()
771 .map(|pt| area.contains(pt))
772 .unwrap_or(false)
773 {
774 self.interact
775 } else {
776 Interaction::None
777 }
778 }
779
780 /// Sets the area to draw in, if clipping is enabled.
781 ///
782 /// # Arguments
783 ///
784 /// * `area` - The area to draw in.
785 #[cfg(feature = "clipped_draw")]
786 pub fn set_clipped_area(&mut self, area: Option<Rectangle>) {
787 self.painter.set_clipped_area(area);
788 }
789}
790
791impl<COL, DRAW> Ui<'_, DRAW, COL>
792where
793 DRAW: DrawTarget<Color = COL>,
794 COL: PixelColor,
795{
796 /// Returns whether the background has been cleared.
797 ///
798 /// # Returns
799 ///
800 /// `true` if the background has been cleared, `false` otherwise.
801 pub const fn cleared(&self) -> bool {
802 self.cleared
803 }
804
805 /// Clears the specified area with the background color.
806 ///
807 /// This method fills the given rectangle with the background color from
808 /// the current style. If the background has already been cleared and the
809 /// debug-color feature is not enabled, this method returns early.
810 ///
811 /// # Arguments
812 ///
813 /// * `area` - The rectangle to clear.
814 ///
815 /// # Returns
816 ///
817 /// `Ok(())` if clearing succeeds, `Err(GuiError)` otherwise.
818 pub fn clear_area(&mut self, area: &Rectangle) -> GuiResult<()> {
819 #[cfg(not(feature = "debug-color"))]
820 if self.cleared {
821 return Ok(());
822 }
823
824 self.clear_area_raw(area, self.style.background_color)?;
825
826 #[cfg(feature = "debug-color")]
827 self.draw_widget_bound_box(area);
828
829 Ok(())
830 }
831
832 /// Clears the specified area with a custom color.
833 ///
834 /// This method fills the given rectangle with the specified color.
835 /// It uses either the standard `StyledDrawable` trait or the optimized
836 /// `fill-rect` feature if enabled.
837 ///
838 /// # Arguments
839 ///
840 /// * `area` - The rectangle to clear.
841 /// * `color` - The color to fill the area with.
842 ///
843 /// # Returns
844 ///
845 /// `Ok(())` if clearing succeeds, `Err(GuiError::DrawError)` otherwise.
846 pub fn clear_area_raw(&mut self, area: &Rectangle, color: COL) -> GuiResult<()> {
847 #[cfg(not(feature = "fill-rect"))]
848 {
849 use embedded_graphics::{prelude::Primitive, primitives::PrimitiveStyle};
850 self.draw(&area.into_styled(PrimitiveStyle::with_fill(color)))
851 }
852 #[cfg(feature = "fill-rect")]
853 {
854 crate::fill_rect::fill_with_color(area, color);
855 Ok(())
856 }
857 }
858
859 /// Clears the entire background area.
860 ///
861 /// This method fills the entire UI bounds with the background color
862 /// from the current style and sets the cleared flag to `true`.
863 ///
864 /// # Returns
865 ///
866 /// `Ok(())` if clearing succeeds, `Err(GuiError)` otherwise.
867 pub fn clear_background(&mut self) -> GuiResult<()> {
868 self.cleared = true;
869 self.clear_area_raw(&self.bounds.clone(), self.style.background_color)
870 }
871}
872
873impl<'a, COL, DRAW> Ui<'a, DRAW, COL>
874where
875 DRAW: DrawTarget<Color = COL>,
876 COL: PixelColor,
877{
878 /// Draws a drawable item to the UI.
879 ///
880 /// This method provides direct access to draw any item that implements
881 /// the `Drawable` trait to the UI's draw target.
882 ///
883 /// # Arguments
884 ///
885 /// * `item` - Reference to the item to draw.
886 ///
887 /// # Returns
888 ///
889 /// `Ok(())` if drawing succeeds, `Err(GuiError::DrawError)` otherwise.
890 pub fn draw(&mut self, item: &impl Drawable<Color = COL>) -> GuiResult<()> {
891 #[cfg(feature = "debug-color")]
892 {
893 self.update_debug_counter();
894 }
895
896 self.painter.draw(item)
897 }
898}
899
900impl<'a, COL, DRAW> Ui<'a, DRAW, COL>
901where
902 DRAW: DrawTarget<Color = COL>,
903 COL: PixelColor,
904{
905 /// Retrieves the render state for a widget.
906 ///
907 /// This method returns a reference to the render state for the widget
908 /// with the specified ID. The render state contains information about
909 /// whether the widget needs to be redrawn.
910 ///
911 /// # Type Parameters
912 ///
913 /// * `ID` - The widget ID type implementing [`WidgetId`]
914 ///
915 /// # Arguments
916 ///
917 /// * `widget_id` - The identifier of the widget.
918 ///
919 /// # Returns
920 ///
921 /// A reference to the [`RenderState`] if the widget exists,
922 /// `Err(GuiError::InvalidWidgetId)` otherwise.
923 pub fn get_widget_state<ID: WidgetId>(&self, widget_id: ID) -> GuiResult<&RenderState> {
924 self.widget_states.get_state(widget_id)
925 }
926
927 /// Retrieves the internal flag for a widget.
928 ///
929 /// This method returns the internal flag for the widget with the specified ID.
930 /// The internal flag is a boolean value that is used to track the state of the widget.
931 ///
932 /// # Type Parameters
933 ///
934 /// * `ID` - The widget ID type implementing [`WidgetId`]
935 ///
936 /// # Arguments
937 ///
938 /// * `widget_id` - The identifier of the widget.
939 ///
940 /// # Returns
941 ///
942 /// The internal flag as a `bool` value.
943 pub fn get_widget_internal_flag<ID: WidgetId>(&self, widget_id: ID) -> GuiResult<bool> {
944 self.widget_states.get_internal_flag(widget_id)
945 }
946
947 /// Sets the internal flag for a widget.
948 ///
949 /// This method sets the internal flag for the widget with the specified ID.
950 /// The internal flag is a boolean value that is used to track the state of the widget.
951 ///
952 /// # Type Parameters
953 ///
954 /// * `ID` - The widget ID type implementing [`WidgetId`]
955 ///
956 /// # Arguments
957 ///
958 /// * `widget_id` - The identifier of the widget.
959 /// * `value` - The new value for the internal flag.
960 ///
961 /// # Returns
962 ///
963 /// `Ok(())` if setting the internal flag succeeds, `Err(GuiError)` otherwise.
964 pub fn set_widget_internal_flag<ID: WidgetId>(
965 &self,
966 widget_id: ID,
967 value: bool,
968 ) -> GuiResult<()> {
969 self.widget_states.set_internal_flag(widget_id, value)
970 }
971
972 /// Retrieves the animation status for a widget.
973 ///
974 /// This method returns the current animation status for the widget with the
975 /// specified ID. The animation status is a value that tracks the progress of
976 /// the animation.
977 ///
978 /// # Arguments
979 ///
980 /// * `anim_id` - The identifier of the animation.
981 ///
982 /// # Returns
983 ///
984 /// The current animation status as an `Option<i32>` value.
985 #[cfg(feature = "animation")]
986 pub fn take_anim_status(&self, anim_id: crate::prelude::AnimId) -> GuiResult<Option<i32>> {
987 self.widget_states.take_anim_status(anim_id)
988 }
989
990 /// Retrieves the animation status for a widget.
991 ///
992 /// This method returns the current animation status for the widget with the
993 /// specified ID. The animation status is a value that tracks the progress of
994 /// the animation.
995 ///
996 /// # Arguments
997 ///
998 /// * `anim_id` - The identifier of the animation.
999 ///
1000 /// # Returns
1001 ///
1002 /// The current animation status as an `Option<i32>` value.
1003 #[cfg(feature = "animation")]
1004 pub fn get_anim_status(&self, anim_id: crate::prelude::AnimId) -> GuiResult<Option<i32>> {
1005 self.widget_states.get_anim_status(anim_id)
1006 }
1007
1008 /// Forces a widget to be redrawn on the next frame.
1009 ///
1010 /// This method marks the widget with the specified ID as needing to be
1011 /// redrawn, regardless of its current state.
1012 ///
1013 /// # Type Parameters
1014 ///
1015 /// * `ID` - The widget ID type implementing [`WidgetId`]
1016 ///
1017 /// # Arguments
1018 ///
1019 /// * `widget_id` - The identifier of the widget to force redraw.
1020 pub fn force_redraw<ID: WidgetId>(&self, widget_id: ID) {
1021 self.widget_states.force_redraw(widget_id);
1022 }
1023
1024 /// Forces all widgets to be redrawn on the next frame.
1025 ///
1026 /// This method marks all widgets as needing to be redrawn, regardless of
1027 /// their current state. This is useful for refreshing the entire UI.
1028 pub fn force_redraw_all(&self) {
1029 self.widget_states.force_redraw_all();
1030 }
1031}
1032
1033/// Focus management implementation (requires "focus" feature).
1034///
1035/// This implementation provides methods for managing keyboard focus
1036/// and navigation between widgets.
1037#[cfg(feature = "focus")]
1038impl<'a, COL, DRAW> Ui<'a, DRAW, COL>
1039where
1040 DRAW: DrawTarget<Color = COL>,
1041 COL: PixelColor,
1042{
1043 /// Sets the focus state for the UI.
1044 ///
1045 /// This method associates a focus state tracker with the UI, enabling
1046 /// keyboard navigation and focus management.
1047 ///
1048 /// # Type Parameters
1049 ///
1050 /// * `N` - The maximum number of focusable widgets
1051 ///
1052 /// # Arguments
1053 ///
1054 /// * `focus_state` - Mutable reference to the focus state tracker.
1055 pub fn set_focus_state<const N: usize>(
1056 &mut self,
1057 focus_state: &'a mut crate::focus::FocusState<N>,
1058 ) {
1059 self.focus = Some(crate::focus::Focus::new(focus_state));
1060 }
1061
1062 /// Checks if a widget is focused.
1063 ///
1064 /// This method determines if the widget with the specified ID is currently
1065 /// focused. It also handles trigger events and updates the focus state.
1066 ///
1067 /// # Type Parameters
1068 ///
1069 /// * `ID` - The widget ID type implementing [`WidgetId`]
1070 ///
1071 /// # Arguments
1072 ///
1073 /// * `id` - The identifier of the widget to check.
1074 ///
1075 /// # Returns
1076 ///
1077 /// A [`crate::focus::Focused`] enum indicating the focus status.
1078 pub fn check_focused<ID: WidgetId>(&mut self, id: ID) -> crate::focus::Focused {
1079 use crate::focus::Focused;
1080
1081 #[cfg(feature = "popup")]
1082 if self.is_modal_active() {
1083 return Focused::No;
1084 }
1085
1086 if let Some(focus) = self.focus.as_mut() {
1087 self.widget_states.mark_as_interact(id);
1088
1089 if let Some(trigger_id) = focus.tracker.trigger
1090 && trigger_id == id.id() as u16
1091 {
1092 focus.tracker.trigger = None;
1093 return Focused::Trigger;
1094 } else if focus.register_focus(id.id()) && focus.is_focused(id.id()) {
1095 return Focused::Yes;
1096 }
1097 }
1098
1099 Focused::No
1100 }
1101}
1102
1103#[cfg(feature = "popup")]
1104impl<'a, COL, DRAW> Ui<'a, DRAW, COL>
1105where
1106 DRAW: DrawTarget<Color = COL>,
1107 COL: PixelColor,
1108{
1109 pub const fn is_modal_active(&self) -> bool {
1110 self.widget_states.is_modal_active()
1111 }
1112
1113 pub fn set_modal_active(&self, active: bool) {
1114 self.widget_states.set_modal_active(active);
1115 }
1116}
1117
1118/// Debug drawing implementation (requires "debug-color" feature).
1119///
1120/// This implementation provides methods for drawing debug visualizations
1121/// of widget bounds and UI areas.
1122#[cfg(feature = "debug-color")]
1123impl<COL, DRAW> Ui<'_, DRAW, COL>
1124where
1125 DRAW: DrawTarget<Color = COL>,
1126 COL: PixelColor,
1127{
1128 /// Draws a debug border around the UI bounds.
1129 ///
1130 /// This method draws a 1-pixel wide border around the entire UI area
1131 /// using the specified color. This is useful for debugging UI layout.
1132 ///
1133 /// # Arguments
1134 ///
1135 /// * `color` - The color to use for the border.
1136 ///
1137 /// # Returns
1138 ///
1139 /// `Ok(())` if drawing succeeds, `Err(GuiError::DrawError)` otherwise.
1140 pub fn draw_bounds_debug(&mut self, color: COL) -> GuiResult<()> {
1141 use embedded_graphics::primitives::{PrimitiveStyleBuilder, StyledDrawable};
1142
1143 let bounds = self.bounds;
1144 bounds
1145 .draw_styled(
1146 &PrimitiveStyleBuilder::new()
1147 .stroke_color(color)
1148 .stroke_width(1)
1149 .build(),
1150 &mut self.painter,
1151 )
1152 .map_err(|_| GuiError::DrawError)
1153 }
1154
1155 /// Enables debug drawing for widget bounds.
1156 ///
1157 /// This method sets a color that will be used to draw borders around
1158 /// widget areas when they are cleared. This is useful for visualizing
1159 /// widget boundaries during development.
1160 ///
1161 /// # Arguments
1162 ///
1163 /// * `color` - The color to use for widget bound boxes.
1164 pub fn draw_widget_bounds_debug(&mut self, color: COL) {
1165 self.debug_color = Some(color);
1166 }
1167
1168 /// Draws a debug border around a widget area.
1169 ///
1170 /// This method draws a 1-pixel wide border around the specified area
1171 /// if debug drawing is enabled. This is called automatically when
1172 /// clearing widget areas.
1173 ///
1174 /// # Arguments
1175 ///
1176 /// * `area` - The rectangle to draw a border around.
1177 pub fn draw_widget_bound_box(&mut self, area: &Rectangle) {
1178 if let Some(debug_color) = self.debug_color {
1179 use embedded_graphics::primitives::{PrimitiveStyleBuilder, StyledDrawable};
1180
1181 area.draw_styled(
1182 &PrimitiveStyleBuilder::new()
1183 .stroke_color(debug_color)
1184 .stroke_width(1)
1185 .build(),
1186 &mut self.painter,
1187 )
1188 .ok();
1189 }
1190 }
1191
1192 /// Returns the current debug counter value.
1193 ///
1194 /// This method is useful for debugging and tracking the number of draw calls
1195 /// made by the UI context.
1196 pub fn get_debug_counter(&self) -> u32 {
1197 DBG_CNT.load(core::sync::atomic::Ordering::Relaxed)
1198 }
1199
1200 /// Updates the debug counter.
1201 fn update_debug_counter(&mut self) {
1202 DBG_CNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
1203 }
1204}