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.
|
//! Messages owned by bar modules.
|
||||||
|
|
||||||
|
mod battery;
|
||||||
mod clock;
|
mod clock;
|
||||||
mod weather;
|
mod weather;
|
||||||
|
|
||||||
|
pub use battery::*;
|
||||||
pub use clock::*;
|
pub use clock::*;
|
||||||
pub use weather::*;
|
pub use weather::*;
|
||||||
|
|||||||
+100
-14
@@ -1,9 +1,12 @@
|
|||||||
//! Battery module: Android-style battery icon fed by the battery service.
|
//! 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 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 serde::Deserialize;
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
|
||||||
@@ -16,9 +19,14 @@ const PAD: f32 = 2.0;
|
|||||||
pub struct Battery {
|
pub struct Battery {
|
||||||
percent: Option<i32>,
|
percent: Option<i32>,
|
||||||
status: BatteryStatus,
|
status: BatteryStatus,
|
||||||
|
eta_secs: Option<u64>,
|
||||||
numbers_in_icon: bool,
|
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.
|
/// `module.battery` table; everything optional.
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default)]
|
||||||
struct BatteryConfig {
|
struct BatteryConfig {
|
||||||
@@ -32,6 +40,7 @@ impl Battery {
|
|||||||
Self {
|
Self {
|
||||||
percent: None,
|
percent: None,
|
||||||
status: BatteryStatus::default(),
|
status: BatteryStatus::default(),
|
||||||
|
eta_secs: None,
|
||||||
numbers_in_icon: config
|
numbers_in_icon: config
|
||||||
.and_then(|table| table.get("battery").cloned())
|
.and_then(|table| table.get("battery").cloned())
|
||||||
.and_then(|battery| battery.try_into().ok())
|
.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.
|
/// Icon ink: the theme's success green while charging, bar text otherwise.
|
||||||
fn ink(status: BatteryStatus, theme: &Theme) -> Color {
|
fn ink(status: BatteryStatus, theme: &Theme) -> Color {
|
||||||
let palette = theme.palette();
|
let palette = theme.palette();
|
||||||
@@ -104,12 +125,10 @@ fn bold() -> iced::Font {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BarModule for Battery {
|
impl Battery {
|
||||||
fn id(&self) -> &'static str {
|
/// Bar tile: icon (plus `%` label unless numbers live in the icon),
|
||||||
"battery"
|
/// left-click opens the info popup.
|
||||||
}
|
fn bar(&self) -> Element<'_, Wire> {
|
||||||
|
|
||||||
fn view(&self, _window_id: Option<iced::window::Id>) -> Element<'_, Wire> {
|
|
||||||
let status = self.status;
|
let status = self.status;
|
||||||
let shell = container(
|
let shell = container(
|
||||||
container(text(""))
|
container(text(""))
|
||||||
@@ -152,7 +171,7 @@ impl BarModule for Battery {
|
|||||||
]
|
]
|
||||||
.spacing(2)
|
.spacing(2)
|
||||||
.align_y(iced::Alignment::Center);
|
.align_y(iced::Alignment::Center);
|
||||||
if self.numbers_in_icon {
|
let bar: Element<'_, Wire> = if self.numbers_in_icon {
|
||||||
icon.into()
|
icon.into()
|
||||||
} else {
|
} else {
|
||||||
let label = match self.percent {
|
let label = match self.percent {
|
||||||
@@ -163,19 +182,86 @@ impl BarModule for Battery {
|
|||||||
.spacing(6)
|
.spacing(6)
|
||||||
.align_y(iced::Alignment::Center)
|
.align_y(iced::Alignment::Center)
|
||||||
.into()
|
.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> {
|
fn update(&mut self, msg: Wire) -> Task<ModuleEffect> {
|
||||||
match msg.downcast::<BatteryEvent>() {
|
if let Some(event) = msg.downcast::<BatteryEvent>() {
|
||||||
Some(BatteryEvent::PercentageChanged(percent)) => self.percent = Some(*percent),
|
match event {
|
||||||
Some(BatteryEvent::StatusChanged(status)) => self.status = *status,
|
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> {
|
fn services(&self) -> Vec<Service> {
|
||||||
vec![BatteryMsg::Events.key()]
|
vec![BatteryMsg::Events.key()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn popup_size(&self) -> Option<(u32, u32)> {
|
||||||
|
Some((POPUP_W, POPUP_H))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user