diff --git a/src/widgets/batterywidget.rs b/src/widgets/batterywidget.rs new file mode 100644 index 0000000..f039fd3 --- /dev/null +++ b/src/widgets/batterywidget.rs @@ -0,0 +1,107 @@ +use super::common::run_cmd; +use crate::TopBar; +use slint::{ComponentHandle, SharedString, Timer, TimerMode}; +use std::{fs, time::Duration}; + +// Read and trim a file +fn read_sys(path: &str) -> String { + fs::read_to_string(path) + .unwrap_or_default() + .trim() + .to_string() +} + +// Convert battery state strings into normalized tokens +fn normalize_status(s: &str) -> String { + s.to_lowercase() + .chars() + .filter(char::is_ascii_alphabetic) + .collect() +} + +// Map numeric capacity → level tags +fn capacity_level(cap: u8) -> String { + match cap { + 0..=10 => "critical".into(), + 11..=25 => "low".into(), + 26..=75 => "normal".into(), + 76..=95 => "high".into(), + _ => "full".into(), + } +} + +// Choose icon from status + level +fn select_icon(status: &str, level: &str) -> SharedString { + let icon = match status { + "charging" => match level { + "critical" => "󰢜", + "low" => "󰂇", + "normal" => "󰢝", + "high" => "󰂋", + "full" => "󰂄", + _ => "󰂑", + }, + "discharging" => match level { + "critical" => "󰂎", + "low" => "󰁻", + "normal" => "󰁿", + "high" => "󰂂", + "full" => "󰁹", + _ => "󰂑", + }, + "notcharging" => "󰂃", + "full" => "󰂄", + _ => "󰂑", // unknown fallback + }; + + SharedString::from(icon) +} + +// Main battery updater logic +fn start_battery_updater(ui: &TopBar) { + let weak = ui.as_weak(); + + // Timer fires every second (battery can change quickly) + let timer = Box::leak(Box::new(Timer::default())); + + timer.start(TimerMode::Repeated, Duration::from_secs(1), move || { + if let Some(ui) = weak.upgrade() { + // Read sysfs values + let cap_str = read_sys("/sys/class/power_supply/BAT0/capacity"); + let status_str = read_sys("/sys/class/power_supply/BAT0/status"); + let level_str = read_sys("/sys/class/power_supply/BAT0/capacity_level"); + + // Normalize values + let status = normalize_status(&status_str); + let cap: u8 = cap_str.parse().unwrap_or(0); + let level = normalize_status(&level_str); // low, normal, full, etc. + let level_calc = capacity_level(cap); // fallback level + + // Prefer kernel level if valid; otherwise use cap-based level + let final_level = if level.is_empty() { level_calc } else { level }; + + // Select icon + let icon = select_icon(&status, &final_level); + + // Update Slint properties + ui.set_battery_status(status.into()); + ui.set_battery_capacity(cap.into()); + ui.set_battery_capacity_level(final_level.into()); + ui.set_battery_icon(icon); + } + }); +} + +// Public entry: connect click callback + start updater +pub fn install(ui: &TopBar) { + let weak = ui.as_weak(); + + // Click → open battery monitor + ui.on_show_battery(move || { + if weak.upgrade().is_some() { + run_cmd("xclock"); // Fix later + } + }); + + start_battery_updater(ui); +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 983d02c..c8b2d4a 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,3 +1,4 @@ +mod batterywidget; mod common; mod datewidget; mod timewidget; @@ -7,8 +8,8 @@ use crate::TopBar; pub fn install_callbacks(ui: &TopBar) { timewidget::install(ui); datewidget::install(ui); + batterywidget::install(ui); // In the future: // networkwidget::install(ui); - // batterywidget::install(ui); } diff --git a/ui/battery-widget.slint b/ui/battery-widget.slint new file mode 100644 index 0000000..6772d4d --- /dev/null +++ b/ui/battery-widget.slint @@ -0,0 +1,35 @@ +export component BatteryWidget { + in property bar_height; + in property battery_status; // charging / discharging / notcharging / unknown + in property battery_capacity; // numeric percentage + in property battery_capacity_level; // critical / low / normal / high / full + in property battery_icon; + + callback show_battery(); + + height: bar_height * 1px; + width: bar_height * 1px; + + Rectangle { + background: touch_area.pressed ? #4a5f70 + : touch_area.has-hover ? #6d8a4d + : #8e9162; + border-radius: 3px; + + HorizontalLayout { + padding-left: 3px; + padding-right: 3px; + + Text { + text: battery_icon; + color: white; + vertical-alignment: center; + horizontal-alignment: center; + } + } + + touch_area := TouchArea { + clicked => { show_battery(); } + } + } +} diff --git a/ui/topbar.slint b/ui/topbar.slint index 4f06a86..8525647 100644 --- a/ui/topbar.slint +++ b/ui/topbar.slint @@ -1,5 +1,6 @@ import { TimeWidget } from "time-widget.slint"; import { DateWidget } from "date-widget.slint"; +import { BatteryWidget } from "battery-widget.slint"; export component TopBar inherits Window { @@ -17,6 +18,14 @@ export component TopBar inherits Window { in-out property date_text; callback show_calendar(); + // Battery widget + in property battery_status; + in property battery_capacity; + in property battery_capacity_level; + in property battery_icon; + callback show_battery(); + + title: "chocobar"; width: bar_width *1px; height: bar_height *1px; @@ -61,6 +70,15 @@ export component TopBar inherits Window { HorizontalLayout { alignment: end; // Right-align + BatteryWidget { + bar_height: root.bar_height; + battery_status: root.battery_status; + battery_capacity: root.battery_capacity; + battery_capacity_level: root.battery_capacity_level; + battery_icon: root.battery_icon; + show_battery => root.show_battery(); + } + DateWidget { // Get from root date_text: root.date_text;