blizz_ui/components/
hint.rs1use 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}