1use crate::color::Color;
8use crate::console::{Console, ConsoleOptions};
9use crate::protocol::Renderable;
10use crate::segment::Segment;
11use crate::style::Style;
12
13const FULL_BLOCK: &str = "\u{2588}"; const BEGIN_BLOCK_ELEMENTS: [&str; 8] = [
16 "\u{2588}", "\u{2588}", "\u{2588}", "\u{2590}", "\u{2590}", "\u{2590}", "\u{2595}", "\u{2595}",
17];
18const END_BLOCK_ELEMENTS: [&str; 8] = [
20 " ", "\u{258f}", "\u{258e}", "\u{258d}", "\u{258c}", "\u{258b}", "\u{258a}", "\u{2589}",
21];
22
23pub struct Bar {
25 size: f64,
26 begin: f64,
27 end: f64,
28 width: Option<usize>,
29 style: Style,
30}
31
32impl Bar {
33 pub fn new(size: f64, begin: f64, end: f64) -> Self {
35 Bar {
36 size,
37 begin,
38 end,
39 width: None,
40 style: Style::new()
42 .with_color(Color::default_color())
43 .with_bgcolor(Color::default_color()),
44 }
45 }
46
47 pub fn width(mut self, width: usize) -> Self {
49 self.width = Some(width);
50 self
51 }
52}
53
54impl Renderable for Bar {
55 fn rich_render(&self, _console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
56 let width = self
57 .width
58 .unwrap_or(options.max_width)
59 .min(options.max_width);
60 let style = Some(self.style.clone());
61
62 if self.begin >= self.end {
63 return vec![Segment::new(" ".repeat(width), style)];
64 }
65
66 let prefix_complete_eighths = (width as f64 * 8.0 * self.begin / self.size) as usize;
67 let prefix_bar_count = prefix_complete_eighths / 8;
68 let prefix_eighths = prefix_complete_eighths % 8;
69
70 let body_complete_eighths = (width as f64 * 8.0 * self.end / self.size) as usize;
71 let body_bar_count = body_complete_eighths / 8;
72 let body_eighths = body_complete_eighths % 8;
73
74 let mut prefix = " ".repeat(prefix_bar_count);
75 if prefix_eighths != 0 {
76 prefix.push_str(BEGIN_BLOCK_ELEMENTS[prefix_eighths]);
77 }
78
79 let mut body = FULL_BLOCK.repeat(body_bar_count);
80 if body_eighths != 0 {
81 body.push_str(END_BLOCK_ELEMENTS[body_eighths]);
82 }
83
84 let body_len = body.chars().count();
85 let suffix = " ".repeat(width.saturating_sub(body_len));
86
87 let prefix_len = prefix.chars().count();
89 let body_tail: String = body.chars().skip(prefix_len).collect();
90 let line = format!("{prefix}{body_tail}{suffix}");
91
92 vec![Segment::new(line, style)]
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::color::ColorSystem;
100
101 fn render(begin: f64, end: f64) -> String {
102 let console = Console::builder()
103 .force_terminal(true)
104 .color_system(Some(ColorSystem::Truecolor))
105 .width(20)
106 .build();
107 console.render_to_string(&Bar::new(100.0, begin, end).width(20))
108 }
109
110 #[test]
111 fn full_bar() {
112 assert_eq!(
113 render(0.0, 100.0),
114 format!("\x1b[39;49m{}\x1b[0m", FULL_BLOCK.repeat(20))
115 );
116 }
117
118 #[test]
119 fn half_bar() {
120 assert_eq!(
121 render(0.0, 50.0),
122 format!("\x1b[39;49m{} \x1b[0m", FULL_BLOCK.repeat(10))
123 );
124 }
125
126 #[test]
127 fn partial_edge_uses_eighth_block() {
128 assert_eq!(render(0.0, 33.0), "\x1b[39;49m██████▌ \x1b[0m");
130 }
131}