Skip to main content

uzor_core/widgets/
scrollable.rs

1//! Scrollable container geometry and configuration
2//!
3//! High-level abstraction for scrollable content areas.
4//! Handles offset calculation and geometry computation for headless architecture.
5
6use crate::types::{Rect, ScrollState};
7use serde::{Deserialize, Serialize};
8
9/// Response from scrollable container geometry calculation
10#[derive(Clone, Debug, Default, Serialize, Deserialize)]
11pub struct ScrollableResponse {
12    /// Total content size (as measured)
13    pub content_size: f64,
14    /// Viewport size
15    pub viewport_size: f64,
16    /// Whether scrollbar is visible
17    pub has_scrollbar: bool,
18    /// Viewport rectangle
19    pub viewport: Rect,
20    /// Content area rectangle (excluding scrollbar)
21    pub content_area: Rect,
22}
23
24/// Configuration for scrollable container
25#[derive(Clone, Debug, Serialize, Deserialize)]
26pub struct ScrollableConfig {
27    /// Width of scrollbar
28    pub scrollbar_size: f64,
29    /// Whether to always show scrollbar
30    pub always_show_scrollbar: bool,
31}
32
33impl Default for ScrollableConfig {
34    fn default() -> Self {
35        Self {
36            scrollbar_size: 8.0,
37            always_show_scrollbar: false,
38        }
39    }
40}
41
42/// Scrollable container for managing scrollable content rendering
43pub struct ScrollableContainer {
44    viewport: Rect,
45    scroll_offset: f64,
46    #[allow(dead_code)]
47    is_dragging: bool,
48    config: ScrollableConfig,
49}
50
51impl ScrollableContainer {
52    pub fn new(viewport: Rect, scroll_state: &ScrollState, config: Option<ScrollableConfig>) -> Self {
53        Self {
54            viewport,
55            scroll_offset: scroll_state.offset,
56            is_dragging: scroll_state.is_dragging,
57            config: config.unwrap_or_default(),
58        }
59    }
60
61    /// Get content area rectangle (excludes scrollbar space)
62    pub fn content_area(&self) -> Rect {
63        Rect::new(
64            self.viewport.x,
65            self.viewport.y,
66            self.viewport.width - self.config.scrollbar_size,
67            self.viewport.height,
68        )
69    }
70
71    /// Calculate scrollable area geometry
72    pub fn calculate(self, content_height: f64) -> ScrollableResponse {
73        let needs_scrollbar = content_height > self.viewport.height || self.config.always_show_scrollbar;
74
75        let content_area = Rect::new(
76            self.viewport.x,
77            self.viewport.y,
78            self.viewport.width - self.config.scrollbar_size,
79            self.viewport.height,
80        );
81
82        ScrollableResponse {
83            content_size: content_height,
84            viewport_size: self.viewport.height,
85            has_scrollbar: needs_scrollbar,
86            viewport: self.viewport,
87            content_area,
88        }
89    }
90
91    pub fn content_y(&self) -> f64 {
92        self.viewport.y - self.scroll_offset
93    }
94
95    pub fn content_width(&self) -> f64 {
96        self.viewport.width - self.config.scrollbar_size
97    }
98}