diff --git a/crates/modules/src/battery.rs b/crates/modules/src/battery.rs index b835676..342b2f5 100644 --- a/crates/modules/src/battery.rs +++ b/crates/modules/src/battery.rs @@ -1,9 +1,11 @@ //! Battery module: Android-style battery icon fed by the battery service. -use iced::widget::{container, row, text}; -use iced::{Border, Color, Element, Length, Task, Theme}; +use iced::widget::{container, row, stack, text}; +use iced::{alignment, Border, Color, Element, Length, Task, Theme}; use common::{BarModule, BatteryEvent, BatteryMsg, BatteryStatus, ModuleEffect, Service, Wire}; +use serde::Deserialize; +use toml::Table; /// Icon shell size (logical px); fill scales inside it. const SHELL_W: f32 = 28.0; @@ -14,13 +16,27 @@ const PAD: f32 = 2.0; pub struct Battery { percent: Option, status: BatteryStatus, + numbers_in_icon: bool, +} + +/// `module.battery` table; everything optional. +#[derive(Deserialize, Default)] +struct BatteryConfig { + /// Render the percentage as a number centered inside the icon shell. + #[serde(default)] + numbers_in_icon: bool, } impl Battery { - pub fn new() -> Self { + pub fn new(config: Option) -> Self { Self { percent: None, status: BatteryStatus::default(), + numbers_in_icon: config + .and_then(|table| table.get("battery").cloned()) + .and_then(|battery| battery.try_into().ok()) + .map(|cfg: BatteryConfig| cfg.numbers_in_icon) + .unwrap_or_default(), } } @@ -75,17 +91,36 @@ impl BarModule for Battery { fn view(&self, _window_id: Option) -> Element<'_, Wire> { let status = self.status; + let shell = container( + container(text("")) + .width(Length::Fixed(self.fill_w())) + .height(Length::Fill) + .style(solid_style(status)), + ) + .width(Length::Fixed(SHELL_W)) + .height(Length::Fixed(SHELL_H)) + .padding(PAD) + .style(shell_style(status)); + // Optional percentage readout centered over the fill, in the bar's + // background color — legible on the light fill, dim where unfilled. + let shell: Element<'_, Wire> = match (self.numbers_in_icon, self.percent) { + (true, Some(percent)) => stack![ + shell, + container( + text(format!("{percent}")).size(9).style(|theme: &Theme| text::Style { + color: Some(theme.palette().background), + }), + ) + .width(Length::Fill) + .height(Length::Fill) + .align_x(alignment::Horizontal::Center) + .align_y(alignment::Vertical::Center), + ] + .into(), + _ => shell.into(), + }; let icon = row![ - container( - container(text("")) - .width(Length::Fixed(self.fill_w())) - .height(Length::Fill) - .style(solid_style(status)), - ) - .width(Length::Fixed(SHELL_W)) - .height(Length::Fixed(SHELL_H)) - .padding(PAD) - .style(shell_style(status)), + shell, // Nub cap on the right edge. container(text("")) .width(Length::Fixed(2.5)) @@ -94,10 +129,18 @@ impl BarModule for Battery { ] .spacing(2) .align_y(iced::Alignment::Center); - row![icon] - .spacing(6) - .align_y(iced::Alignment::Center) - .into() + if self.numbers_in_icon { + icon.into() + } else { + let label = match self.percent { + Some(percent) => text(format!("{percent}%")), + None => text("--"), + }; + row![icon, label] + .spacing(6) + .align_y(iced::Alignment::Center) + .into() + } } fn update(&mut self, msg: Wire) -> Task { diff --git a/crates/modules/src/lib.rs b/crates/modules/src/lib.rs index 4e20107..6cc7820 100644 --- a/crates/modules/src/lib.rs +++ b/crates/modules/src/lib.rs @@ -16,7 +16,7 @@ pub use weather::*; pub fn all(module_config: Table) -> Vec> { vec![ Box::new(audio::Audio::new()), - Box::new(battery::Battery::new()), + Box::new(battery::Battery::new(Some(module_config.clone()))), Box::new(clock::Clock::new(Some(module_config.clone()))), Box::new(weather::WeatherModule::default()), Box::new(workspaces::Workspaces::new(Some(module_config))),