Skip to main content

fret_ui_kit/style/
state.rs

1use std::ops::{BitOr, BitOrAssign};
2
3use fret_ui::element::PressableState;
4use fret_ui::{ElementContext, UiHost};
5use smallvec::SmallVec;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[repr(u16)]
9pub enum WidgetState {
10    Disabled = 1 << 0,
11    Hovered = 1 << 1,
12    Active = 1 << 2,
13    Focused = 1 << 3,
14    FocusVisible = 1 << 4,
15    Selected = 1 << 5,
16    Open = 1 << 6,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub struct WidgetStates(u16);
21
22impl WidgetStates {
23    pub const fn empty() -> Self {
24        Self(0)
25    }
26
27    pub const DISABLED: Self = Self(WidgetState::Disabled as u16);
28    pub const HOVERED: Self = Self(WidgetState::Hovered as u16);
29    pub const ACTIVE: Self = Self(WidgetState::Active as u16);
30    pub const FOCUSED: Self = Self(WidgetState::Focused as u16);
31    pub const FOCUS_VISIBLE: Self = Self(WidgetState::FocusVisible as u16);
32    pub const SELECTED: Self = Self(WidgetState::Selected as u16);
33    pub const OPEN: Self = Self(WidgetState::Open as u16);
34
35    pub const fn contains(self, other: Self) -> bool {
36        (self.0 & other.0) == other.0
37    }
38
39    pub fn has(self, state: WidgetState) -> bool {
40        self.contains(Self::from(state))
41    }
42
43    pub fn insert(&mut self, state: WidgetState) {
44        self.0 |= state as u16;
45    }
46
47    pub fn remove(&mut self, state: WidgetState) {
48        self.0 &= !(state as u16);
49    }
50
51    pub fn set(&mut self, state: WidgetState, enabled: bool) {
52        if enabled {
53            self.insert(state);
54        } else {
55            self.remove(state);
56        }
57    }
58
59    pub fn from_pressable<H: UiHost>(
60        cx: &mut ElementContext<'_, H>,
61        st: PressableState,
62        enabled: bool,
63    ) -> Self {
64        let mut states = Self::empty();
65        states.set(WidgetState::Disabled, !enabled);
66        states.set(WidgetState::Hovered, enabled && st.hovered);
67        states.set(WidgetState::Active, enabled && st.pressed);
68        states.set(WidgetState::Focused, enabled && st.focused);
69        states.set(
70            WidgetState::FocusVisible,
71            enabled
72                && st.focused
73                && fret_ui::focus_visible::is_focus_visible(cx.app, Some(cx.window)),
74        );
75        states
76    }
77}
78
79impl From<WidgetState> for WidgetStates {
80    fn from(value: WidgetState) -> Self {
81        Self(value as u16)
82    }
83}
84
85impl BitOr for WidgetStates {
86    type Output = Self;
87
88    fn bitor(self, rhs: Self) -> Self::Output {
89        Self(self.0 | rhs.0)
90    }
91}
92
93impl BitOrAssign for WidgetStates {
94    fn bitor_assign(&mut self, rhs: Self) {
95        self.0 |= rhs.0;
96    }
97}
98
99#[derive(Debug, Clone)]
100pub struct WidgetStateProperty<T> {
101    default: T,
102    overrides: SmallVec<[(WidgetStates, T); 4]>,
103}
104
105impl<T> WidgetStateProperty<T> {
106    pub fn new(default: T) -> Self {
107        Self {
108            default,
109            overrides: SmallVec::new(),
110        }
111    }
112
113    /// Adds an override that applies when `states` is a subset of the current widget state.
114    ///
115    /// Precedence rule: the **last** matching override wins.
116    pub fn when(mut self, states: WidgetStates, value: T) -> Self {
117        self.overrides.push((states, value));
118        self
119    }
120
121    pub fn resolve(&self, states: WidgetStates) -> &T {
122        for (required, value) in self.overrides.iter().rev() {
123            if states.contains(*required) {
124                return value;
125            }
126        }
127        &self.default
128    }
129}
130
131/// Applies ADR 0220 "nullable per-state override" semantics for a single slot.
132///
133/// If the override is present and resolves to `Some(value)` for the current `states`, that value is
134/// returned. Otherwise the provided `default` is returned.
135pub fn resolve_slot<T: Clone>(
136    overrides: Option<&WidgetStateProperty<Option<T>>>,
137    default: T,
138    states: WidgetStates,
139) -> T {
140    if let Some(overrides) = overrides
141        && let Some(value) = overrides.resolve(states).clone()
142    {
143        return value;
144    }
145    default
146}
147
148/// Merges ADR 0220 slot overrides with shallow, right-biased precedence.
149pub fn merge_slot<T>(dst: &mut Option<T>, src: Option<T>) {
150    if src.is_some() {
151        *dst = src;
152    }
153}