mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-26 07:04:51 +02:00
refactor: move over battery widget
This commit is contained in:
parent
eddc9a16c7
commit
fa00dec146
@ -1,9 +1,24 @@
|
|||||||
use std::collections::HashMap;
|
use std::{
|
||||||
|
cmp::{max, min},
|
||||||
|
collections::HashMap,
|
||||||
|
};
|
||||||
|
|
||||||
use tui::{layout::Rect, widgets::Borders};
|
use crossterm::event::{KeyCode, KeyEvent, MouseEvent};
|
||||||
|
use tui::{
|
||||||
|
backend::Backend,
|
||||||
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
text::{Span, Spans},
|
||||||
|
widgets::{Block, Borders, Paragraph, Tabs},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{data_farmer::DataCollection, Component, Widget},
|
app::{
|
||||||
|
data_farmer::DataCollection, does_bound_intersect_coordinate, event::WidgetEventResult,
|
||||||
|
widgets::tui_widgets::PipeGauge, Component, Widget,
|
||||||
|
},
|
||||||
|
canvas::Painter,
|
||||||
|
constants::TABLE_GAP_HEIGHT_LIMIT,
|
||||||
data_conversion::{convert_battery_harvest, ConvertedBatteryData},
|
data_conversion::{convert_battery_harvest, ConvertedBatteryData},
|
||||||
options::layout_options::LayoutRule,
|
options::layout_options::LayoutRule,
|
||||||
};
|
};
|
||||||
@ -33,28 +48,27 @@ impl BatteryState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement battery widget.
|
|
||||||
/// A table displaying battery information on a per-battery basis.
|
/// A table displaying battery information on a per-battery basis.
|
||||||
pub struct BatteryTable {
|
pub struct BatteryTable {
|
||||||
bounds: Rect,
|
bounds: Rect,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
batteries: Vec<String>,
|
|
||||||
battery_data: Vec<ConvertedBatteryData>,
|
battery_data: Vec<ConvertedBatteryData>,
|
||||||
width: LayoutRule,
|
width: LayoutRule,
|
||||||
height: LayoutRule,
|
height: LayoutRule,
|
||||||
block_border: Borders,
|
block_border: Borders,
|
||||||
|
tab_bounds: Vec<Rect>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BatteryTable {
|
impl Default for BatteryTable {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
batteries: vec![],
|
|
||||||
bounds: Default::default(),
|
bounds: Default::default(),
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
battery_data: Default::default(),
|
battery_data: Default::default(),
|
||||||
width: LayoutRule::default(),
|
width: LayoutRule::default(),
|
||||||
height: LayoutRule::default(),
|
height: LayoutRule::default(),
|
||||||
block_border: Borders::ALL,
|
block_border: Borders::ALL,
|
||||||
|
tab_bounds: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,9 +91,16 @@ impl BatteryTable {
|
|||||||
self.selected_index
|
self.selected_index
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the battery names.
|
fn increment_index(&mut self) {
|
||||||
pub fn batteries(&self) -> &[String] {
|
if self.selected_index + 1 < self.battery_data.len() {
|
||||||
&self.batteries
|
self.selected_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrement_index(&mut self) {
|
||||||
|
if self.selected_index > 0 {
|
||||||
|
self.selected_index -= 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the block border style.
|
/// Sets the block border style.
|
||||||
@ -100,6 +121,46 @@ impl Component for BatteryTable {
|
|||||||
fn set_bounds(&mut self, new_bounds: tui::layout::Rect) {
|
fn set_bounds(&mut self, new_bounds: tui::layout::Rect) {
|
||||||
self.bounds = new_bounds;
|
self.bounds = new_bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_key_event(&mut self, event: KeyEvent) -> WidgetEventResult {
|
||||||
|
if event.modifiers.is_empty() {
|
||||||
|
match event.code {
|
||||||
|
KeyCode::Left => {
|
||||||
|
let current_index = self.selected_index;
|
||||||
|
self.decrement_index();
|
||||||
|
if current_index == self.selected_index {
|
||||||
|
WidgetEventResult::NoRedraw
|
||||||
|
} else {
|
||||||
|
WidgetEventResult::Redraw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Right => {
|
||||||
|
let current_index = self.selected_index;
|
||||||
|
self.increment_index();
|
||||||
|
if current_index == self.selected_index {
|
||||||
|
WidgetEventResult::NoRedraw
|
||||||
|
} else {
|
||||||
|
WidgetEventResult::Redraw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => WidgetEventResult::NoRedraw,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
WidgetEventResult::NoRedraw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mouse_event(&mut self, event: MouseEvent) -> WidgetEventResult {
|
||||||
|
for (itx, bound) in self.tab_bounds.iter().enumerate() {
|
||||||
|
if does_bound_intersect_coordinate(event.column, event.row, *bound) {
|
||||||
|
if itx < self.battery_data.len() {
|
||||||
|
self.selected_index = itx;
|
||||||
|
return WidgetEventResult::Redraw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WidgetEventResult::NoRedraw
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for BatteryTable {
|
impl Widget for BatteryTable {
|
||||||
@ -109,6 +170,9 @@ impl Widget for BatteryTable {
|
|||||||
|
|
||||||
fn update_data(&mut self, data_collection: &DataCollection) {
|
fn update_data(&mut self, data_collection: &DataCollection) {
|
||||||
self.battery_data = convert_battery_harvest(data_collection);
|
self.battery_data = convert_battery_harvest(data_collection);
|
||||||
|
if self.battery_data.len() <= self.selected_index {
|
||||||
|
self.selected_index = self.battery_data.len().saturating_sub(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn width(&self) -> LayoutRule {
|
fn width(&self) -> LayoutRule {
|
||||||
@ -118,4 +182,163 @@ impl Widget for BatteryTable {
|
|||||||
fn height(&self) -> LayoutRule {
|
fn height(&self) -> LayoutRule {
|
||||||
self.height
|
self.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw<B: Backend>(
|
||||||
|
&mut self, painter: &Painter, f: &mut Frame<'_, B>, area: Rect, selected: bool,
|
||||||
|
) {
|
||||||
|
let block = Block::default()
|
||||||
|
.border_style(if selected {
|
||||||
|
painter.colours.highlighted_border_style
|
||||||
|
} else {
|
||||||
|
painter.colours.border_style
|
||||||
|
})
|
||||||
|
.borders(self.block_border.clone());
|
||||||
|
|
||||||
|
let inner_area = block.inner(area);
|
||||||
|
const CONSTRAINTS: [Constraint; 2] = [Constraint::Length(1), Constraint::Min(0)];
|
||||||
|
let split_area = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(CONSTRAINTS)
|
||||||
|
.split(inner_area);
|
||||||
|
let tab_area = Rect::new(
|
||||||
|
split_area[0].x.saturating_sub(1),
|
||||||
|
split_area[0].y,
|
||||||
|
split_area[0].width,
|
||||||
|
split_area[0].height,
|
||||||
|
);
|
||||||
|
let data_area = if inner_area.height >= TABLE_GAP_HEIGHT_LIMIT && split_area[1].height > 0 {
|
||||||
|
Rect::new(
|
||||||
|
split_area[1].x,
|
||||||
|
split_area[1].y + 1,
|
||||||
|
split_area[1].width,
|
||||||
|
split_area[1].height - 1,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
split_area[1]
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.battery_data.is_empty() {
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new("No batteries found").style(painter.colours.text_style),
|
||||||
|
tab_area,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let battery_tab_names = self
|
||||||
|
.battery_data
|
||||||
|
.iter()
|
||||||
|
.map(|d| Spans::from(d.battery_name.as_str()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let mut start_x_offset = tab_area.x + 1;
|
||||||
|
self.tab_bounds = battery_tab_names
|
||||||
|
.iter()
|
||||||
|
.map(|name| {
|
||||||
|
let length = name.width() as u16;
|
||||||
|
let start = start_x_offset;
|
||||||
|
start_x_offset += length;
|
||||||
|
start_x_offset += 3;
|
||||||
|
|
||||||
|
Rect::new(start, tab_area.y, length, 1)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
f.render_widget(
|
||||||
|
Tabs::new(battery_tab_names)
|
||||||
|
.divider(tui::symbols::line::VERTICAL)
|
||||||
|
.style(painter.colours.text_style)
|
||||||
|
.highlight_style(painter.colours.currently_selected_text_style)
|
||||||
|
.select(self.selected_index),
|
||||||
|
tab_area,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(battery_details) = self.battery_data.get(self.selected_index) {
|
||||||
|
let labels = vec![
|
||||||
|
Spans::from(Span::styled("Charge %", painter.colours.text_style)),
|
||||||
|
Spans::from(Span::styled("Consumption", painter.colours.text_style)),
|
||||||
|
match &battery_details.charge_times {
|
||||||
|
crate::data_conversion::BatteryDuration::Charging { .. } => {
|
||||||
|
Spans::from(Span::styled("Time to full", painter.colours.text_style))
|
||||||
|
}
|
||||||
|
crate::data_conversion::BatteryDuration::Discharging { .. } => {
|
||||||
|
Spans::from(Span::styled("Time to empty", painter.colours.text_style))
|
||||||
|
}
|
||||||
|
crate::data_conversion::BatteryDuration::Neither => Spans::from(
|
||||||
|
Span::styled("Time to full/empty", painter.colours.text_style),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Spans::from(Span::styled("Health %", painter.colours.text_style)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let data_constraints = if let Some(len) = labels.iter().map(|s| s.width()).max() {
|
||||||
|
[
|
||||||
|
Constraint::Length(min(
|
||||||
|
max(len as u16 + 2, data_area.width / 2),
|
||||||
|
data_area.width,
|
||||||
|
)),
|
||||||
|
Constraint::Min(0),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
[Constraint::Ratio(1, 2); 2]
|
||||||
|
};
|
||||||
|
const VALUE_CONSTRAINTS: [Constraint; 2] =
|
||||||
|
[Constraint::Length(1), Constraint::Min(0)];
|
||||||
|
let details_split_area = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(data_constraints)
|
||||||
|
.split(data_area);
|
||||||
|
let per_detail_area = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(VALUE_CONSTRAINTS)
|
||||||
|
.split(details_split_area[1]);
|
||||||
|
|
||||||
|
f.render_widget(Paragraph::new(labels), details_split_area[0]);
|
||||||
|
f.render_widget(
|
||||||
|
PipeGauge::default()
|
||||||
|
.end_label(format!(
|
||||||
|
"{:3.0}%",
|
||||||
|
battery_details.charge_percentage.round()
|
||||||
|
))
|
||||||
|
.ratio(battery_details.charge_percentage / 100.0)
|
||||||
|
.style(if battery_details.charge_percentage < 10.0 {
|
||||||
|
painter.colours.low_battery_colour
|
||||||
|
} else if battery_details.charge_percentage < 50.0 {
|
||||||
|
painter.colours.medium_battery_colour
|
||||||
|
} else {
|
||||||
|
painter.colours.high_battery_colour
|
||||||
|
}),
|
||||||
|
per_detail_area[0],
|
||||||
|
);
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new(vec![
|
||||||
|
Spans::from(Span::styled(
|
||||||
|
battery_details.watt_consumption.clone(),
|
||||||
|
painter.colours.text_style,
|
||||||
|
)),
|
||||||
|
match &battery_details.charge_times {
|
||||||
|
crate::data_conversion::BatteryDuration::Charging { short, long }
|
||||||
|
| crate::data_conversion::BatteryDuration::Discharging {
|
||||||
|
short,
|
||||||
|
long,
|
||||||
|
} => Spans::from(Span::styled(
|
||||||
|
if (per_detail_area[1].width as usize) >= long.len() {
|
||||||
|
long
|
||||||
|
} else {
|
||||||
|
short
|
||||||
|
},
|
||||||
|
painter.colours.text_style,
|
||||||
|
)),
|
||||||
|
crate::data_conversion::BatteryDuration::Neither => {
|
||||||
|
Spans::from(Span::styled("N/A", painter.colours.text_style))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Spans::from(Span::styled(
|
||||||
|
battery_details.health.clone(),
|
||||||
|
painter.colours.text_style,
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
per_detail_area[1],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Note the block must be rendered last, to cover up the tabs!
|
||||||
|
f.render_widget(block, area);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,6 @@ impl Widget for Carousel {
|
|||||||
|
|
||||||
fn selectable_type(&self) -> SelectableType {
|
fn selectable_type(&self) -> SelectableType {
|
||||||
if let Some(node) = self.get_currently_selected() {
|
if let Some(node) = self.get_currently_selected() {
|
||||||
debug!("node: {:?}", node);
|
|
||||||
SelectableType::Redirect(node)
|
SelectableType::Redirect(node)
|
||||||
} else {
|
} else {
|
||||||
SelectableType::Unselectable
|
SelectableType::Unselectable
|
||||||
|
@ -10,7 +10,7 @@ use tui::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
event::WidgetEventResult, sort_text_table::SimpleSortableColumn, time_graph::TimeGraphData,
|
event::WidgetEventResult, text_table::SimpleColumn, time_graph::TimeGraphData,
|
||||||
AppConfigFields, AppScrollWidgetState, CanvasTableWidthState, Component, DataCollection,
|
AppConfigFields, AppScrollWidgetState, CanvasTableWidthState, Component, DataCollection,
|
||||||
TextTable, TimeGraph, Widget,
|
TextTable, TimeGraph, Widget,
|
||||||
},
|
},
|
||||||
@ -79,7 +79,7 @@ pub enum CpuGraphLegendPosition {
|
|||||||
/// A widget designed to show CPU usage via a graph, along with a side legend in a table.
|
/// A widget designed to show CPU usage via a graph, along with a side legend in a table.
|
||||||
pub struct CpuGraph {
|
pub struct CpuGraph {
|
||||||
graph: TimeGraph,
|
graph: TimeGraph,
|
||||||
legend: TextTable<SimpleSortableColumn>,
|
legend: TextTable<SimpleColumn>,
|
||||||
legend_position: CpuGraphLegendPosition,
|
legend_position: CpuGraphLegendPosition,
|
||||||
showing_avg: bool,
|
showing_avg: bool,
|
||||||
|
|
||||||
@ -98,9 +98,10 @@ impl CpuGraph {
|
|||||||
pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
|
pub fn from_config(app_config_fields: &AppConfigFields) -> Self {
|
||||||
let graph = TimeGraph::from_config(app_config_fields);
|
let graph = TimeGraph::from_config(app_config_fields);
|
||||||
let legend = TextTable::new(vec![
|
let legend = TextTable::new(vec![
|
||||||
SimpleSortableColumn::new_flex("CPU".into(), None, false, 0.5),
|
SimpleColumn::new_flex("CPU".into(), 0.5),
|
||||||
SimpleSortableColumn::new_flex("Use%".into(), None, false, 0.5),
|
SimpleColumn::new_hard("Use%".into(), None),
|
||||||
]);
|
])
|
||||||
|
.default_ltr(false);
|
||||||
let legend_position = if app_config_fields.left_legend {
|
let legend_position = if app_config_fields.left_legend {
|
||||||
CpuGraphLegendPosition::Left
|
CpuGraphLegendPosition::Left
|
||||||
} else {
|
} else {
|
||||||
|
@ -144,7 +144,7 @@ impl Widget for TempTable {
|
|||||||
} else {
|
} else {
|
||||||
painter.colours.border_style
|
painter.colours.border_style
|
||||||
})
|
})
|
||||||
.borders(self.block_border.clone()); // TODO: Also do the scrolling indicator!
|
.borders(self.block_border.clone());
|
||||||
|
|
||||||
self.table
|
self.table
|
||||||
.draw_tui_table(painter, f, &self.display_data, block, area, selected);
|
.draw_tui_table(painter, f, &self.display_data, block, area, selected);
|
||||||
|
@ -86,10 +86,7 @@ impl<'a> Widget for PipeGauge<'a> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ratio = self.ratio;
|
let start_label = self.start_label.unwrap_or_else(move || Spans::from(""));
|
||||||
let start_label = self
|
|
||||||
.start_label
|
|
||||||
.unwrap_or_else(move || Spans::from(format!("{:.0}%", ratio * 100.0)));
|
|
||||||
|
|
||||||
let (col, row) = buf.set_spans(
|
let (col, row) = buf.set_spans(
|
||||||
gauge_area.left(),
|
gauge_area.left(),
|
||||||
@ -131,8 +128,8 @@ impl<'a> Widget for PipeGauge<'a> {
|
|||||||
sub_modifier: self.gauge_style.sub_modifier,
|
sub_modifier: self.gauge_style.sub_modifier,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for col in end..gauge_end {
|
// for col in end..gauge_end {
|
||||||
buf.get_mut(col, row).set_symbol(" ");
|
// buf.get_mut(col, row).set_symbol(" ");
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,15 +131,15 @@ pub fn draw_battery_display<B: Backend>(
|
|||||||
]),
|
]),
|
||||||
Row::new(vec!["Consumption", &battery_details.watt_consumption])
|
Row::new(vec!["Consumption", &battery_details.watt_consumption])
|
||||||
.style(painter.colours.text_style),
|
.style(painter.colours.text_style),
|
||||||
if let Some(duration_until_full) = &battery_details.duration_until_full {
|
// if let Some(duration_until_full) = &battery_details.duration_until_full {
|
||||||
Row::new(vec!["Time to full", duration_until_full])
|
// Row::new(vec!["Time to full", duration_until_full])
|
||||||
.style(painter.colours.text_style)
|
// .style(painter.colours.text_style)
|
||||||
} else if let Some(duration_until_empty) = &battery_details.duration_until_empty {
|
// } else if let Some(duration_until_empty) = &battery_details.duration_until_empty {
|
||||||
Row::new(vec!["Time to empty", duration_until_empty])
|
// Row::new(vec!["Time to empty", duration_until_empty])
|
||||||
.style(painter.colours.text_style)
|
// .style(painter.colours.text_style)
|
||||||
} else {
|
// } else {
|
||||||
Row::new(vec!["Time to full/empty", "N/A"]).style(painter.colours.text_style)
|
// Row::new(vec!["Time to full/empty", "N/A"]).style(painter.colours.text_style)
|
||||||
},
|
// },
|
||||||
Row::new(vec!["Health %", &battery_details.health])
|
Row::new(vec!["Health %", &battery_details.health])
|
||||||
.style(painter.colours.text_style),
|
.style(painter.colours.text_style),
|
||||||
];
|
];
|
||||||
|
@ -17,13 +17,25 @@ use std::collections::{HashMap, VecDeque};
|
|||||||
/// Point is of time, data
|
/// Point is of time, data
|
||||||
type Point = (f64, f64);
|
type Point = (f64, f64);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum BatteryDuration {
|
||||||
|
Charging { short: String, long: String },
|
||||||
|
Discharging { short: String, long: String },
|
||||||
|
Neither,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BatteryDuration {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Neither
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct ConvertedBatteryData {
|
pub struct ConvertedBatteryData {
|
||||||
pub battery_name: String,
|
pub battery_name: String,
|
||||||
pub charge_percentage: f64,
|
pub charge_percentage: f64,
|
||||||
pub watt_consumption: String,
|
pub watt_consumption: String,
|
||||||
pub duration_until_full: Option<String>,
|
pub charge_times: BatteryDuration,
|
||||||
pub duration_until_empty: Option<String>,
|
|
||||||
pub health: String,
|
pub health: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1362,37 +1374,40 @@ pub fn convert_battery_harvest(current_data: &DataCollection) -> Vec<ConvertedBa
|
|||||||
battery_name: format!("Battery {}", itx),
|
battery_name: format!("Battery {}", itx),
|
||||||
charge_percentage: battery_harvest.charge_percent,
|
charge_percentage: battery_harvest.charge_percent,
|
||||||
watt_consumption: format!("{:.2}W", battery_harvest.power_consumption_rate_watts),
|
watt_consumption: format!("{:.2}W", battery_harvest.power_consumption_rate_watts),
|
||||||
duration_until_empty: if let Some(secs_till_empty) = battery_harvest.secs_until_empty {
|
charge_times: if let Some(secs_till_empty) = battery_harvest.secs_until_empty {
|
||||||
let time = chrono::Duration::seconds(secs_till_empty);
|
let time = chrono::Duration::seconds(secs_till_empty);
|
||||||
let num_minutes = time.num_minutes() - time.num_hours() * 60;
|
let num_minutes = time.num_minutes() - time.num_hours() * 60;
|
||||||
let num_seconds = time.num_seconds() - time.num_minutes() * 60;
|
let num_seconds = time.num_seconds() - time.num_minutes() * 60;
|
||||||
Some(format!(
|
BatteryDuration::Discharging {
|
||||||
"{} hour{}, {} minute{}, {} second{}",
|
long: format!(
|
||||||
time.num_hours(),
|
"{} hour{}, {} minute{}, {} second{}",
|
||||||
if time.num_hours() == 1 { "" } else { "s" },
|
time.num_hours(),
|
||||||
num_minutes,
|
if time.num_hours() == 1 { "" } else { "s" },
|
||||||
if num_minutes == 1 { "" } else { "s" },
|
num_minutes,
|
||||||
num_seconds,
|
if num_minutes == 1 { "" } else { "s" },
|
||||||
if num_seconds == 1 { "" } else { "s" },
|
num_seconds,
|
||||||
))
|
if num_seconds == 1 { "" } else { "s" },
|
||||||
} else {
|
),
|
||||||
None
|
short: format!("{}:{:02}:{:02}", time.num_hours(), num_minutes, num_seconds),
|
||||||
},
|
}
|
||||||
duration_until_full: if let Some(secs_till_full) = battery_harvest.secs_until_full {
|
} else if let Some(secs_till_full) = battery_harvest.secs_until_full {
|
||||||
let time = chrono::Duration::seconds(secs_till_full); // FIXME [DEP]: Can I get rid of chrono?
|
let time = chrono::Duration::seconds(secs_till_full); // FIXME [DEP]: Can I get rid of chrono?
|
||||||
let num_minutes = time.num_minutes() - time.num_hours() * 60;
|
let num_minutes = time.num_minutes() - time.num_hours() * 60;
|
||||||
let num_seconds = time.num_seconds() - time.num_minutes() * 60;
|
let num_seconds = time.num_seconds() - time.num_minutes() * 60;
|
||||||
Some(format!(
|
BatteryDuration::Charging {
|
||||||
"{} hour{}, {} minute{}, {} second{}",
|
long: format!(
|
||||||
time.num_hours(),
|
"{} hour{}, {} minute{}, {} second{}",
|
||||||
if time.num_hours() == 1 { "" } else { "s" },
|
time.num_hours(),
|
||||||
num_minutes,
|
if time.num_hours() == 1 { "" } else { "s" },
|
||||||
if num_minutes == 1 { "" } else { "s" },
|
num_minutes,
|
||||||
num_seconds,
|
if num_minutes == 1 { "" } else { "s" },
|
||||||
if num_seconds == 1 { "" } else { "s" },
|
num_seconds,
|
||||||
))
|
if num_seconds == 1 { "" } else { "s" },
|
||||||
|
),
|
||||||
|
short: format!("{}:{:02}:{:02}", time.num_hours(), num_minutes, num_seconds),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
BatteryDuration::Neither
|
||||||
},
|
},
|
||||||
health: format!("{:.2}%", battery_harvest.health_percent),
|
health: format!("{:.2}%", battery_harvest.health_percent),
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user