diff --git a/crates/core/src/view.rs b/crates/core/src/view.rs index 0ca9af9..c6e38d2 100644 --- a/crates/core/src/view.rs +++ b/crates/core/src/view.rs @@ -35,6 +35,7 @@ fn bar_view(bar: &Bar) -> Element<'_, Message> { }) .collect::>>()) .spacing(bar.spacing) + .width(Length::Shrink) .align_y(Alignment::Center); let middle = row(bar @@ -50,6 +51,7 @@ fn bar_view(bar: &Bar) -> Element<'_, Message> { }) .collect::>>()) .spacing(bar.spacing) + .width(Length::Shrink) .align_y(Alignment::Center); let right = row(bar .order @@ -64,6 +66,7 @@ fn bar_view(bar: &Bar) -> Element<'_, Message> { }) .collect::>>()) .spacing(bar.spacing) + .width(Length::Shrink) .align_y(Alignment::Center); let row = row![ diff --git a/crates/modules/src/battery.rs b/crates/modules/src/battery.rs index 32b7de1..cc90616 100644 --- a/crates/modules/src/battery.rs +++ b/crates/modules/src/battery.rs @@ -1,17 +1,69 @@ -//! Battery module: prints the charge percentage from the battery service. +//! Battery module: Android-style battery icon fed by the battery service. -use iced::widget::text; -use iced::{Element, Task}; +use iced::widget::{container, row, text}; +use iced::{Border, Color, Element, Length, Task, Theme}; -use common::{BarModule, BatteryEvent, BatteryMsg, ModuleEffect, Service, Wire}; +use common::{BarModule, BatteryEvent, BatteryMsg, BatteryStatus, ModuleEffect, Service, Wire}; + +/// Icon shell size (logical px); fill scales inside it. +const SHELL_W: f32 = 28.0; +const SHELL_H: f32 = 14.0; +const BORDER_W: f32 = 1.5; +const PAD: f32 = 2.0; pub struct Battery { percent: Option, + status: BatteryStatus, } impl Battery { pub fn new() -> Self { - Self { percent: None } + Self { + percent: None, + status: BatteryStatus::default(), + } + } + + /// Fill width for the level, leaving a sliver visible below 100%. + fn fill_w(&self) -> f32 { + match self.percent { + Some(p) => ((SHELL_W - 2.0 * (BORDER_W + PAD)) * p as f32 / 100.0) + .clamp(0.0, SHELL_W) + .max(if p > 0 { 2.0 } else { 0.0 }), + None => 0.0, + } + } +} + +/// Icon ink: green while charging, white otherwise. +fn ink(status: BatteryStatus) -> Color { + match status { + BatteryStatus::Charging => Color::from_rgb8(0x76, 0xFF, 0x03), + _ => Color::WHITE, + } +} + +/// Transparent shell with a rounded outline tinted by charge state. +fn shell_style(status: BatteryStatus) -> impl Fn(&Theme) -> container::Style { + move |_theme: &Theme| container::Style { + border: Border { + color: ink(status), + width: BORDER_W, + radius: 4.0.into(), + }, + ..container::Style::default() + } +} + +/// Solid fill (and nub cap) tinted by charge state. +fn solid_style(status: BatteryStatus) -> impl Fn(&Theme) -> container::Style { + move |_theme: &Theme| container::Style { + background: Some(ink(status).into()), + border: Border { + radius: 2.0.into(), + ..Default::default() + }, + ..container::Style::default() } } @@ -21,16 +73,37 @@ impl BarModule for Battery { } fn view(&self, _window_id: Option) -> Element<'_, Wire> { - match self.percent { - Some(percent) => text(format!("{percent}%")).into(), - None => text("--").into(), - } + let status = self.status; + 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)), + // Nub cap on the right edge. + container(text("")) + .width(Length::Fixed(2.5)) + .height(Length::Fixed(6.0)) + .style(solid_style(status)), + ] + .spacing(2) + .align_y(iced::Alignment::Center); + row![icon] + .spacing(6) + .align_y(iced::Alignment::Center) + .into() } fn update(&mut self, msg: Wire) -> Task { - if let Some(BatteryEvent::PercentageChanged(percent)) = msg.downcast::() - { - self.percent = Some(*percent); + match msg.downcast::() { + Some(BatteryEvent::PercentageChanged(percent)) => self.percent = Some(*percent), + Some(BatteryEvent::StatusChanged(status)) => self.status = *status, + _ => {} } Task::none() }