Skip to main content

uzor_core/
context.rs

1//! UZOR central context and Immediate Mode API
2//!
3//! The Context is the primary entry point for the UZOR API. It manages
4//! input processing, layout computation, and persistent widget state.
5//!
6//! This is a HEADLESS architecture - Context only handles geometry and interaction,
7//! not rendering. Platforms are responsible for visual output.
8
9use crate::animation::AnimationCoordinator;
10use crate::input::InputState;
11use crate::layout::tree::LayoutTree;
12use crate::state::StateRegistry;
13use crate::types::{Rect, WidgetId, WidgetState, ScrollState};
14use crate::widgets;
15use serde::{Deserialize, Serialize};
16
17/// Button interaction response (used by Context::button)
18#[derive(Clone, Debug, Default, Serialize, Deserialize)]
19pub struct ButtonResponse {
20    /// Whether button was clicked this frame
21    pub clicked: bool,
22    /// Whether button is currently hovered
23    pub hovered: bool,
24    /// Whether button is currently pressed
25    pub pressed: bool,
26    /// Current widget state
27    pub state: WidgetState,
28    /// Button rectangle (for platform rendering)
29    pub rect: Rect,
30}
31
32/// The central brain of the UZOR engine
33pub struct Context {
34    /// Transient input state for the current frame
35    pub input: InputState,
36
37    /// Calculated layout rectangles for all widgets
38    pub layout: LayoutTree,
39
40    /// Persistent behavioral state (scroll, focus, etc.)
41    pub registry: StateRegistry,
42
43    /// Animation coordinator for managing widget animations
44    pub animations: AnimationCoordinator,
45
46    /// Time since startup in seconds (for animations)
47    pub time: f64,
48}
49
50impl Context {
51    pub fn new(root_node: crate::layout::types::LayoutNode) -> Self {
52        Self {
53            input: InputState::default(),
54            layout: LayoutTree::new(root_node),
55            registry: StateRegistry::new(),
56            animations: AnimationCoordinator::new(),
57            time: 0.0,
58        }
59    }
60
61    /// Begin a new frame with updated input
62    pub fn begin_frame(&mut self, input: InputState, viewport: Rect) {
63        self.input = input;
64        self.time = self.input.time;
65
66        // Update animations for this frame
67        self.animations.update(self.time);
68
69        // Re-compute layout based on current viewport
70        self.layout.compute(viewport);
71    }
72
73    /// Access persistent state for a widget
74    pub fn state<T: 'static + Send + Sync + Default>(&mut self, id: impl Into<WidgetId>) -> &mut T {
75        self.registry.get_or_insert_with(id.into(), T::default)
76    }
77
78    /// Helper to get widget rectangle from computed layout
79    pub fn widget_rect(&self, id: &WidgetId) -> Rect {
80        self.layout.get_rect(id).unwrap_or_default()
81    }
82
83    // =========================================================================
84    // Immediate Mode API (Headless - Interaction Detection Only)
85    // =========================================================================
86
87    /// Calculate button interaction state
88    pub fn button(&mut self, id: impl Into<WidgetId>) -> ButtonResponse {
89        let id = id.into();
90        let rect = self.widget_rect(&id);
91        let is_hovered = self.input.is_hovered(&rect);
92        let clicked = is_hovered && self.input.is_clicked();
93
94        let state = if clicked {
95            WidgetState::Pressed
96        } else if is_hovered {
97            WidgetState::Hovered
98        } else {
99            WidgetState::Normal
100        };
101
102        ButtonResponse {
103            clicked,
104            hovered: is_hovered,
105            pressed: clicked,
106            state,
107            rect,
108        }
109    }
110
111    /// Calculate checkbox interaction state
112    pub fn checkbox(&mut self, id: impl Into<WidgetId>, checked: bool) -> widgets::checkbox::CheckboxResponse {
113        let id = id.into();
114        let rect = self.widget_rect(&id);
115        let is_hovered = self.input.is_hovered(&rect);
116        let clicked = is_hovered && self.input.is_clicked();
117
118        let state = if clicked {
119            WidgetState::Pressed
120        } else if is_hovered {
121            WidgetState::Hovered
122        } else {
123            WidgetState::Normal
124        };
125
126        let toggled = clicked;
127        let new_checked = if toggled { !checked } else { checked };
128
129        widgets::checkbox::CheckboxResponse {
130            toggled,
131            new_checked,
132            hovered: is_hovered,
133            state,
134            rect,
135        }
136    }
137
138    /// Calculate scroll area geometry and physics
139    pub fn scroll_area(&mut self, id: impl Into<WidgetId>, content_height: f64) -> (Rect, ScrollState) {
140        let id = id.into();
141        let viewport = self.widget_rect(&id);
142        let is_hovered = self.input.is_hovered(&viewport);
143
144        let dt = self.input.dt;
145        let scroll_delta_y = if is_hovered { self.input.scroll_delta.1 } else { 0.0 };
146
147        let state = self.state::<ScrollState>(id);
148        state.content_size = content_height;
149
150        if scroll_delta_y != 0.0 {
151            state.velocity -= scroll_delta_y * 1500.0;
152        }
153
154        state.offset += state.velocity * dt;
155        state.velocity *= 0.90;
156
157        if state.velocity.abs() < 0.1 {
158            state.velocity = 0.0;
159        }
160
161        let max_scroll = (content_height - viewport.height).max(0.0);
162        if state.offset < 0.0 {
163            state.offset = 0.0;
164            state.velocity = 0.0;
165        } else if state.offset > max_scroll {
166            state.offset = max_scroll;
167            state.velocity = 0.0;
168        }
169
170        (viewport, state.clone())
171    }
172
173    /// Calculate interaction state for an icon button
174    pub fn icon_button(
175        &mut self,
176        id: impl Into<WidgetId>,
177    ) -> widgets::icon_button::IconButtonResponse {
178        let id = id.into();
179        let rect = self.widget_rect(&id);
180        let is_hovered = self.input.is_hovered(&rect);
181        let clicked = is_hovered && self.input.is_clicked();
182
183        let state = if is_hovered {
184            if self.input.is_mouse_down() {
185                WidgetState::Pressed
186            } else {
187                WidgetState::Hovered
188            }
189        } else {
190            WidgetState::Normal
191        };
192
193        if clicked {
194            println!("[UZOR Core] Button '{:?}' CLICKED at rect {:?}", id, rect);
195        } else if is_hovered {
196            println!("[UZOR Core] Button '{:?}' HOVERED at rect {:?}", id, rect);
197        }
198
199        widgets::icon_button::IconButtonResponse {
200            clicked,
201            hovered: is_hovered,
202            state,
203        }
204    }
205}