Skip to main content

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}