termint/widgets/
progress_bar.rs1use std::{cell::Cell, rc::Rc};
2
3use crate::{
4 buffer::Buffer,
5 geometry::{Rect, Vec2},
6 style::Style,
7 widgets::cache::Cache,
8};
9
10use super::{Element, Widget};
11
12pub struct ProgressBar {
36 state: Rc<Cell<f64>>,
37 thumb_chars: Vec<char>,
38 thumb_style: Style,
39 track_char: char,
40 style: Style,
41}
42
43impl ProgressBar {
44 #[must_use]
54 pub fn new(state: Rc<Cell<f64>>) -> Self {
55 Self {
56 state,
57 thumb_style: Default::default(),
58 thumb_chars: vec!['▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'],
59 track_char: ' ',
60 style: Default::default(),
61 }
62 }
63
64 #[must_use]
78 pub fn thumb_chars<C>(mut self, chars: C) -> Self
79 where
80 C: IntoIterator<Item = char>,
81 {
82 self.thumb_chars = chars.into_iter().collect();
83 self
84 }
85
86 #[must_use]
90 pub fn thumb_style<S>(mut self, style: S) -> Self
91 where
92 S: Into<Style>,
93 {
94 self.thumb_style = style.into();
95 self
96 }
97
98 #[must_use]
100 pub fn track_char(mut self, track: char) -> Self {
101 self.track_char = track;
102 self
103 }
104
105 #[must_use]
109 pub fn style<S>(mut self, style: S) -> Self
110 where
111 S: Into<Style>,
112 {
113 self.style = style.into();
114 self
115 }
116}
117
118impl Widget for ProgressBar {
119 fn render(&self, buffer: &mut Buffer, rect: Rect, _cache: &mut Cache) {
120 if rect.is_empty() || self.thumb_chars.is_empty() {
121 return;
122 }
123
124 let (full_cells, head_id) = self.calc_size(&rect);
125 let mut rest_len = rect.width().saturating_sub(full_cells);
126
127 let mut track_pos = Vec2::new(rect.x() + full_cells, rect.y());
128 if head_id > 0 {
129 rest_len = rest_len.saturating_sub(1);
130 buffer[track_pos]
131 .char(self.thumb_chars[head_id])
132 .style(self.thumb_style);
133 track_pos.x += 1;
134 }
135
136 let thumb = self.thumb_chars[self.thumb_chars.len() - 1];
137 buffer.set_str_styled(
138 thumb.to_string().repeat(full_cells),
139 rect.pos(),
140 self.thumb_style,
141 );
142
143 buffer.set_str_styled(
144 self.track_char.to_string().repeat(rest_len),
145 &track_pos,
146 self.style,
147 );
148 }
149
150 fn height(&self, _size: &Vec2) -> usize {
151 1
152 }
153
154 fn width(&self, size: &Vec2) -> usize {
155 size.x
156 }
157}
158
159impl ProgressBar {
160 fn calc_size(&self, rect: &Rect) -> (usize, usize) {
163 let progress = (self.state.get() / 100.0).clamp(0.0, 1.0);
164 let len = rect.width() as f64 * progress;
165 let full_cells = len.floor() as usize;
166
167 let frac = len - full_cells as f64;
168 let head_id = (frac * (self.thumb_chars.len() - 1) as f64).round();
169 (full_cells, head_id as usize)
170 }
171}
172
173impl From<ProgressBar> for Element {
174 fn from(value: ProgressBar) -> Self {
175 Element::new(value)
176 }
177}
178
179impl From<ProgressBar> for Box<dyn Widget> {
180 fn from(value: ProgressBar) -> Self {
181 Box::new(value)
182 }
183}