blizz-ui 3.0.0-dev.12

Self-rendering terminal UI components for the blizz wizard
Documentation
//! Shared box-drawing primitives for bordered UI components (text entry, selector).

pub const CORNER_TOP_LEFT: &str = "\u{256d}";
pub const CORNER_TOP_RIGHT: &str = "\u{256e}";
pub const CORNER_BOTTOM_LEFT: &str = "\u{2570}";
pub const CORNER_BOTTOM_RIGHT: &str = "\u{256f}";
pub const VERTICAL: &str = "\u{2502}";
pub const HORIZONTAL: &str = "\u{2500}";

/// Total columns consumed by border chrome: "│ " + " │" = 4.
pub const CHROME_WIDTH: u16 = 4;
/// Columns consumed by the left border before content starts ("│ " = 2).
pub const CONTENT_OFFSET: u16 = 2;
/// Space padding inside the vertical bars (one space each side).
pub const INNER_PADDING: usize = 2;
/// Number of border rows in a box (top border + bottom border).
pub const BORDER_ROWS: u16 = 2;

/// Snap a width to the nearest even number, using `min_width` when rounding
/// would collapse a nonzero value to zero. Used during partial-open animation
/// so the box stays symmetric as it grows.
pub fn snap_even(raw: u16, min_width: u16) -> u16 {
  let even = (raw / 2) * 2;
  if even == 0 && raw > 0 {
    min_width
  } else {
    even
  }
}

/// Plain top border: `╭──────╮`
pub fn top_border(inner_width: u16) -> String {
  let fill: String = HORIZONTAL.repeat(inner_width as usize + INNER_PADDING);
  format!("{CORNER_TOP_LEFT}{fill}{CORNER_TOP_RIGHT}")
}

/// Plain bottom border: `╰──────╯`
pub fn bottom_border(inner_width: u16) -> String {
  let fill: String = HORIZONTAL.repeat(inner_width as usize + INNER_PADDING);
  format!("{CORNER_BOTTOM_LEFT}{fill}{CORNER_BOTTOM_RIGHT}")
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn top_border_has_correct_corners() {
    let border = top_border(10);
    assert!(border.starts_with(CORNER_TOP_LEFT));
    assert!(border.ends_with(CORNER_TOP_RIGHT));
  }

  #[test]
  fn bottom_border_has_correct_corners() {
    let border = bottom_border(10);
    assert!(border.starts_with(CORNER_BOTTOM_LEFT));
    assert!(border.ends_with(CORNER_BOTTOM_RIGHT));
  }

  #[test]
  fn border_fill_width_includes_inner_padding() {
    let border = top_border(5);
    let fill_chars = border.chars().count() - 2; // minus two corner chars
    assert_eq!(fill_chars, 5 + INNER_PADDING);
  }

  #[test]
  fn snap_even_rounds_down_to_even() {
    assert_eq!(snap_even(5, 2), 4);
    assert_eq!(snap_even(7, 2), 6);
    assert_eq!(snap_even(4, 2), 4);
  }

  #[test]
  fn snap_even_uses_min_when_rounding_collapses_to_zero() {
    assert_eq!(snap_even(1, 2), 2);
    assert_eq!(snap_even(0, 2), 0);
  }
}