use crate::components::ComponentDimensions;
use crate::draw_utils::print_with_color_and_background_at;
use crate::{Bounds, ScreenBuffer};
#[derive(Debug, Clone)]
pub struct HorizontalScrollbar {
pub parent_id: String,
track_char: &'static str,
knob_char: &'static str,
}
impl HorizontalScrollbar {
pub fn new(parent_id: String) -> Self {
Self {
parent_id,
track_char: "─", knob_char: "■", }
}
pub fn should_draw(&self, content_width: usize, viewable_width: usize) -> bool {
content_width > viewable_width
}
pub fn get_bounds(&self, parent_bounds: &Bounds) -> (usize, usize, usize) {
let component_dims = ComponentDimensions::new(*parent_bounds);
let track_bounds = component_dims.horizontal_scrollbar_track_bounds();
(track_bounds.y1, track_bounds.x1, track_bounds.x2)
}
pub fn calculate_knob_metrics(
&self,
content_width: usize,
viewable_width: usize,
horizontal_scroll: f64,
track_width: usize,
) -> (usize, usize) {
if track_width == 0 {
return (0, 0);
}
use crate::components::dimensions::{Orientation, ScrollDimensions};
use crate::Bounds;
let scroll_dims = ScrollDimensions::new(
(content_width, 1), (viewable_width, 1),
(horizontal_scroll, 0.0), Bounds::new(0, 0, track_width + 1, 2), );
let knob_size = scroll_dims.calculate_knob_size(Orientation::Horizontal);
let knob_position = scroll_dims.calculate_knob_position(Orientation::Horizontal);
(knob_position, knob_size)
}
pub fn draw(
&self,
parent_bounds: &Bounds,
content_width: usize,
viewable_width: usize,
horizontal_scroll: f64,
border_color: &Option<String>,
bg_color: &Option<String>,
buffer: &mut ScreenBuffer,
) {
if !self.should_draw(content_width, viewable_width) {
return;
}
let (y, start_x, end_x) = self.get_bounds(parent_bounds);
let track_width = end_x.saturating_sub(start_x);
for x in start_x..end_x {
print_with_color_and_background_at(
y,
x,
&Some("bright_black".to_string()),
bg_color,
self.track_char,
buffer,
);
}
if track_width > 0 {
let (knob_position, knob_size) = self.calculate_knob_metrics(
content_width,
viewable_width,
horizontal_scroll,
track_width,
);
for i in 0..knob_size {
let knob_x = start_x + knob_position + i;
if knob_x < end_x {
print_with_color_and_background_at(
y,
knob_x,
border_color,
bg_color,
self.knob_char,
buffer,
);
}
}
}
}
pub fn is_click_on_scrollbar(
&self,
click_x: usize,
click_y: usize,
parent_bounds: &Bounds,
) -> bool {
let (y, _, _) = self.get_bounds(parent_bounds);
click_y == y && click_x > parent_bounds.left() && click_x < parent_bounds.right()
}
pub fn is_click_on_knob(
&self,
click_x: usize,
click_y: usize,
parent_bounds: &Bounds,
content_width: usize,
viewable_width: usize,
horizontal_scroll: f64,
) -> bool {
if !self.is_click_on_scrollbar(click_x, click_y, parent_bounds) {
return false;
}
let (_, start_x, end_x) = self.get_bounds(parent_bounds);
let track_width = end_x.saturating_sub(start_x);
if track_width == 0 {
return false;
}
let (knob_position, knob_size) = self.calculate_knob_metrics(
content_width,
viewable_width,
horizontal_scroll,
track_width,
);
let knob_start_x = start_x + knob_position;
let knob_end_x = knob_start_x + knob_size;
click_x >= knob_start_x && click_x < knob_end_x
}
pub fn click_position_to_scroll_percentage(
&self,
click_x: usize,
parent_bounds: &Bounds,
) -> f64 {
let (_, start_x, end_x) = self.get_bounds(parent_bounds);
let track_width = end_x.saturating_sub(start_x);
if track_width == 0 {
return 0.0;
}
let click_position = (click_x.saturating_sub(start_x)) as f64 / track_width as f64;
(click_position * 100.0).clamp(0.0, 100.0)
}
pub fn drag_to_scroll_percentage(
&self,
start_x: u16,
current_x: u16,
start_scroll_percentage: f64,
parent_bounds: &Bounds,
) -> f64 {
let (_, track_start_x, track_end_x) = self.get_bounds(parent_bounds);
let track_width = track_end_x.saturating_sub(track_start_x);
if track_width == 0 {
return start_scroll_percentage;
}
let drag_delta = (current_x as isize) - (start_x as isize);
let percentage_delta = (drag_delta as f64 / track_width as f64) * 100.0;
(start_scroll_percentage + percentage_delta).clamp(0.0, 100.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_horizontal_scrollbar_creation() {
let scrollbar = HorizontalScrollbar::new("test_muxbox".to_string());
assert_eq!(scrollbar.parent_id, "test_muxbox");
assert_eq!(scrollbar.track_char, "─");
assert_eq!(scrollbar.knob_char, "■");
}
#[test]
fn test_should_draw_logic() {
let scrollbar = HorizontalScrollbar::new("test".to_string());
assert!(!scrollbar.should_draw(10, 20));
assert!(!scrollbar.should_draw(10, 10));
assert!(scrollbar.should_draw(20, 10));
}
#[test]
fn test_knob_metrics_calculation() {
let scrollbar = HorizontalScrollbar::new("test".to_string());
let (position, size) = scrollbar.calculate_knob_metrics(100, 50, 0.0, 20);
assert_eq!(size, 10); assert_eq!(position, 0);
let (position, _) = scrollbar.calculate_knob_metrics(100, 50, 50.0, 20);
assert_eq!(position, 5); }
#[test]
fn test_click_position_conversion() {
let scrollbar = HorizontalScrollbar::new("test".to_string());
let bounds = Bounds::new(10, 10, 50, 30);
let scroll_pct = scrollbar.click_position_to_scroll_percentage(11, &bounds);
assert!((scroll_pct - 0.0).abs() < 0.01);
let scroll_pct = scrollbar.click_position_to_scroll_percentage(48, &bounds);
assert!((scroll_pct - 97.37).abs() < 1.0);
let scroll_pct = scrollbar.click_position_to_scroll_percentage(30, &bounds);
assert!((scroll_pct - 50.0).abs() < 1.0); }
}