Skip to main content

blizz_ui/components/
hint.rs

1use std::io::Write;
2
3use crossterm::{
4  cursor::MoveTo,
5  queue,
6  style::{Color, Print, ResetColor, SetForegroundColor},
7};
8
9use crate::layout::centered_column;
10
11const ANSI_GRAYSCALE_START: u8 = 232;
12const GRAYSCALE_RANGE: f64 = 15.0;
13
14pub struct HintComponent {
15  pub text: &'static str,
16  pub fade: f64,
17}
18
19impl HintComponent {
20  pub fn new(text: &'static str) -> Self {
21    Self { text, fade: 0.0 }
22  }
23
24  #[cfg(not(tarpaulin_include))]
25  pub fn render<W: Write>(&self, writer: &mut W, tw: u16, row: u16) -> std::io::Result<()> {
26    if self.fade <= 0.0 {
27      return Ok(());
28    }
29
30    let color = fade_color(self.fade);
31    let col = centered_column(tw, self.text.chars().count() as u16);
32    queue!(
33      writer,
34      MoveTo(col, row),
35      SetForegroundColor(color),
36      Print(self.text),
37      ResetColor
38    )
39  }
40}
41
42fn fade_color(fade: f64) -> Color {
43  if fade >= 1.0 {
44    return Color::DarkGrey;
45  }
46  Color::AnsiValue(ANSI_GRAYSCALE_START + (fade * GRAYSCALE_RANGE).round() as u8)
47}
48
49#[cfg(test)]
50mod tests {
51  use super::*;
52
53  #[test]
54  fn new_starts_invisible() {
55    let hint = HintComponent::new("(hit enter)");
56    assert_eq!(hint.fade, 0.0);
57    assert_eq!(hint.text, "(hit enter)");
58  }
59
60  #[test]
61  fn fade_color_returns_dark_grey_at_full() {
62    assert_eq!(fade_color(1.0), Color::DarkGrey);
63    assert_eq!(fade_color(1.5), Color::DarkGrey);
64  }
65
66  #[test]
67  fn fade_color_returns_ansi_gradient_below_one() {
68    let color = fade_color(0.5);
69    match color {
70      Color::AnsiValue(v) => {
71        assert!(v >= 232);
72        assert!(v <= 247);
73      }
74      _ => panic!("expected AnsiValue"),
75    }
76  }
77
78  #[test]
79  fn fade_color_at_zero_is_darkest() {
80    assert_eq!(fade_color(0.0), Color::AnsiValue(ANSI_GRAYSCALE_START));
81  }
82
83  #[test]
84  fn render_skips_when_invisible() {
85    let hint = HintComponent::new("test");
86    let mut buf = Vec::new();
87    hint.render(&mut buf, 80, 10).unwrap();
88    assert!(buf.is_empty());
89  }
90
91  #[test]
92  fn render_writes_when_visible() {
93    let mut hint = HintComponent::new("hello");
94    hint.fade = 1.0;
95    let mut buf = Vec::new();
96    hint.render(&mut buf, 80, 10).unwrap();
97    assert!(!buf.is_empty());
98    let output = String::from_utf8(buf).unwrap();
99    assert!(output.contains("hello"));
100  }
101}