Skip to main content

matrix_gui/
widget_state.rs

1use core::cell::Cell;
2use enum_iterator::Sequence;
3
4use crate::ui::{GuiError, GuiResult};
5
6pub trait WidgetId: Copy + Sequence + PartialEq + Default {
7    fn id(&self) -> usize;
8}
9
10#[derive(Clone, Copy, Debug, PartialEq)]
11#[non_exhaustive]
12#[repr(u8)]
13pub enum RenderStatus {
14    NeedsRedraw = 0,
15    Rendered = 1,
16    Normal = 2,
17    Dragging = 3,
18    Pressed = 4,
19    Released = 5,
20    Focused = 6,
21    Defocused = 7,
22    Triggered = 8,
23    Inactive = 9,
24    UserStatus0 = 10,
25    UserStatus1 = 11,
26    UserStatus2 = 12,
27    UserStatus3 = 13,
28    Unknown = 127,
29}
30
31impl From<u8> for RenderStatus {
32    fn from(val: u8) -> Self {
33        match val {
34            0 => Self::NeedsRedraw,
35            1 => Self::Rendered,
36            2 => Self::Normal,
37            3 => Self::Dragging,
38            4 => Self::Pressed,
39            5 => Self::Released,
40            6 => Self::Focused,
41            7 => Self::Defocused,
42            8 => Self::Triggered,
43            9 => Self::Inactive,
44            10 => Self::UserStatus0,
45            11 => Self::UserStatus1,
46            12 => Self::UserStatus2,
47            13 => Self::UserStatus3,
48            _ => Self::Unknown,
49        }
50    }
51}
52
53#[derive(Clone, Debug)]
54pub struct RenderState(Cell<u8>);
55
56#[cfg(not(feature = "interaction"))]
57impl RenderState {
58    #[inline]
59    const fn raw_val(&self) -> u8 {
60        self.0.get()
61    }
62
63    #[inline]
64    const fn interactive(&self) -> u8 {
65        0
66    }
67
68    pub fn set_status(&self, val: RenderStatus) {
69        self.0.set(val as u8);
70    }
71}
72
73#[cfg(feature = "interaction")]
74impl RenderState {
75    const MASK_VALUE: u8 = 0x7F; // 7 bits for value
76    const MASK_INTERACTIVE: u8 = 0x80; // 1 bit for interactive
77
78    #[inline]
79    const fn raw_val(&self) -> u8 {
80        self.0.get() & Self::MASK_VALUE
81    }
82
83    #[inline]
84    const fn interactive(&self) -> u8 {
85        self.0.get() & Self::MASK_INTERACTIVE
86    }
87
88    #[inline]
89    pub fn mark_as_interact(&self) {
90        self.0.set(self.0.get() | Self::MASK_INTERACTIVE);
91    }
92
93    pub fn set_status(&self, val: RenderStatus) {
94        self.0.set(self.interactive() | val as u8);
95    }
96}
97
98impl RenderState {
99    pub const fn new(status: RenderStatus) -> Self {
100        Self(Cell::new(status as u8))
101    }
102
103    pub fn new_array<const N: usize>() -> [Self; N] {
104        core::array::from_fn(|_| RenderState::needs_redraw())
105    }
106
107    pub const fn needs_redraw() -> Self {
108        Self(Cell::new(RenderStatus::NeedsRedraw as u8))
109    }
110
111    pub fn status(&self) -> RenderStatus {
112        self.raw_val().into()
113    }
114
115    #[inline]
116    pub const fn is_interact(&self) -> bool {
117        self.interactive() != 0
118    }
119
120    #[inline]
121    pub const fn is_static(&self) -> bool {
122        !self.is_interact()
123    }
124
125    pub fn force_redraw(&self) {
126        self.0.set(RenderStatus::NeedsRedraw as u8);
127    }
128
129    pub fn compare_set(&self, other: RenderStatus) -> bool {
130        let result = self.raw_val() == other as u8;
131        if !result {
132            self.set_status(other);
133        }
134        result
135    }
136
137    pub fn compare(&self, other: RenderStatus) -> bool {
138        self.raw_val() == other as u8
139    }
140}
141
142impl PartialEq for RenderState {
143    fn eq(&self, other: &Self) -> bool {
144        self.0 == other.0
145    }
146}
147
148#[derive(Debug)]
149pub struct WidgetStates<'a> {
150    states: &'a [RenderState],
151    #[cfg(feature = "animation")]
152    anim_status: &'a [crate::prelude::AnimStatus],
153    #[cfg(feature = "popup")]
154    modal_active: Cell<bool>,
155}
156
157impl<'a> WidgetStates<'a> {
158    #[cfg(not(feature = "animation"))]
159    pub const fn new(states: &'a [RenderState]) -> Self {
160        WidgetStates {
161            states,
162            #[cfg(feature = "popup")]
163            modal_active: Cell::new(false),
164        }
165    }
166
167    pub fn get_state<ID: WidgetId>(&self, widget_id: ID) -> GuiResult<&RenderState> {
168        #[cfg(feature = "popup")]
169        if self.is_modal_active() {
170            return Err(GuiError::ModalActive);
171        }
172        self.states
173            .get(widget_id.id())
174            .ok_or(GuiError::InvalidWidgetId)
175    }
176
177    pub fn set_status<ID: WidgetId>(&self, widget_id: ID, status: RenderStatus) -> bool {
178        if let Some(state) = self.states.get(widget_id.id()) {
179            state.set_status(status);
180            true
181        } else {
182            false
183        }
184    }
185
186    pub fn compare_set<ID: WidgetId>(&self, widget_id: ID, value: RenderStatus) -> bool {
187        if let Some(state) = self.states.get(widget_id.id()) {
188            state.compare_set(value)
189        } else {
190            false
191        }
192    }
193
194    pub fn force_redraw_all(&self) {
195        #[cfg(feature = "popup")]
196        {
197            self.set_modal_active(false);
198        }
199
200        for state in self.states.iter() {
201            state.force_redraw();
202        }
203    }
204
205    pub fn force_redraw_range<ID: WidgetId>(&self, start: ID, end: ID) {
206        let mut curr = start;
207        self.force_redraw(curr);
208        while let Some(next) = curr.next() {
209            self.force_redraw(next);
210
211            if next == end {
212                break;
213            }
214            curr = next;
215        }
216    }
217
218    pub fn force_redraw_multi<ID: WidgetId>(&self, widget_ids: &[ID]) {
219        for widget_id in widget_ids {
220            self.force_redraw(*widget_id);
221        }
222    }
223
224    pub fn force_redraw<ID: WidgetId>(&self, widget_id: ID) {
225        if let Some(state) = self.states.get(widget_id.id()) {
226            state.force_redraw();
227        }
228    }
229}
230
231#[cfg(feature = "popup")]
232impl<'a> WidgetStates<'a> {
233    pub fn set_modal_active(&self, modal_status: bool) {
234        self.modal_active.set(modal_status);
235    }
236
237    pub const fn is_modal_active(&self) -> bool {
238        self.modal_active.get()
239    }
240}
241
242#[cfg(feature = "animation")]
243impl<'a> WidgetStates<'a> {
244    pub const fn new(
245        states: &'a [RenderState],
246        anim_status: &'a [crate::prelude::AnimStatus],
247    ) -> Self {
248        WidgetStates {
249            states,
250            anim_status,
251            #[cfg(feature = "popup")]
252            modal_active: Cell::new(false),
253        }
254    }
255
256    pub fn take_anim_status(&self, anim_id: crate::prelude::AnimId) -> GuiResult<Option<i32>> {
257        self.anim_status
258            .get(anim_id as usize)
259            .map_or(Err(GuiError::InvalidAnimId), |s| Ok(s.take()))
260    }
261
262    pub fn get_anim_status(&self, anim_id: crate::prelude::AnimId) -> GuiResult<Option<i32>> {
263        self.anim_status
264            .get(anim_id as usize)
265            .map_or(Err(GuiError::InvalidAnimId), |s| Ok(s.get()))
266    }
267}
268
269impl<'a> WidgetStates<'a> {
270    #[cfg(feature = "interaction")]
271    pub(crate) fn mark_as_interact<ID: WidgetId>(&self, widget_id: ID) {
272        if let Some(state) = self.states.get(widget_id.id()) {
273            state.mark_as_interact();
274        }
275    }
276
277    pub fn should_redraw<ID: WidgetId>(&self, widget_id: ID) -> bool {
278        if let Ok(state) = self.get_state(widget_id) {
279            state.compare(RenderStatus::NeedsRedraw)
280        } else {
281            false
282        }
283    }
284
285    pub fn should_redraw_multi<ID: WidgetId>(&self, widget_ids: &[ID]) -> bool {
286        for widget_id in widget_ids {
287            if let Ok(state) = self.get_state(*widget_id) {
288                if state.compare(RenderStatus::NeedsRedraw) {
289                    return true;
290                }
291            }
292        }
293
294        false
295    }
296
297    pub fn should_redraw_static(&self) -> bool {
298        for state in self.states {
299            if state.is_static() && state.compare(RenderStatus::NeedsRedraw) {
300                return true;
301            }
302        }
303
304        false
305    }
306
307    pub fn should_redraw_any(&self) -> bool {
308        for state in self.states {
309            if state.compare(RenderStatus::NeedsRedraw) {
310                return true;
311            }
312        }
313
314        false
315    }
316}