From fe070220155980e21f72d24ac972ec8dc42a13fc Mon Sep 17 00:00:00 2001 From: Doloro1978 Date: Wed, 16 Sep 2026 12:07:30 +0100 Subject: [PATCH] popup --- crates/common/src/messages/modules/battery.rs | 8 ++ crates/common/src/messages/modules/mod.rs | 2 + crates/modules/src/battery.rs | 114 +++++++++++++++--- 3 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 crates/common/src/messages/modules/battery.rs diff --git a/crates/common/src/messages/modules/battery.rs b/crates/common/src/messages/modules/battery.rs new file mode 100644 index 0000000..edb8900 --- /dev/null +++ b/crates/common/src/messages/modules/battery.rs @@ -0,0 +1,8 @@ +//! Battery module messages. + +/// A module-local message for the battery tile (bar/popup interactions). +#[derive(Clone)] +pub enum BatteryAction { + /// Open the battery info popup. + OpenPopup, +} diff --git a/crates/common/src/messages/modules/mod.rs b/crates/common/src/messages/modules/mod.rs index 812f7df..3e0d8e5 100644 --- a/crates/common/src/messages/modules/mod.rs +++ b/crates/common/src/messages/modules/mod.rs @@ -1,7 +1,9 @@ //! Messages owned by bar modules. +mod battery; mod clock; mod weather; +pub use battery::*; pub use clock::*; pub use weather::*; diff --git a/crates/modules/src/battery.rs b/crates/modules/src/battery.rs index cc05e76..0959c60 100644 --- a/crates/modules/src/battery.rs +++ b/crates/modules/src/battery.rs @@ -1,9 +1,12 @@ //! Battery module: Android-style battery icon fed by the battery service. -use iced::widget::{container, row, stack, text}; +use iced::widget::{column, container, mouse_area, row, stack, text}; use iced::{alignment, Border, Color, Element, Length, Task, Theme}; -use common::{BarModule, BatteryEvent, BatteryMsg, BatteryStatus, ModuleEffect, Service, Wire}; +use common::{ + BarModule, BatteryAction, BatteryEvent, BatteryMsg, BatteryStatus, Endpoint, ModuleEffect, + PopupSettings, Service, Wire, +}; use serde::Deserialize; use toml::Table; @@ -16,9 +19,14 @@ const PAD: f32 = 2.0; pub struct Battery { percent: Option, status: BatteryStatus, + eta_secs: Option, numbers_in_icon: bool, } +/// Battery info popup size (logical px). +const POPUP_W: u32 = 280; +const POPUP_H: u32 = 160; + /// `module.battery` table; everything optional. #[derive(Deserialize, Default)] struct BatteryConfig { @@ -32,6 +40,7 @@ impl Battery { Self { percent: None, status: BatteryStatus::default(), + eta_secs: None, numbers_in_icon: config .and_then(|table| table.get("battery").cloned()) .and_then(|battery| battery.try_into().ok()) @@ -51,6 +60,18 @@ impl Battery { } } +/// Human form of a duration: `1 h 5 min`, `42 min`, or `30 s`. +fn fmt_duration(secs: u64) -> String { + let (hours, mins) = (secs / 3600, secs % 3600 / 60); + if hours > 0 { + format!("{hours} h {mins} min") + } else if mins > 0 { + format!("{mins} min") + } else { + format!("{secs} s") + } +} + /// Icon ink: the theme's success green while charging, bar text otherwise. fn ink(status: BatteryStatus, theme: &Theme) -> Color { let palette = theme.palette(); @@ -104,12 +125,10 @@ fn bold() -> iced::Font { } } -impl BarModule for Battery { - fn id(&self) -> &'static str { - "battery" - } - - fn view(&self, _window_id: Option) -> Element<'_, Wire> { +impl Battery { + /// Bar tile: icon (plus `%` label unless numbers live in the icon), + /// left-click opens the info popup. + fn bar(&self) -> Element<'_, Wire> { let status = self.status; let shell = container( container(text("")) @@ -152,7 +171,7 @@ impl BarModule for Battery { ] .spacing(2) .align_y(iced::Alignment::Center); - if self.numbers_in_icon { + let bar: Element<'_, Wire> = if self.numbers_in_icon { icon.into() } else { let label = match self.percent { @@ -163,19 +182,86 @@ impl BarModule for Battery { .spacing(6) .align_y(iced::Alignment::Center) .into() + }; + // Container carries the module id so the popup can anchor to it. + container( + mouse_area(bar).on_press(Wire::module( + Endpoint::module(self.id()), + self.id(), + BatteryAction::OpenPopup, + )), + ) + .id(iced::widget::Id::from(self.id())) + .into() + } + + /// Popup surface: charge, status, and time to full/empty. + fn popup(&self) -> Element<'_, Wire> { + let charge = match self.percent { + Some(percent) => text(format!("{percent}%")).size(44), + None => text("--").size(44), + }; + let status = text(match self.status { + BatteryStatus::Charging => "Charging".to_string(), + BatteryStatus::Discharging => "Discharging".to_string(), + BatteryStatus::Empty => "Empty".to_string(), + BatteryStatus::Full => "Fully charged".to_string(), + BatteryStatus::Unknown => "Unknown".to_string(), + }); + let mut lines = column![charge, status].spacing(4); + if let Some(secs) = self.eta_secs { + let eta = match self.status { + BatteryStatus::Charging => format!("Full in {}", fmt_duration(secs)), + BatteryStatus::Discharging => format!("{} remaining", fmt_duration(secs)), + _ => fmt_duration(secs), + }; + lines = lines.push(text(eta)); + } + container(lines) + .padding(16) + .width(Length::Fill) + .into() + } +} + +impl BarModule for Battery { + fn id(&self) -> &'static str { + "battery" + } + + fn view(&self, window_id: Option) -> Element<'_, Wire> { + match window_id { + Some(_) => self.popup(), + None => self.bar(), } } fn update(&mut self, msg: Wire) -> Task { - match msg.downcast::() { - Some(BatteryEvent::PercentageChanged(percent)) => self.percent = Some(*percent), - Some(BatteryEvent::StatusChanged(status)) => self.status = *status, - _ => {} + if let Some(event) = msg.downcast::() { + match event { + BatteryEvent::PercentageChanged(percent) => self.percent = Some(*percent), + BatteryEvent::StatusChanged(status) => self.status = *status, + BatteryEvent::EtaChanged(eta) => self.eta_secs = *eta, + } + return Task::none(); + } + match msg.downcast::() { + Some(BatteryAction::OpenPopup) => { + Task::done(ModuleEffect::RequestPopup(PopupSettings { + module_id: self.id().into(), + element_id: self.id().into(), + gap: 8, + })) + } + None => Task::none(), } - Task::none() } fn services(&self) -> Vec { vec![BatteryMsg::Events.key()] } + + fn popup_size(&self) -> Option<(u32, u32)> { + Some((POPUP_W, POPUP_H)) + } }