ailloli_ui_core/style/
interaction.rs

1//! Widget interaction flags and precedence-based state style selection.
2
3/// Pointer/focus/disabled flags for a widget at paint or input time.
4///
5/// # Examples
6///
7/// ```
8/// use ailloli_ui_core::style::InteractionState;
9/// assert!(!InteractionState::normal().pressed);
10/// ```
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12pub struct InteractionState {
13    /// `true` while the pointer hover region contains the active pointer.
14    pub hovered: bool,
15    /// `true` while the widget owns an active press gesture.
16    pub pressed: bool,
17    /// `true` while the widget owns keyboard focus.
18    pub focused: bool,
19    /// `true` when interaction should be disabled.
20    pub disabled: bool,
21}
22
23impl InteractionState {
24    /// Default interactive state (not hovered, pressed, or focused).
25    ///
26    /// # Examples
27    ///
28    /// ```
29    /// use ailloli_ui_core::style::InteractionState;
30    /// assert_eq!(InteractionState::normal(), InteractionState::default());
31    /// ```
32    pub fn normal() -> Self {
33        Self::default()
34    }
35
36    /// Disabled widget (other flags cleared).
37    ///
38    /// # Examples
39    ///
40    /// ```
41    /// use ailloli_ui_core::style::InteractionState;
42    /// assert!(InteractionState::disabled().disabled);
43    /// ```
44    pub fn disabled() -> Self {
45        Self {
46            disabled: true,
47            ..Self::default()
48        }
49    }
50}
51
52/// Style map keyed by interaction state (hover, press, focus, disabled).
53///
54/// # Examples
55///
56/// ```
57/// use ailloli_ui_core::style::{InteractionState, StateStyle};
58/// let styles = StateStyle { normal: 0, hovered: Some(1), pressed: None, focused: None, disabled: None };
59/// assert_eq!(styles.resolve(InteractionState { hovered: true, ..InteractionState::default() }), 1);
60/// ```
61#[derive(Clone, Debug, PartialEq)]
62pub struct StateStyle<T> {
63    /// Required fallback style when no applicable override exists.
64    pub normal: T,
65    /// Optional hover override.
66    pub hovered: Option<T>,
67    /// Optional active-press override.
68    pub pressed: Option<T>,
69    /// Optional keyboard-focus override.
70    pub focused: Option<T>,
71    /// Optional disabled override.
72    pub disabled: Option<T>,
73}
74
75impl<T: Clone> StateStyle<T> {
76    /// Clones the highest-priority available override for `state`.
77    ///
78    /// Precedence is disabled, pressed, hovered, focused, then normal. A true
79    /// flag with a `None` override falls through to the next true flag rather
80    /// than immediately selecting normal.
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// use ailloli_ui_core::style::{InteractionState, StateStyle};
86    /// let styles = StateStyle { normal: "normal", hovered: Some("hover"), pressed: Some("press"), focused: None, disabled: None };
87    /// let state = InteractionState { hovered: true, pressed: true, ..InteractionState::default() };
88    /// assert_eq!(styles.resolve(state), "press");
89    /// ```
90    pub fn resolve(&self, state: InteractionState) -> T {
91        if state.disabled {
92            if let Some(value) = &self.disabled {
93                return value.clone();
94            }
95        }
96
97        if state.pressed {
98            if let Some(value) = &self.pressed {
99                return value.clone();
100            }
101        }
102
103        if state.hovered {
104            if let Some(value) = &self.hovered {
105                return value.clone();
106            }
107        }
108
109        if state.focused {
110            if let Some(value) = &self.focused {
111                return value.clone();
112            }
113        }
114
115        self.normal.clone()
116    }
117}