uzor_core/widgets/container/state.rs
1//! Container state adapter - Contract/Connector for scroll state management
2//!
3//! # Architecture Role
4//!
5//! **ContainerState is a CONTRACT/CONNECTOR trait** that connects:
6//! - Factory rendering functions (`factory/render.rs`)
7//! - External state management systems (app state, Redux, ECS, etc.)
8//!
9//! # How It Works
10//!
11//! ```text
12//! ┌─────────────────────────────────────────────────────────────┐
13//! │ 1. External State Manager (e.g., AppState, UIState) │
14//! │ - Stores scroll positions and interaction state │
15//! │ - Implements ContainerState trait (mapping) │
16//! └─────────────────────────────────────────────────────────────┘
17//! ↓
18//! ┌─────────────────────────────────────────────────────────────┐
19//! │ 2. ContainerState trait (THIS MODULE) │
20//! │ - Defines contract (which state containers need) │
21//! │ - Acts as connector interface │
22//! └─────────────────────────────────────────────────────────────┘
23//! ↓
24//! ┌─────────────────────────────────────────────────────────────┐
25//! │ 3. Factory render functions (factory/render.rs) │
26//! │ - Accept &ContainerState or &mut ContainerState │
27//! │ - Call trait methods to get/update state │
28//! └─────────────────────────────────────────────────────────────┘
29//! ```
30//!
31//! # What is Container State?
32//!
33//! Container state tracks **scrollbar interaction state** - ephemeral data during scrolling:
34//! - **Scroll offset** - Current vertical scroll position in pixels
35//! - **Dragging state** - Is scrollbar thumb being dragged?
36//! - **Hover state** - Is mouse over scrollbar?
37//!
38//! **NOT content state** - The container's content (children, data) lives in application domain.
39//! ContainerState only tracks scroll/interaction state.
40//!
41//! # Implementation Example
42//!
43//! Each external project implements ContainerState for their state manager:
44//!
45//! ```rust,ignore
46//! // In ui/app_state.rs (or your state module)
47//! pub struct AppState {
48//! pub scroll_offsets: HashMap<String, f64>,
49//! pub scrollbar_dragging: Option<String>, // container_id being dragged
50//! pub scrollbar_hovered: Option<String>, // container_id being hovered
51//! // ... other app state
52//! }
53//!
54//! impl ContainerState for AppState {
55//! fn scroll_offset(&self, container_id: &str) -> f64 {
56//! *self.scroll_offsets.get(container_id).unwrap_or(&0.0)
57//! }
58//!
59//! fn is_scrollbar_dragging(&self, container_id: &str) -> bool {
60//! self.scrollbar_dragging.as_deref() == Some(container_id)
61//! }
62//!
63//! fn is_scrollbar_hovered(&self, container_id: &str) -> bool {
64//! self.scrollbar_hovered.as_deref() == Some(container_id)
65//! }
66//!
67//! fn set_scroll_offset(&mut self, container_id: &str, offset: f64) {
68//! self.scroll_offsets.insert(container_id.to_string(), offset);
69//! }
70//!
71//! fn set_scrollbar_dragging(&mut self, container_id: &str, dragging: bool) {
72//! if dragging {
73//! self.scrollbar_dragging = Some(container_id.to_string());
74//! } else if self.scrollbar_dragging.as_deref() == Some(container_id) {
75//! self.scrollbar_dragging = None;
76//! }
77//! }
78//!
79//! fn set_scrollbar_hovered(&mut self, container_id: &str, hovered: bool) {
80//! if hovered {
81//! self.scrollbar_hovered = Some(container_id.to_string());
82//! } else if self.scrollbar_hovered.as_deref() == Some(container_id) {
83//! self.scrollbar_hovered = None;
84//! }
85//! }
86//! }
87//! ```
88//!
89//! # Usage in Factory
90//!
91//! ```rust,ignore
92//! use container::factory::render_default;
93//!
94//! let mut app_state = AppState::default();
95//!
96//! // Factory automatically uses ContainerState trait
97//! render_default(
98//! ctx,
99//! &container,
100//! &theme,
101//! "chat_panel", // container_id
102//! &mut app_state, // ← Implements ContainerState
103//! &input_handler,
104//! rect
105//! );
106//!
107//! // State changes during interaction
108//! if mouse_over_scrollbar {
109//! app_state.set_scrollbar_hovered("chat_panel", true);
110//! }
111//! ```
112//!
113//! # Notes
114//!
115//! - **Simple containers need simple state** - Most only need scroll offset
116//! - **Complex containers need more** - Dragging, hover for visual feedback
117//! - **State lives in app** - ContainerState connects to app's state management
118//! - **Factory reads state** - Uses `scroll_offset()` for rendering position
119//! - **Factory writes state** - Uses `set_scroll_offset()` during drag events
120
121use std::collections::HashMap;
122
123/// State adapter for container scroll interaction
124///
125/// This trait defines the contract for tracking container scroll state.
126/// External projects implement this trait to integrate with their state management systems.
127///
128/// # Responsibilities
129///
130/// - Track scroll offset (vertical position in pixels)
131/// - Track scrollbar dragging state (for smooth drag scrolling)
132/// - Track scrollbar hover state (for visual feedback)
133/// - Provide state to rendering functions
134/// - **NOT responsible for content state** (children, data)
135///
136/// # State Ownership
137///
138/// The external project owns the state. Factory borrows via trait:
139/// - **Read state** - `&self` methods (`scroll_offset`, `is_*`)
140/// - **Write state** - `&mut self` methods (`set_*`)
141///
142/// # Container Identity
143///
144/// All methods take `container_id: &str` to identify which container's state to check/update.
145/// This enables a single state manager to track multiple containers.
146pub trait ContainerState {
147 // =========================================================================
148 // Read State (Immutable)
149 // =========================================================================
150
151 /// Get current scroll offset for container
152 ///
153 /// # Parameters
154 /// - `container_id` - Unique identifier for this container (e.g., "chat_panel", "order_list")
155 ///
156 /// # Returns
157 /// Current scroll offset in pixels (0.0 = top, positive = scrolled down)
158 ///
159 /// # Usage
160 /// Factory uses this to offset content rendering and position scrollbar thumb
161 fn scroll_offset(&self, container_id: &str) -> f64;
162
163 /// Check if scrollbar is currently being dragged
164 ///
165 /// # Parameters
166 /// - `container_id` - Unique identifier for this container
167 ///
168 /// # Returns
169 /// `true` if scrollbar thumb is being dragged, `false` otherwise
170 ///
171 /// # Usage
172 /// Factory uses this to continue tracking mouse during drag (even outside scrollbar)
173 fn is_scrollbar_dragging(&self, container_id: &str) -> bool;
174
175 /// Check if scrollbar is currently hovered
176 ///
177 /// # Parameters
178 /// - `container_id` - Unique identifier for this container
179 ///
180 /// # Returns
181 /// `true` if mouse is over scrollbar, `false` otherwise
182 ///
183 /// # Usage
184 /// Factory uses this to apply hover color to scrollbar thumb
185 fn is_scrollbar_hovered(&self, container_id: &str) -> bool;
186
187 // =========================================================================
188 // Write State (Mutable)
189 // =========================================================================
190
191 /// Set scroll offset for container
192 ///
193 /// # Parameters
194 /// - `container_id` - Which container to set scroll for
195 /// - `offset` - New scroll offset in pixels (should be clamped by caller)
196 ///
197 /// # Usage
198 /// Factory calls this during scroll wheel or scrollbar drag events
199 fn set_scroll_offset(&mut self, container_id: &str, offset: f64);
200
201 /// Set scrollbar dragging state
202 ///
203 /// # Parameters
204 /// - `container_id` - Which container's scrollbar
205 /// - `dragging` - `true` when drag starts, `false` when drag ends
206 ///
207 /// # Usage
208 /// Factory calls this on mouse down (start drag) and mouse up (end drag)
209 fn set_scrollbar_dragging(&mut self, container_id: &str, dragging: bool);
210
211 /// Set scrollbar hover state
212 ///
213 /// # Parameters
214 /// - `container_id` - Which container's scrollbar
215 /// - `hovered` - `true` when mouse enters scrollbar, `false` when leaves
216 ///
217 /// # Usage
218 /// Factory calls this during mouse move for hover visual feedback
219 fn set_scrollbar_hovered(&mut self, container_id: &str, hovered: bool);
220}
221
222// =============================================================================
223// Default State Implementation
224// =============================================================================
225
226/// Simple implementation of ContainerState for prototyping
227///
228/// This struct provides a minimal state implementation for external projects
229/// that don't need complex state management integration.
230///
231/// Tracks state for multiple containers using container IDs and HashMaps.
232///
233/// # Usage
234///
235/// ```rust,ignore
236/// use container::state::{ContainerState, SimpleContainerState};
237///
238/// let mut state = SimpleContainerState::new();
239///
240/// // Set scroll offset
241/// state.set_scroll_offset("chat_panel", 150.0);
242/// assert_eq!(state.scroll_offset("chat_panel"), 150.0);
243///
244/// // Simulate scrollbar hover
245/// state.set_scrollbar_hovered("chat_panel", true);
246/// assert!(state.is_scrollbar_hovered("chat_panel"));
247/// assert!(!state.is_scrollbar_hovered("order_list"));
248///
249/// // Simulate drag start
250/// state.set_scrollbar_dragging("chat_panel", true);
251/// assert!(state.is_scrollbar_dragging("chat_panel"));
252/// ```
253///
254/// For production, implement ContainerState for your app's state manager instead.
255#[derive(Clone, Debug, Default)]
256pub struct SimpleContainerState {
257 /// Scroll offsets by container ID
258 pub scroll_offsets: HashMap<String, f64>,
259
260 /// Currently dragging scrollbar (container ID)
261 pub dragging: Option<String>,
262
263 /// Currently hovered scrollbar (container ID)
264 pub hovered: Option<String>,
265}
266
267impl SimpleContainerState {
268 /// Create new container state
269 pub fn new() -> Self {
270 Self {
271 scroll_offsets: HashMap::new(),
272 dragging: None,
273 hovered: None,
274 }
275 }
276}
277
278impl ContainerState for SimpleContainerState {
279 fn scroll_offset(&self, container_id: &str) -> f64 {
280 *self.scroll_offsets.get(container_id).unwrap_or(&0.0)
281 }
282
283 fn is_scrollbar_dragging(&self, container_id: &str) -> bool {
284 self.dragging.as_deref() == Some(container_id)
285 }
286
287 fn is_scrollbar_hovered(&self, container_id: &str) -> bool {
288 self.hovered.as_deref() == Some(container_id)
289 }
290
291 fn set_scroll_offset(&mut self, container_id: &str, offset: f64) {
292 self.scroll_offsets.insert(container_id.to_string(), offset);
293 }
294
295 fn set_scrollbar_dragging(&mut self, container_id: &str, dragging: bool) {
296 if dragging {
297 self.dragging = Some(container_id.to_string());
298 } else if self.dragging.as_deref() == Some(container_id) {
299 self.dragging = None;
300 }
301 }
302
303 fn set_scrollbar_hovered(&mut self, container_id: &str, hovered: bool) {
304 if hovered {
305 self.hovered = Some(container_id.to_string());
306 } else if self.hovered.as_deref() == Some(container_id) {
307 self.hovered = None;
308 }
309 }
310}