Skip to main content

reratui_hooks/area/
mod.rs

1//! Component Area Hook - Access to component's render area
2//!
3//! This module provides a professional context-based API for accessing the current
4//! component's render area (Rect). The renderer provides the area via context,
5//! and components can consume it using `use_area()`.
6//!
7//! # Architecture
8//!
9//! - Renderer provides area via `use_context_provider`
10//! - Components consume area via `use_area()` (which calls `use_context`)
11//! - Clean separation of concerns following SOLID principles
12//!
13//! # Use Cases
14//!
15//! - Layout calculations
16//! - Responsive design decisions
17//! - Scroll calculations
18//! - Mouse event handling within component bounds
19
20use crate::context::use_context;
21use ratatui::layout::Rect;
22use std::ops::Deref;
23
24/// Context type for component render area
25///
26/// This is provided by the renderer and consumed by components via `use_area()`.
27///
28/// Implements `Deref<Target = Rect>` so you can access Rect methods directly:
29///
30/// ```rust,ignore
31/// use reratui::prelude::*;
32/// use ratatui::layout::Margin;
33///
34/// let area = use_area();
35/// let width = area.width;  // Direct access to Rect fields
36/// let height = area.height;
37/// let inner = area.inner(&Margin::new(1, 1));  // Call Rect methods
38/// ```
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub struct ComponentArea(pub Rect);
41
42impl Deref for ComponentArea {
43    type Target = Rect;
44
45    fn deref(&self) -> &Self::Target {
46        &self.0
47    }
48}
49
50/// Hook to access the current component's render area
51///
52/// This hook retrieves the component's render area from the context provided
53/// by the renderer. The area represents the Rect that the component is being
54/// rendered into.
55///
56/// # Panics
57///
58/// Panics if called outside of a component render context where the area
59/// has been provided by the renderer.
60///
61/// # Use Cases
62///
63/// - **Layout calculations**: Determine available space for child components
64/// - **Responsive design**: Adjust rendering based on available space
65/// - **Positioning**: Calculate absolute positions within the component
66/// - **Scroll handling**: Track viewport dimensions for scrollable content
67/// - **Mouse events**: Determine if mouse events are within component bounds
68///
69/// # Examples
70///
71/// ## Basic Usage - Direct Field Access
72///
73/// ```rust,ignore
74/// use reratui::prelude::*;
75/// #[component]
76/// fn MyComponent() -> Element {
77///     let area = use_area();
78///     
79///     // Direct access to Rect fields via Deref
80///     println!("Width: {}, Height: {}", area.width, area.height);
81///     println!("Position: ({}, {})", area.x, area.y);
82///     
83///     rsx! {
84///         <Block title="My Component">
85///             <Paragraph>
86///                 {format!("Size: {}x{}", area.width, area.height)}
87///             </Paragraph>
88///         </Block>
89///     }
90/// }
91/// ```
92///
93/// ## Responsive Layout
94///
95/// ```rust,ignore
96/// use reratui::prelude::*;
97/// #[component]
98/// fn AdaptiveLayout() -> Element {
99///     let area = use_area();
100///     
101///     // Direct field access - no .0 needed!
102///     let layout_mode = if area.width < 80 {
103///         "compact"
104///     } else {
105///         "wide"
106///     };
107///     
108///     rsx! {
109///         <Block title={format!("Layout: {}", layout_mode)}>
110///             <Paragraph>
111///                 {format!("Adaptive content")}
112///             </Paragraph>
113///         </Block>
114///     }
115/// }
116/// ```
117///
118/// ## Scroll Viewport
119///
120/// ```rust,ignore
121/// use reratui::prelude::*;
122/// #[component]
123/// fn ScrollableList() -> Element {
124///     let area = use_area();
125///     let (scroll_offset, set_scroll) = use_state(|| 0u16);
126///     
127///     // Calculate visible items based on area height
128///     let visible_count = area.height.saturating_sub(2);
129///     
130///     rsx! {
131///         <Block title="Scrollable List">
132///             <Paragraph>
133///                 {format!("Showing {} items", visible_count)}
134///             </Paragraph>
135///         </Block>
136///     }
137/// }
138/// ```
139///
140/// ## Using Rect Methods
141///
142/// ```rust,ignore
143/// use reratui::prelude::*;
144/// use ratatui::layout::Margin;
145/// #[component]
146/// fn LayoutComponent() -> Element {
147///     let area = use_area();
148///     
149///     // Call Rect methods directly
150///     let inner = area.inner(&Margin::new(1, 1));
151///     let is_empty = area.is_empty();
152///     let area_size = area.area();
153///     
154///     rsx! {
155///         <Block title="Layout">
156///             <Paragraph>
157///                 {format!("Inner: {}x{}", inner.width, inner.height)}
158///             </Paragraph>
159///         </Block>
160///     }
161/// }
162/// ```
163pub fn use_area() -> ComponentArea {
164    use_context::<ComponentArea>()
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use crate::context::use_context_provider;
171    use crate::test_utils::with_component_id;
172    use ratatui::layout::Rect;
173
174    #[test]
175    fn test_use_area_with_context() {
176        // Simulate renderer providing area context
177        with_component_id("RendererComponent", |_ctx| {
178            let test_rect = Rect::new(10, 20, 100, 50);
179            let _area = use_context_provider(|| ComponentArea(test_rect));
180
181            // Simulate child component consuming area
182            with_component_id("ChildComponent", |_ctx| {
183                let area = use_area();
184
185                // Direct field access via Deref
186                assert_eq!(*area, test_rect);
187                assert_eq!(area.width, 100);
188                assert_eq!(area.height, 50);
189                assert_eq!(area.x, 10);
190                assert_eq!(area.y, 20);
191            });
192        });
193    }
194
195    #[test]
196    fn test_use_area_responsive_layout() {
197        // Test responsive behavior with different sizes
198        with_component_id("ResponsiveComponent", |_ctx| {
199            // Small screen
200            let small_rect = Rect::new(0, 0, 60, 20);
201            let _area = use_context_provider(|| ComponentArea(small_rect));
202
203            with_component_id("ChildComponent", |_ctx| {
204                let area = use_area();
205                // Direct field access - no .0 needed!
206                let is_compact = area.width < 80;
207                assert!(is_compact);
208            });
209        });
210
211        with_component_id("ResponsiveComponent", |_ctx| {
212            // Large screen
213            let large_rect = Rect::new(0, 0, 120, 40);
214            let _area = use_context_provider(|| ComponentArea(large_rect));
215
216            with_component_id("ChildComponent", |_ctx| {
217                let area = use_area();
218                // Direct field access
219                let is_compact = area.width < 80;
220                assert!(!is_compact);
221            });
222        });
223    }
224
225    #[test]
226    fn test_use_area_deref() {
227        // Test that Deref works correctly
228        with_component_id("DerefTestComponent", |_ctx| {
229            let test_rect = Rect::new(5, 10, 80, 24);
230            let _area = use_context_provider(|| ComponentArea(test_rect));
231
232            with_component_id("ChildComponent", |_ctx| {
233                let area = use_area();
234
235                // Test direct field access
236                assert_eq!(area.width, 80);
237                assert_eq!(area.height, 24);
238
239                // Test calling Rect methods
240                let area_size = area.area();
241                assert_eq!(area_size, 80 * 24);
242
243                let is_empty = area.is_empty();
244                assert!(!is_empty);
245            });
246        });
247    }
248
249    #[test]
250    #[should_panic(expected = "Context value for type")]
251    fn test_use_area_without_context_panics() {
252        // Calling use_area without context should panic
253        with_component_id("ComponentWithoutContext", |_ctx| {
254            let _area = use_area();
255        });
256    }
257}