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}