Skip to main content

minifb_ui/ui/
scrollarea.rs

1pub struct ScrollArea {
2    pub pos_x: usize,
3    pub pos_y: usize,
4    pub width: usize,
5    pub height: usize,
6
7    pub content_height: usize,
8    scroll_offset: f32,
9    pub scroll_speed: f32,
10
11    pub scrollbar_width: usize,
12    pub scrollbar_color: crate::color::Color,
13    pub scrollbar_track_color: crate::color::Color,
14    pub scrollbar_radius: usize,
15
16    dragging: bool,
17    drag_start_y: f32,
18    drag_start_offset: f32,
19    lmb_was_down: bool,
20}
21
22impl Default for ScrollArea {
23    fn default() -> Self {
24        Self {
25            pos_x: 0,
26            pos_y: 0,
27            width: 200,
28            height: 300,
29            content_height: 300,
30            scroll_offset: 0.0,
31            scroll_speed: 3.0,
32            scrollbar_width: 6,
33            scrollbar_color: crate::color::Color::rgba(200, 200, 210, 120),
34            scrollbar_track_color: crate::color::Color::rgba(60, 60, 80, 40),
35            scrollbar_radius: 3,
36            dragging: false,
37            drag_start_y: 0.0,
38            drag_start_offset: 0.0,
39            lmb_was_down: false,
40        }
41    }
42}
43
44impl ScrollArea {
45    pub fn position(mut self, x: usize, y: usize) -> Self {
46        self.pos_x = x;
47        self.pos_y = y;
48        self
49    }
50
51    pub fn size(mut self, width: usize, height: usize) -> Self {
52        self.width = width;
53        self.height = height;
54        self
55    }
56
57    pub fn content_height(mut self, h: usize) -> Self {
58        self.content_height = h;
59        self
60    }
61
62    pub fn scroll_speed(mut self, speed: f32) -> Self {
63        self.scroll_speed = speed;
64        self
65    }
66
67    pub fn scrollbar_width(mut self, w: usize) -> Self {
68        self.scrollbar_width = w;
69        self
70    }
71
72    pub fn scrollbar_color(mut self, color: crate::color::Color) -> Self {
73        self.scrollbar_color = color;
74        self
75    }
76
77    pub fn scrollbar_track_color(mut self, color: crate::color::Color) -> Self {
78        self.scrollbar_track_color = color;
79        self
80    }
81
82    pub fn scrollbar_radius(mut self, r: usize) -> Self {
83        self.scrollbar_radius = r;
84        self
85    }
86
87    /// Sets the content height dynamically (call before draw each frame if content changes)
88    pub fn set_content_height(&mut self, h: usize) {
89        self.content_height = h;
90    }
91
92    /// Returns current scroll offset in pixels
93    pub fn offset(&self) -> f32 {
94        self.scroll_offset
95    }
96
97    /// Sets scroll offset directly
98    pub fn set_offset(&mut self, offset: f32) {
99        self.scroll_offset = offset.max(0.0);
100        self.clamp_offset();
101    }
102
103    /// Returns true if content overflows the visible area
104    pub fn can_scroll(&self) -> bool {
105        self.content_height > self.height
106    }
107
108    fn max_offset(&self) -> f32 {
109        (self.content_height as f32 - self.height as f32).max(0.0)
110    }
111
112    fn clamp_offset(&mut self) {
113        self.scroll_offset = self.scroll_offset.clamp(0.0, self.max_offset());
114    }
115
116    /// Updates scroll state (call once per frame, before drawing content)
117    /// Pushes a clip region. Call `end_draw` after drawing content.
118    pub fn begin_draw(&mut self, window: &mut crate::window::Window) {
119        let mouse = window.get_mouse_state();
120        let mx = mouse.pos_x;
121        let my = mouse.pos_y;
122        let lmb = mouse.lmb_clicked;
123
124        let in_bounds = mx >= self.pos_x as f32
125            && mx < (self.pos_x + self.width) as f32
126            && my >= self.pos_y as f32
127            && my < (self.pos_y + self.height) as f32;
128
129        // Mouse wheel scrolling
130        if in_bounds {
131            if let Some(scroll) = window.window.get_scroll_wheel() {
132                self.scroll_offset += scroll.1 * self.scroll_speed;
133                self.clamp_offset();
134            }
135        }
136
137        // Scrollbar dragging
138        if self.can_scroll() {
139            let sb_x = self.pos_x + self.width - self.scrollbar_width - 2;
140            let thumb_frac = self.height as f32 / self.content_height as f32;
141            let thumb_h = (thumb_frac * self.height as f32).max(20.0);
142            let track_range = self.height as f32 - thumb_h;
143            let thumb_y = self.pos_y as f32 + (self.scroll_offset / self.max_offset()) * track_range;
144
145            let lmb_just = lmb && !self.lmb_was_down;
146
147            if lmb_just {
148                let on_thumb = mx >= sb_x as f32
149                    && mx < (sb_x + self.scrollbar_width) as f32
150                    && my >= thumb_y
151                    && my < thumb_y + thumb_h;
152                if on_thumb {
153                    self.dragging = true;
154                    self.drag_start_y = my;
155                    self.drag_start_offset = self.scroll_offset;
156                }
157            }
158
159            if self.dragging {
160                if lmb {
161                    let delta = my - self.drag_start_y;
162                    let ratio = delta / track_range;
163                    self.scroll_offset = self.drag_start_offset + ratio * self.max_offset();
164                    self.clamp_offset();
165                } else {
166                    self.dragging = false;
167                }
168            }
169        }
170
171        self.lmb_was_down = lmb;
172
173        // Push clip
174        window.push_clip(self.pos_x, self.pos_y, self.width, self.height);
175    }
176
177    /// Ends the scroll area draw (pops clip) and draws scrollbar
178    pub fn end_draw(&self, window: &mut crate::window::Window) {
179        window.pop_clip();
180
181        // Draw scrollbar if content overflows
182        if self.can_scroll() {
183            let sb_x = self.pos_x + self.width - self.scrollbar_width - 2;
184            let thumb_frac = self.height as f32 / self.content_height as f32;
185            let thumb_h = (thumb_frac * self.height as f32).max(20.0) as usize;
186            let track_range = self.height as f32 - thumb_h as f32;
187            let thumb_y = self.pos_y + ((self.scroll_offset / self.max_offset()) * track_range) as usize;
188
189            // Track
190            window.draw_rect_f(
191                sb_x, self.pos_y, self.scrollbar_width, self.height,
192                self.scrollbar_radius, &self.scrollbar_track_color, 0,
193            );
194
195            // Thumb
196            window.draw_rect_f(
197                sb_x, thumb_y, self.scrollbar_width, thumb_h,
198                self.scrollbar_radius, &self.scrollbar_color, 0,
199            );
200        }
201    }
202}