woocraft 0.4.5

GPUI components lib for Woocraft design system.
Documentation
use gpui::{Half, Pixels, px};

pub(crate) const VERTICAL_SCROLLBAR_WIDTH: Pixels = px(32.0);
pub(crate) const DEFAULT_VIEWPORT_ROWS: usize = 24;
const MIN_THUMB_HEIGHT: Pixels = px(8.0);

pub(crate) fn viewport_rows(viewport_height: Pixels, line_height: Pixels) -> usize {
  if viewport_height <= px(0.0) || line_height <= px(0.0) {
    return DEFAULT_VIEWPORT_ROWS;
  }

  ((viewport_height / line_height) as usize).max(1)
}

pub(crate) fn max_top_row(total_rows: usize, viewport_rows: usize) -> usize {
  total_rows.saturating_sub(viewport_rows.min(total_rows))
}

pub(crate) fn clamp_top_row(top_row: usize, total_rows: usize, viewport_rows: usize) -> usize {
  top_row.min(max_top_row(total_rows, viewport_rows))
}

pub(crate) fn compute_scrollbar_thumb(
  top_row: usize, viewport_rows: usize, total_rows: usize, track_height: Pixels,
) -> (Pixels, Pixels) {
  if track_height <= px(0.0) {
    return (px(0.0), track_height);
  }

  if total_rows == 0 {
    return (px(0.0), track_height);
  }

  let total_rows = total_rows.max(1) as f32;
  let viewport_rows = viewport_rows.max(1) as f32;
  let clamped_top_row = top_row.min(total_rows as usize) as f32;
  let track_height_f32 = f32::from(track_height);

  let thumb_height = (track_height_f32 * (viewport_rows / total_rows))
    .max(f32::from(MIN_THUMB_HEIGHT))
    .min(track_height_f32);
  let max_top = (total_rows - viewport_rows).max(0.0);
  let y = if max_top <= 0.0 {
    0.0
  } else {
    let frac = (clamped_top_row / max_top).clamp(0.0, 1.0);
    frac * (track_height_f32 - thumb_height)
  };

  (px(y), px(thumb_height))
}

pub(crate) fn scrollbar_y_to_top_row(
  local_y: Pixels, total_rows: usize, viewport_rows: usize, track_height: Pixels,
) -> usize {
  if total_rows == 0 || track_height <= px(0.0) {
    return 0;
  }

  let (_thumb_y, thumb_height) =
    compute_scrollbar_thumb(0, viewport_rows, total_rows, track_height);
  let travel = (track_height - thumb_height).max(px(0.0));
  if travel <= px(0.0) {
    return 0;
  }

  let thumb_center = (local_y - thumb_height.half()).clamp(px(0.0), travel);
  let frac = (f32::from(thumb_center) / f32::from(travel)).clamp(0.0, 1.0);
  let max_top = max_top_row(total_rows, viewport_rows);
  (frac * max_top as f32).round() as usize
}

#[cfg(test)]
mod tests {
  use gpui::px;

  use super::*;

  #[test]
  fn viewport_row_count_has_floor_and_default() {
    assert_eq!(viewport_rows(px(0.0), px(20.0)), DEFAULT_VIEWPORT_ROWS);
    assert_eq!(viewport_rows(px(100.0), px(20.0)), 5);
    assert_eq!(viewport_rows(px(5.0), px(20.0)), 1);
  }

  #[test]
  fn top_row_is_clamped_to_valid_range() {
    assert_eq!(max_top_row(100, 10), 90);
    assert_eq!(clamp_top_row(95, 100, 10), 90);
    assert_eq!(clamp_top_row(3, 2, 10), 0);
  }

  #[test]
  fn scrollbar_mapping_round_trips_reasonably() {
    let track_height = px(200.0);
    let (thumb_y, thumb_height) = compute_scrollbar_thumb(40, 10, 100, track_height);
    assert!(thumb_height >= MIN_THUMB_HEIGHT);
    let mapped = scrollbar_y_to_top_row(thumb_y + thumb_height.half(), 100, 10, track_height);
    assert!(mapped.abs_diff(40) <= 1);
  }
}