mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-19 07:19:50 +02:00
192 lines
6.8 KiB
Rust
192 lines
6.8 KiB
Rust
use crate::config::LabelPrefix;
|
|
use crate::render::RenderConfig;
|
|
use crate::selected_frame::SelectableFrame;
|
|
use crate::widgets::widget::BarWidget;
|
|
use eframe::egui::Align;
|
|
use eframe::egui::Context;
|
|
use eframe::egui::Label;
|
|
use eframe::egui::TextFormat;
|
|
use eframe::egui::Ui;
|
|
use eframe::egui::text::LayoutJob;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use starship_battery::Manager;
|
|
use starship_battery::State;
|
|
use starship_battery::units::ratio::percent;
|
|
use std::process::Command;
|
|
use std::time::Duration;
|
|
use std::time::Instant;
|
|
|
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// Battery widget configuration
|
|
pub struct BatteryConfig {
|
|
/// Enable the Battery widget
|
|
pub enable: bool,
|
|
/// Hide the widget if the battery is at full charge
|
|
pub hide_on_full_charge: Option<bool>,
|
|
/// Data refresh interval in seconds
|
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10)))]
|
|
pub data_refresh_interval: Option<u64>,
|
|
/// Display label prefix
|
|
pub label_prefix: Option<LabelPrefix>,
|
|
/// Select when the current percentage is under this value [[1-100]]
|
|
pub auto_select_under: Option<u8>,
|
|
}
|
|
|
|
impl From<BatteryConfig> for Battery {
|
|
fn from(value: BatteryConfig) -> Self {
|
|
let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);
|
|
|
|
Self {
|
|
enable: value.enable,
|
|
hide_on_full_charge: value.hide_on_full_charge.unwrap_or(false),
|
|
manager: Manager::new().unwrap(),
|
|
last_state: None,
|
|
data_refresh_interval,
|
|
label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),
|
|
auto_select_under: value.auto_select_under.map(|u| u.clamp(1, 100)),
|
|
state: BatteryState::Discharging,
|
|
last_updated: Instant::now()
|
|
.checked_sub(Duration::from_secs(data_refresh_interval))
|
|
.unwrap(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub enum BatteryState {
|
|
Charging,
|
|
Discharging,
|
|
High,
|
|
Medium,
|
|
Low,
|
|
Warning,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct BatteryOutput {
|
|
label: String,
|
|
selected: bool,
|
|
}
|
|
|
|
pub struct Battery {
|
|
pub enable: bool,
|
|
hide_on_full_charge: bool,
|
|
manager: Manager,
|
|
pub state: BatteryState,
|
|
data_refresh_interval: u64,
|
|
label_prefix: LabelPrefix,
|
|
auto_select_under: Option<u8>,
|
|
last_state: Option<BatteryOutput>,
|
|
last_updated: Instant,
|
|
}
|
|
|
|
impl Battery {
|
|
fn output(&mut self) -> Option<BatteryOutput> {
|
|
let mut output = self.last_state.clone();
|
|
|
|
let now = Instant::now();
|
|
if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {
|
|
output = None;
|
|
|
|
if let Ok(mut batteries) = self.manager.batteries()
|
|
&& let Some(Ok(first)) = batteries.nth(0)
|
|
{
|
|
let percentage = first.state_of_charge().get::<percent>().round() as u8;
|
|
|
|
if percentage == 100 && self.hide_on_full_charge {
|
|
output = None
|
|
} else {
|
|
match first.state() {
|
|
State::Charging => self.state = BatteryState::Charging,
|
|
State::Discharging => {
|
|
self.state = match percentage {
|
|
p if p > 75 => BatteryState::Discharging,
|
|
p if p > 50 => BatteryState::High,
|
|
p if p > 25 => BatteryState::Medium,
|
|
p if p > 10 => BatteryState::Low,
|
|
_ => BatteryState::Warning,
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
let selected = self.auto_select_under.is_some_and(|u| percentage <= u);
|
|
|
|
output = Some(BatteryOutput {
|
|
label: match self.label_prefix {
|
|
LabelPrefix::Text | LabelPrefix::IconAndText => {
|
|
format!("BAT: {percentage}%")
|
|
}
|
|
LabelPrefix::None | LabelPrefix::Icon => {
|
|
format!("{percentage}%")
|
|
}
|
|
},
|
|
selected,
|
|
})
|
|
}
|
|
}
|
|
|
|
self.last_state.clone_from(&output);
|
|
self.last_updated = now;
|
|
}
|
|
|
|
output
|
|
}
|
|
}
|
|
|
|
impl BarWidget for Battery {
|
|
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
|
|
if self.enable {
|
|
let output = self.output();
|
|
if let Some(output) = output {
|
|
let emoji = match self.state {
|
|
BatteryState::Charging => egui_phosphor::regular::BATTERY_CHARGING,
|
|
BatteryState::Discharging => egui_phosphor::regular::BATTERY_FULL,
|
|
BatteryState::High => egui_phosphor::regular::BATTERY_HIGH,
|
|
BatteryState::Medium => egui_phosphor::regular::BATTERY_MEDIUM,
|
|
BatteryState::Low => egui_phosphor::regular::BATTERY_LOW,
|
|
BatteryState::Warning => egui_phosphor::regular::BATTERY_WARNING,
|
|
};
|
|
|
|
let auto_text_color = config.auto_select_text.filter(|_| output.selected);
|
|
|
|
let mut layout_job = LayoutJob::simple(
|
|
match self.label_prefix {
|
|
LabelPrefix::Icon | LabelPrefix::IconAndText => emoji.to_string(),
|
|
LabelPrefix::None | LabelPrefix::Text => String::new(),
|
|
},
|
|
config.icon_font_id.clone(),
|
|
auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),
|
|
100.0,
|
|
);
|
|
|
|
layout_job.append(
|
|
&output.label,
|
|
10.0,
|
|
TextFormat {
|
|
font_id: config.text_font_id.clone(),
|
|
color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),
|
|
valign: Align::Center,
|
|
..Default::default()
|
|
},
|
|
);
|
|
|
|
let auto_focus_fill = config.auto_select_fill;
|
|
|
|
config.apply_on_widget(false, ui, |ui| {
|
|
if SelectableFrame::new_auto(output.selected, auto_focus_fill)
|
|
.show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))
|
|
.clicked()
|
|
&& let Err(error) = Command::new("cmd.exe")
|
|
.args(["/C", "start", "ms-settings:batterysaver"])
|
|
.spawn()
|
|
{
|
|
eprintln!("{error}")
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|