popup
This commit is contained in:
@@ -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,
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
//! Messages owned by bar modules.
|
||||
|
||||
mod battery;
|
||||
mod clock;
|
||||
mod weather;
|
||||
|
||||
pub use battery::*;
|
||||
pub use clock::*;
|
||||
pub use weather::*;
|
||||
|
||||
+100
-14
@@ -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<i32>,
|
||||
status: BatteryStatus,
|
||||
eta_secs: Option<u64>,
|
||||
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<iced::window::Id>) -> 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<iced::window::Id>) -> Element<'_, Wire> {
|
||||
match window_id {
|
||||
Some(_) => self.popup(),
|
||||
None => self.bar(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Wire) -> Task<ModuleEffect> {
|
||||
match msg.downcast::<BatteryEvent>() {
|
||||
Some(BatteryEvent::PercentageChanged(percent)) => self.percent = Some(*percent),
|
||||
Some(BatteryEvent::StatusChanged(status)) => self.status = *status,
|
||||
_ => {}
|
||||
if let Some(event) = msg.downcast::<BatteryEvent>() {
|
||||
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::<BatteryAction>() {
|
||||
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<Service> {
|
||||
vec![BatteryMsg::Events.key()]
|
||||
}
|
||||
|
||||
fn popup_size(&self) -> Option<(u32, u32)> {
|
||||
Some((POPUP_W, POPUP_H))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user