Skip to main content

embedded_gui/context/
core_impl.rs

1use heapless::Vec;
2
3#[cfg(not(feature = "std"))]
4use crate::math::F32Ext as _;
5use crate::{
6    geometry::{DirtyTracker, Rect},
7    input::UiEvent,
8    present::PresentRegion,
9    render::RenderQuality,
10    style::{Style, Theme, VisualState, WidgetStyle, lerp_style},
11    widget::{MenuContract, StyleClassId, WidgetId},
12    widgets::WidgetNode,
13};
14
15use super::*;
16
17impl<'a, const NODES: usize, const EVENTS: usize, const DIRTY: usize>
18    GuiContext<'a, NODES, EVENTS, DIRTY>
19{
20    pub fn new(viewport: Rect) -> Self {
21        let mut dirty = DirtyTracker::new();
22        let _ = dirty.mark_all(viewport);
23        Self {
24            viewport,
25            widgets: Vec::new(),
26            subscriptions: Vec::new(),
27            dispatch_policies: Vec::new(),
28            class_styles: Vec::new(),
29            events: Vec::new(),
30            dirty,
31            theme: Theme::default(),
32            focus: None,
33            active_focus_group: None,
34            render_quality: RenderQuality::High,
35            long_press_ms: 500,
36            textarea_cursor_blink_ms: 500,
37            textarea_cursor_blink_elapsed_ms: 0,
38            press_repeat_delay_ms: 650,
39            press_repeat_interval_ms: 140,
40            select_double_window_ms: 300,
41            select_elapsed_ms: 0,
42            last_select_id: None,
43            pointer_double_window_ms: 300,
44            pointer_elapsed_ms: 0,
45            last_pointer_id: None,
46            pressed: None,
47            inertia_scroll: None,
48            scroll_physics: ScrollPhysics::default(),
49            state_transition_ms: 0,
50            state_transitions: Vec::new(),
51            widget_press_timings: Vec::new(),
52            widget_key_policies: Vec::new(),
53            widget_key_bindings: Vec::new(),
54            menu_contract: MenuContract::default(),
55            textarea_undo: Vec::new(),
56            textarea_redo: Vec::new(),
57            next_id: 1,
58        }
59    }
60
61    pub const fn viewport(&self) -> Rect {
62        self.viewport
63    }
64
65    pub fn set_viewport(&mut self, viewport: Rect) -> Result<(), GuiError> {
66        self.viewport = viewport;
67        self.dirty.mark_all(viewport)?;
68        Ok(())
69    }
70
71    pub fn clear_widgets(&mut self) -> Result<(), GuiError> {
72        self.widgets.clear();
73        self.subscriptions.clear();
74        self.dispatch_policies.clear();
75        self.class_styles.clear();
76        self.focus = None;
77        self.pressed = None;
78        self.inertia_scroll = None;
79        self.last_select_id = None;
80        self.select_elapsed_ms = 0;
81        self.last_pointer_id = None;
82        self.pointer_elapsed_ms = 0;
83        self.state_transitions.clear();
84        self.widget_press_timings.clear();
85        self.widget_key_policies.clear();
86        self.widget_key_bindings.clear();
87        self.textarea_undo.clear();
88        self.textarea_redo.clear();
89        self.dirty.mark_all(self.viewport)?;
90        Ok(())
91    }
92
93    pub const fn long_press_threshold_ms(&self) -> u32 {
94        self.long_press_ms
95    }
96
97    pub fn set_long_press_threshold_ms(&mut self, threshold_ms: u32) {
98        self.long_press_ms = threshold_ms.max(1);
99    }
100
101    pub fn set_press_repeat_timing(&mut self, delay_ms: u32, interval_ms: u32) {
102        self.press_repeat_delay_ms = delay_ms.max(1);
103        self.press_repeat_interval_ms = interval_ms.max(1);
104    }
105
106    pub fn set_double_select_window_ms(&mut self, window_ms: u32) {
107        self.select_double_window_ms = window_ms.max(1);
108    }
109
110    pub fn set_double_pointer_window_ms(&mut self, window_ms: u32) {
111        self.pointer_double_window_ms = window_ms.max(1);
112    }
113
114    pub fn menu_contract(&self) -> MenuContract {
115        self.menu_contract
116    }
117
118    pub fn set_menu_contract(&mut self, contract: MenuContract) {
119        self.menu_contract = contract;
120    }
121
122    pub fn set_widget_press_timing(
123        &mut self,
124        id: WidgetId,
125        timing: PressTiming,
126    ) -> Result<(), GuiError> {
127        self.node(id).ok_or(GuiError::NotFound)?;
128        let timing = PressTiming {
129            long_press_ms: timing.long_press_ms.max(1),
130            repeat_delay_ms: timing.repeat_delay_ms.max(1),
131            repeat_interval_ms: timing.repeat_interval_ms.max(1),
132        };
133        if let Some((_, current)) = self
134            .widget_press_timings
135            .iter_mut()
136            .find(|(timing_id, _)| *timing_id == id)
137        {
138            *current = timing;
139            return Ok(());
140        }
141        self.widget_press_timings
142            .push((id, timing))
143            .map_err(|_| GuiError::WidgetsFull)
144    }
145
146    pub fn clear_widget_press_timing(&mut self, id: WidgetId) -> Result<(), GuiError> {
147        self.node(id).ok_or(GuiError::NotFound)?;
148        if let Some(pos) = self
149            .widget_press_timings
150            .iter()
151            .position(|(timing_id, _)| *timing_id == id)
152        {
153            self.widget_press_timings.remove(pos);
154        }
155        Ok(())
156    }
157
158    pub fn widget_press_timing(&self, id: WidgetId) -> Result<Option<PressTiming>, GuiError> {
159        self.node(id).ok_or(GuiError::NotFound)?;
160        Ok(self
161            .widget_press_timings
162            .iter()
163            .find(|(timing_id, _)| *timing_id == id)
164            .map(|(_, timing)| *timing))
165    }
166
167    pub fn set_widget_key_input_policy(
168        &mut self,
169        id: WidgetId,
170        policy: WidgetKeyInputPolicy,
171    ) -> Result<(), GuiError> {
172        self.node(id).ok_or(GuiError::NotFound)?;
173        if let Some((_, current)) = self
174            .widget_key_policies
175            .iter_mut()
176            .find(|(policy_id, _)| *policy_id == id)
177        {
178            *current = policy;
179            return Ok(());
180        }
181        self.widget_key_policies
182            .push((id, policy))
183            .map_err(|_| GuiError::WidgetsFull)
184    }
185
186    pub fn clear_widget_key_input_policy(&mut self, id: WidgetId) -> Result<(), GuiError> {
187        self.node(id).ok_or(GuiError::NotFound)?;
188        if let Some(pos) = self
189            .widget_key_policies
190            .iter()
191            .position(|(policy_id, _)| *policy_id == id)
192        {
193            self.widget_key_policies.remove(pos);
194        }
195        Ok(())
196    }
197
198    pub fn widget_key_input_policy(
199        &self,
200        id: WidgetId,
201    ) -> Result<Option<WidgetKeyInputPolicy>, GuiError> {
202        self.node(id).ok_or(GuiError::NotFound)?;
203        Ok(self
204            .widget_key_policies
205            .iter()
206            .find(|(policy_id, _)| *policy_id == id)
207            .map(|(_, policy)| *policy))
208    }
209
210    pub fn set_widget_key_bindings(
211        &mut self,
212        id: WidgetId,
213        bindings: WidgetKeyBindings,
214    ) -> Result<(), GuiError> {
215        self.node(id).ok_or(GuiError::NotFound)?;
216        if let Some((_, current)) = self
217            .widget_key_bindings
218            .iter_mut()
219            .find(|(binding_id, _)| *binding_id == id)
220        {
221            *current = bindings;
222            return Ok(());
223        }
224        self.widget_key_bindings
225            .push((id, bindings))
226            .map_err(|_| GuiError::WidgetsFull)
227    }
228
229    pub fn clear_widget_key_bindings(&mut self, id: WidgetId) -> Result<(), GuiError> {
230        self.node(id).ok_or(GuiError::NotFound)?;
231        if let Some(pos) = self
232            .widget_key_bindings
233            .iter()
234            .position(|(binding_id, _)| *binding_id == id)
235        {
236            self.widget_key_bindings.remove(pos);
237        }
238        Ok(())
239    }
240
241    pub fn widget_key_bindings(&self, id: WidgetId) -> Result<Option<WidgetKeyBindings>, GuiError> {
242        self.node(id).ok_or(GuiError::NotFound)?;
243        Ok(self
244            .widget_key_bindings
245            .iter()
246            .find(|(binding_id, _)| *binding_id == id)
247            .map(|(_, bindings)| *bindings))
248    }
249
250    pub fn set_scroll_physics(
251        &mut self,
252        velocity_threshold: f32,
253        velocity_decay: f32,
254        drag_velocity_blend: f32,
255    ) {
256        self.scroll_physics.velocity_threshold = velocity_threshold.max(0.001);
257        self.scroll_physics.velocity_decay = velocity_decay.clamp(0.01, 0.999);
258        self.scroll_physics.drag_velocity_blend = drag_velocity_blend.clamp(0.01, 1.0);
259    }
260
261    pub fn set_state_transition_duration_ms(&mut self, duration_ms: u32) {
262        self.state_transition_ms = duration_ms;
263        if duration_ms == 0 {
264            self.state_transitions.clear();
265        }
266    }
267
268    pub fn active_state_transitions(&self) -> usize {
269        self.state_transitions.len()
270    }
271
272    pub fn set_textarea_cursor_blink_timing(&mut self, period_ms: u32) {
273        self.textarea_cursor_blink_ms = period_ms.max(1);
274    }
275
276    pub fn widgets(&self) -> &[WidgetNode<'a>] {
277        self.widgets.as_slice()
278    }
279
280    pub fn dirty_regions(&self) -> &[Rect] {
281        self.dirty.as_slice()
282    }
283
284    pub fn present_regions(&self) -> impl Iterator<Item = PresentRegion> + '_ {
285        self.dirty
286            .as_slice()
287            .iter()
288            .copied()
289            .map(PresentRegion::from)
290    }
291
292    pub fn bounding_present_region(&self) -> Option<PresentRegion> {
293        self.dirty.bounding_rect().map(PresentRegion::from)
294    }
295
296    pub fn clear_dirty(&mut self) {
297        self.dirty.clear();
298    }
299
300    pub const fn theme(&self) -> Theme {
301        self.theme
302    }
303
304    pub fn set_theme(&mut self, theme: Theme) -> Result<(), GuiError> {
305        self.theme = theme;
306        self.dirty.mark_all(self.viewport)?;
307        Ok(())
308    }
309
310    pub fn set_style_class<S>(&mut self, class: StyleClassId, style: S) -> Result<(), GuiError>
311    where
312        S: Into<WidgetStyle>,
313    {
314        if class == StyleClassId::NONE {
315            return Ok(());
316        }
317        if let Some((_, slot)) = self.class_styles.iter_mut().find(|(id, _)| *id == class) {
318            *slot = style.into();
319        } else {
320            self.class_styles
321                .push((class, style.into()))
322                .map_err(|_| GuiError::WidgetsFull)?;
323        }
324        self.dirty.mark_all(self.viewport)?;
325        Ok(())
326    }
327
328    pub fn clear_style_class(&mut self, class: StyleClassId) -> Result<(), GuiError> {
329        if let Some(pos) = self.class_styles.iter().position(|(id, _)| *id == class) {
330            self.class_styles.remove(pos);
331            self.dirty.mark_all(self.viewport)?;
332        }
333        Ok(())
334    }
335
336    pub fn set_style_class_state(
337        &mut self,
338        class: StyleClassId,
339        state: VisualState,
340        style: Style,
341    ) -> Result<(), GuiError> {
342        if class == StyleClassId::NONE {
343            return Ok(());
344        }
345        if let Some((_, slot)) = self.class_styles.iter_mut().find(|(id, _)| *id == class) {
346            *slot = slot.with_state_override(state, style);
347        } else {
348            let base = WidgetStyle::new(Style::new()).with_state_override(state, style);
349            self.class_styles
350                .push((class, base))
351                .map_err(|_| GuiError::WidgetsFull)?;
352        }
353        self.dirty.mark_all(self.viewport)?;
354        Ok(())
355    }
356
357    pub fn set_widget_style_class(
358        &mut self,
359        id: WidgetId,
360        class: Option<StyleClassId>,
361    ) -> Result<(), GuiError> {
362        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
363        node.style_class = class.filter(|c| *c != StyleClassId::NONE);
364        self.mark_subtree_dirty(id)
365    }
366
367    pub fn apply_widget_style_transition(
368        &mut self,
369        id: WidgetId,
370        from: VisualState,
371        to: VisualState,
372        t: f32,
373    ) -> Result<(), GuiError> {
374        let node = self.node_mut(id).ok_or(GuiError::NotFound)?;
375        let a = node.style.resolve(from);
376        let b = node.style.resolve(to);
377        let blended = lerp_style(a, b, t);
378        node.style = node.style.with_state_override(VisualState::Normal, blended);
379        self.mark_subtree_dirty(id)
380    }
381
382    pub const fn render_quality(&self) -> RenderQuality {
383        self.render_quality
384    }
385
386    pub fn set_render_quality(&mut self, quality: RenderQuality) -> Result<(), GuiError> {
387        if self.render_quality != quality {
388            self.render_quality = quality;
389            self.dirty.mark_all(self.viewport)?;
390        }
391        Ok(())
392    }
393
394    pub const fn focus(&self) -> Option<WidgetId> {
395        self.focus
396    }
397
398    pub fn set_focus(&mut self, focus: Option<WidgetId>) -> Result<(), GuiError> {
399        if let Some(id) = focus {
400            self.node(id).ok_or(GuiError::NotFound)?;
401            if !self.effective_focusable(id) {
402                return Err(GuiError::NotFound);
403            }
404        }
405
406        let old = self.focus;
407        self.focus = focus;
408        self.textarea_cursor_blink_elapsed_ms = 0;
409        self.set_textarea_cursor_visible(old, true);
410        self.set_textarea_cursor_visible(focus, true);
411        self.start_focus_transitions(old, focus);
412        self.mark_focus_pair(old, focus)?;
413        if let Some(id) = old {
414            self.push_event(UiEvent::Defocused(id))?;
415        }
416        if let Some(id) = focus {
417            self.push_event(UiEvent::Focused(id))?;
418        }
419        self.push_event(UiEvent::FocusChanged { old, new: focus })?;
420        Ok(())
421    }
422}