use super::Component;
use crate::action::Action;
use battery::units::power::watt;
use battery::units::ratio::percent;
use battery::units::time::second;
use battery::{Battery, State};
use color_eyre::Result;
use ratatui::layout::Rect;
use ratatui::prelude::*;
use ratatui::widgets::Paragraph;
use std::str::FromStr;
use tracing::{error, warn};
#[derive(Debug, Default, Clone)]
pub struct BatteryComponent<'a> {
pub line: Line<'a>,
}
static BATTERY_STATE_SYMBOL_UNKNOWN: &str = "?";
static BATTERY_STATE_SYMBOL: &[(State, &str)] = &[
(State::Charging, "▲"),
(State::Discharging, "▼"),
(State::Full, "●"),
(State::Empty, "○"),
(State::Unknown, BATTERY_STATE_SYMBOL_UNKNOWN),
];
fn get_state_symbol(s: State) -> String {
if let Some((_, symbol)) = BATTERY_STATE_SYMBOL.iter().find(|(state, _)| s == *state) {
return symbol.to_string();
};
BATTERY_STATE_SYMBOL_UNKNOWN.to_string()
}
fn line<'a>(battery: Battery) -> Line<'a> {
let percentage = battery.state_of_charge().get::<percent>();
let bat = Span::raw(format!(
"BAT{} {}% ",
get_state_symbol(battery.state()),
percentage
));
let mut parts = vec![bat];
let mut bar = bar(percentage);
parts.append(&mut bar);
if let Some(time_to_empty) = battery.time_to_empty() {
let seconds_to_empty = time_to_empty.get::<second>() as i64;
let (hours, minutes) = seconds_to_hours_minutes(seconds_to_empty);
let time_to_empty = Span::raw(format!(" {hours:02}:{minutes:02}"));
parts.push(time_to_empty);
}
if let Some(time_to_full) = battery.time_to_full() {
let seconds_to_full = time_to_full.get::<second>() as i64;
let (hours, minutes) = seconds_to_hours_minutes(seconds_to_full);
let time_to_full = Span::raw(format!(" {hours:02}:{minutes:02}"));
parts.push(time_to_full);
}
let energy_rate = Span::raw(format!(" {:.2}W", battery.energy_rate().get::<watt>()));
parts.push(energy_rate);
Line::from(parts)
}
fn seconds_to_hours_minutes(seconds: i64) -> (i64, i64) {
let hours = seconds / 3600;
let remaining_seconds = seconds % 3600;
let minutes = remaining_seconds / 60;
(hours, minutes)
}
fn bar(percentage: f32) -> Vec<Span<'static>> {
let block_0 = Span::styled(
"■",
Style::default().fg(Color::from_str("#d86453").unwrap()),
);
let block_1 = Span::styled(
"■",
Style::default().fg(Color::from_str("#d57b59").unwrap()),
);
let block_2 = Span::styled(
"■",
Style::default().fg(Color::from_str("#d19260").unwrap()),
);
let block_3 = Span::styled(
"■",
Style::default().fg(Color::from_str("#cea966").unwrap()),
);
let block_4 = Span::styled(
"■",
Style::default().fg(Color::from_str("#cbc06c").unwrap()),
);
let block_5 = Span::styled(
"■",
Style::default().fg(Color::from_str("#bac276").unwrap()),
);
let block_6 = Span::styled(
"■",
Style::default().fg(Color::from_str("#a9c47f").unwrap()),
);
let block_7 = Span::styled(
"■",
Style::default().fg(Color::from_str("#98c689").unwrap()),
);
let block_8 = Span::styled(
"■",
Style::default().fg(Color::from_str("#87c892").unwrap()),
);
let block_9 = Span::styled(
"■",
Style::default().fg(Color::from_str("#77ca9b").unwrap()),
);
let blocks = vec![
block_0, block_1, block_2, block_3, block_4, block_5, block_6, block_7, block_8, block_9,
];
let style_empty = Span::styled(
"■",
Style::default().fg(Color::from_str("#404040").unwrap()),
);
let empty_bar = vec![style_empty; 10];
let until = (percentage / 10.0) as usize;
let filled = &blocks[..until].to_vec();
let emptied = &empty_bar[until..10].to_vec();
let mut bar = vec![];
bar.append(&mut filled.clone());
bar.append(&mut emptied.clone());
bar
}
impl Component for BatteryComponent<'_> {
fn update(&mut self, action: Action) -> Result<Option<Action>> {
match action {
Action::Tick => {
}
Action::Render => {
let manager = battery::Manager::new()?;
let battery = match manager.batteries()?.next() {
Some(Ok(battery)) => battery,
Some(Err(_)) => {
error!("Unable to access battery information");
self.line = Line::default();
return Ok(None);
}
None => {
warn!("Unable to find any batteries");
self.line = Line::default();
return Ok(None);
}
};
self.line = line(battery);
}
_ => {}
}
Ok(None)
}
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
frame.render_widget(Paragraph::new(self.line.clone()), area);
Ok(())
}
}