1use std::borrow::Cow;
2
3use tui::{
4 buffer::Buffer,
5 layout::{Direction, Rect},
6 style::{Color, Style},
7 widgets::{Block, Widget},
8};
9
10#[derive(Debug, Clone)]
12pub struct ValueBar<'a> {
13 value: f32,
14 label: Cow<'a, str>,
15 direction: Direction,
16 style: Style,
17 block: Option<Block<'a>>,
18 range: f32,
19}
20
21impl<'a> Default for ValueBar<'a> {
22 fn default() -> Self {
23 Self {
24 value: 0.,
25 range: 1.,
26 direction: Direction::Horizontal,
27 label: "".into(),
28 style: Style::default(),
29 block: None,
30 }
31 }
32}
33
34impl<'a> ValueBar<'a> {
35 pub fn value(mut self, value: f32) -> Self {
37 self.value = value;
38 self
39 }
40
41 pub fn range(mut self, range: f32) -> Self {
43 self.range = range;
44 self
45 }
46
47 pub fn label<T>(mut self, label: T) -> Self
50 where
51 T: Into<Cow<'a, str>>,
52 {
53 self.label = label.into();
54 self
55 }
56
57 pub fn direction(mut self, direction: Direction) -> Self {
59 self.direction = direction;
60 self
61 }
62
63 pub fn block(mut self, block: Block<'a>) -> Self {
65 self.block = Some(block);
66 self
67 }
68
69 pub fn style(mut self, style: Style) -> Self {
71 self.style = style;
72 self
73 }
74
75 fn symbol(&self, p: i32) -> &str {
76 use Direction::*;
77 let negative = self.value < 0.;
78 match (p, negative, &self.direction) {
79 (..=-8, true, Horizontal) => "█",
80 (-7, true, Horizontal) => "🮋",
81 (-6, true, Horizontal) => "🮊",
82 (-5, true, Horizontal) => "🮉",
83 (-4, true, Horizontal) => "▐",
84 (-3, true, Horizontal) => "🮈",
85 (-2, true, Horizontal) => "🮇",
86 (-1, true, Horizontal) => "▕",
87 (0 | 1, false, Horizontal) => "▏",
88 (2, false, Horizontal) => "▎",
89 (3, false, Horizontal) => "▍",
90 (4, false, Horizontal) => "▌",
91 (5, false, Horizontal) => "▋",
92 (6, false, Horizontal) => "▊",
93 (7, false, Horizontal) => "▉",
94 (8.., false, Horizontal) => "█",
95 (..=-8, true, Vertical) => "█",
96 (-7, true, Vertical) => "🮆",
97 (-6, true, Vertical) => "🮅",
98 (-5, true, Vertical) => "🮄",
99 (-4, true, Vertical) => "▀",
100 (-3, true, Vertical) => "🮃",
101 (-2, true, Vertical) => "🮂",
102 (-1, true, Vertical) => "▔",
103 (0 | 1, false, Vertical) => "▁",
104 (2, false, Vertical) => "▂",
105 (3, false, Vertical) => "▃",
106 (4, false, Vertical) => "▄",
107 (5, false, Vertical) => "▅",
108 (6, false, Vertical) => "▆",
109 (7, false, Vertical) => "▇",
110 (8.., false, Vertical) => "█",
111 _ => " ",
112 }
113 }
114}
115
116impl<'a> Widget for ValueBar<'a> {
117 fn render(mut self, area: Rect, buffer: &mut Buffer) {
118 let area = match self.block.take() {
119 Some(block) => {
120 let inner = block.inner(area);
121 block.render(area, buffer);
122 inner
123 }
124 None => area,
125 };
126 let (length, width, start) = match self.direction {
127 Direction::Horizontal => (area.width, area.height, area.left()),
128 Direction::Vertical => (area.height, area.width, area.top()),
129 };
130 if width < 1 {
131 return;
133 }
134
135 let units_per_px = 2. * self.range / length as f32;
136 let center_row = area.top() + area.height.saturating_sub(1) / 2;
137 let center_col = start + length / 2;
138 let label_start =
139 (area.left() + area.width / 2).saturating_sub(self.label.len() as u16 / 2);
140 for y in area.top()..area.bottom() {
141 for x in area.left()..area.right() {
142 let px = units_per_px
143 * match self.direction {
144 Direction::Horizontal => x as f32 - center_col as f32,
145 Direction::Vertical => center_row as f32 - y as f32,
146 };
147 let symbol = if px < 0. && self.value < 0. {
149 self.symbol(((self.value - px) / units_per_px * 8. - 8.).round() as i32)
150 } else if px >= 0. && self.value >= 0. {
151 self.symbol(((self.value - px) / units_per_px * 8.).round() as i32)
152 } else {
153 " "
154 };
155
156 let cell = buffer.get_mut(x, y);
157 cell.set_style(self.style);
158 cell.set_symbol(symbol);
159
160 if y != center_row {
161 continue;
162 }
163 if area.width < self.label.len() as u16 {
164 continue;
166 }
167 let idx = x
168 .checked_sub(label_start)
169 .and_then(|x| self.label.chars().nth(x as usize));
170 if let Some(c) = idx {
171 cell.set_char(c);
172 cell.set_style(if symbol == "█" {
173 Style::default()
174 .fg(Color::Reset)
175 .bg(self.style.fg.unwrap_or(Color::Reset))
176 } else {
177 self.style
178 });
179 }
180 }
181 }
182 }
183}