1use crate::components::ComponentDimensions;
2use crate::draw_utils::print_with_color_and_background_at;
3use crate::{Bounds, ScreenBuffer};
4
5#[derive(Debug, Clone)]
15pub struct HorizontalScrollbar {
16 pub parent_id: String,
18 track_char: &'static str,
20 knob_char: &'static str,
22}
23
24impl HorizontalScrollbar {
25 pub fn new(parent_id: String) -> Self {
27 Self {
28 parent_id,
29 track_char: "─", knob_char: "■", }
32 }
33
34 pub fn should_draw(&self, content_width: usize, viewable_width: usize) -> bool {
36 content_width > viewable_width
37 }
38
39 pub fn get_bounds(&self, parent_bounds: &Bounds) -> (usize, usize, usize) {
41 let component_dims = ComponentDimensions::new(*parent_bounds);
42 let track_bounds = component_dims.horizontal_scrollbar_track_bounds();
43 (track_bounds.y1, track_bounds.x1, track_bounds.x2)
44 }
45
46 pub fn calculate_knob_metrics(
48 &self,
49 content_width: usize,
50 viewable_width: usize,
51 horizontal_scroll: f64,
52 track_width: usize,
53 ) -> (usize, usize) {
54 if track_width == 0 {
55 return (0, 0);
56 }
57
58 use crate::components::dimensions::{Orientation, ScrollDimensions};
60 use crate::Bounds;
61
62 let scroll_dims = ScrollDimensions::new(
63 (content_width, 1), (viewable_width, 1),
65 (horizontal_scroll, 0.0), Bounds::new(0, 0, track_width + 1, 2), );
68
69 let knob_size = scroll_dims.calculate_knob_size(Orientation::Horizontal);
70 let knob_position = scroll_dims.calculate_knob_position(Orientation::Horizontal);
71
72 (knob_position, knob_size)
73 }
74
75 pub fn draw(
78 &self,
79 parent_bounds: &Bounds,
80 content_width: usize,
81 viewable_width: usize,
82 horizontal_scroll: f64,
83 border_color: &Option<String>,
84 bg_color: &Option<String>,
85 buffer: &mut ScreenBuffer,
86 ) {
87 if !self.should_draw(content_width, viewable_width) {
88 return;
89 }
90
91 let (y, start_x, end_x) = self.get_bounds(parent_bounds);
92 let track_width = end_x.saturating_sub(start_x);
93
94 for x in start_x..end_x {
96 print_with_color_and_background_at(
97 y,
98 x,
99 &Some("bright_black".to_string()),
100 bg_color,
101 self.track_char,
102 buffer,
103 );
104 }
105
106 if track_width > 0 {
107 let (knob_position, knob_size) = self.calculate_knob_metrics(
108 content_width,
109 viewable_width,
110 horizontal_scroll,
111 track_width,
112 );
113
114 for i in 0..knob_size {
116 let knob_x = start_x + knob_position + i;
117 if knob_x < end_x {
118 print_with_color_and_background_at(
119 y,
120 knob_x,
121 border_color,
122 bg_color,
123 self.knob_char,
124 buffer,
125 );
126 }
127 }
128 }
129 }
130
131 pub fn is_click_on_scrollbar(
133 &self,
134 click_x: usize,
135 click_y: usize,
136 parent_bounds: &Bounds,
137 ) -> bool {
138 let (y, _, _) = self.get_bounds(parent_bounds);
139 click_y == y && click_x > parent_bounds.left() && click_x < parent_bounds.right()
140 }
141
142 pub fn is_click_on_knob(
144 &self,
145 click_x: usize,
146 click_y: usize,
147 parent_bounds: &Bounds,
148 content_width: usize,
149 viewable_width: usize,
150 horizontal_scroll: f64,
151 ) -> bool {
152 if !self.is_click_on_scrollbar(click_x, click_y, parent_bounds) {
153 return false;
154 }
155
156 let (_, start_x, end_x) = self.get_bounds(parent_bounds);
157 let track_width = end_x.saturating_sub(start_x);
158
159 if track_width == 0 {
160 return false;
161 }
162
163 let (knob_position, knob_size) = self.calculate_knob_metrics(
164 content_width,
165 viewable_width,
166 horizontal_scroll,
167 track_width,
168 );
169
170 let knob_start_x = start_x + knob_position;
171 let knob_end_x = knob_start_x + knob_size;
172
173 click_x >= knob_start_x && click_x < knob_end_x
174 }
175
176 pub fn click_position_to_scroll_percentage(
178 &self,
179 click_x: usize,
180 parent_bounds: &Bounds,
181 ) -> f64 {
182 let (_, start_x, end_x) = self.get_bounds(parent_bounds);
183 let track_width = end_x.saturating_sub(start_x);
184
185 if track_width == 0 {
186 return 0.0;
187 }
188
189 let click_position = (click_x.saturating_sub(start_x)) as f64 / track_width as f64;
190 (click_position * 100.0).clamp(0.0, 100.0)
191 }
192
193 pub fn drag_to_scroll_percentage(
195 &self,
196 start_x: u16,
197 current_x: u16,
198 start_scroll_percentage: f64,
199 parent_bounds: &Bounds,
200 ) -> f64 {
201 let (_, track_start_x, track_end_x) = self.get_bounds(parent_bounds);
202 let track_width = track_end_x.saturating_sub(track_start_x);
203
204 if track_width == 0 {
205 return start_scroll_percentage;
206 }
207
208 let drag_delta = (current_x as isize) - (start_x as isize);
209 let percentage_delta = (drag_delta as f64 / track_width as f64) * 100.0;
210
211 (start_scroll_percentage + percentage_delta).clamp(0.0, 100.0)
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_horizontal_scrollbar_creation() {
221 let scrollbar = HorizontalScrollbar::new("test_muxbox".to_string());
222 assert_eq!(scrollbar.parent_id, "test_muxbox");
223 assert_eq!(scrollbar.track_char, "─");
224 assert_eq!(scrollbar.knob_char, "■");
225 }
226
227 #[test]
228 fn test_should_draw_logic() {
229 let scrollbar = HorizontalScrollbar::new("test".to_string());
230
231 assert!(!scrollbar.should_draw(10, 20));
233 assert!(!scrollbar.should_draw(10, 10));
234
235 assert!(scrollbar.should_draw(20, 10));
237 }
238
239 #[test]
240 fn test_knob_metrics_calculation() {
241 let scrollbar = HorizontalScrollbar::new("test".to_string());
242
243 let (position, size) = scrollbar.calculate_knob_metrics(100, 50, 0.0, 20);
245 assert_eq!(size, 10); assert_eq!(position, 0); let (position, _) = scrollbar.calculate_knob_metrics(100, 50, 50.0, 20);
250 assert_eq!(position, 5); }
252
253 #[test]
254 fn test_click_position_conversion() {
255 let scrollbar = HorizontalScrollbar::new("test".to_string());
256 let bounds = Bounds::new(10, 10, 50, 30);
257
258 let scroll_pct = scrollbar.click_position_to_scroll_percentage(11, &bounds);
260 assert!((scroll_pct - 0.0).abs() < 0.01);
261
262 let scroll_pct = scrollbar.click_position_to_scroll_percentage(48, &bounds);
264 assert!((scroll_pct - 97.37).abs() < 1.0); let scroll_pct = scrollbar.click_position_to_scroll_percentage(30, &bounds);
268 assert!((scroll_pct - 50.0).abs() < 1.0); }
270}