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}