Skip to main content

blizz_ui/components/
hint.rs

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