Skip to main content

matrix_gui/
widget_state.rs

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