From a1a6caf5e5635e8d9304cab3385ee6a8b1c1277e Mon Sep 17 00:00:00 2001 From: Ilya Zlobintsev Date: Mon, 2 Feb 2026 23:47:38 +0200 Subject: [PATCH] feat(battery): add option to show charge percentage next to the icon (#994) * feat(battery): add option to show charge percentage next to the icon * feat: proper padding and text sizing * chore: use from_vec instead of from_children * chore: remove battery percentage setting from applet dropdown * chore: move config config definition to cosmic-applets-config * feat: add subscription for config * chore: revert unnecessary config module change * fix: correctly scale battery icon size * chore: change battery applet id * feat: better battery text sizing --------- Co-authored-by: Levi Portenier --- Cargo.lock | 3 + cosmic-applet-battery/Cargo.toml | 3 + cosmic-applet-battery/src/app.rs | 96 +++++++++++++++++++++++++--- cosmic-applet-battery/src/config.rs | 2 +- cosmic-applets-config/src/battery.rs | 11 ++++ cosmic-applets-config/src/lib.rs | 1 + 6 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 cosmic-applets-config/src/battery.rs diff --git a/Cargo.lock b/Cargo.lock index 75436a14..350e931f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1243,6 +1243,8 @@ name = "cosmic-applet-battery" version = "1.0.2" dependencies = [ "anyhow", + "cosmic-applets-config", + "cosmic-config", "cosmic-settings-daemon-subscription", "cosmic-settings-upower-subscription", "cosmic-time", @@ -1253,6 +1255,7 @@ dependencies = [ "libcosmic", "rust-embed", "rustc-hash 2.1.1", + "serde", "tokio", "tracing", "tracing-log", diff --git a/cosmic-applet-battery/Cargo.toml b/cosmic-applet-battery/Cargo.toml index 3349d675..b9c4dd13 100644 --- a/cosmic-applet-battery/Cargo.toml +++ b/cosmic-applet-battery/Cargo.toml @@ -7,6 +7,8 @@ license = "GPL-3.0-only" [dependencies] anyhow.workspace = true cosmic-time.workspace = true +cosmic-config.workspace = true +cosmic-applets-config.workspace = true drm = "0.14.1" futures.workspace = true i18n-embed-fl.workspace = true @@ -20,6 +22,7 @@ tracing-subscriber.workspace = true tracing.workspace = true udev = "0.9" zbus.workspace = true +serde.workspace = true [dependencies.cosmic-settings-upower-subscription] git = "https://github.com/pop-os/cosmic-settings" diff --git a/cosmic-applet-battery/src/app.rs b/cosmic-applet-battery/src/app.rs index 206c6d12..5c15c19c 100644 --- a/cosmic-applet-battery/src/app.rs +++ b/cosmic-applet-battery/src/app.rs @@ -13,7 +13,8 @@ use crate::{ use cosmic::{ Element, Task, app, applet::{ - cosmic_panel_config::PanelAnchor, + Size, + cosmic_panel_config::{PanelAnchor, PanelSize}, menu_button, padded_control, token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription}, }, @@ -22,13 +23,16 @@ use cosmic::{ iced::{ Length, Subscription, platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup}, - widget::{Column, Row, column, container, row}, + widget::{Column, column, container, row}, window, }, iced_core::{Alignment, Background, Border, Color, Shadow}, - surface, theme, - widget::{divider, horizontal_space, icon, scrollable, slider, text, vertical_space}, + surface, + theme::{self, Button}, + widget::{button, divider, horizontal_space, icon, scrollable, slider, text, vertical_space}, }; +use cosmic_applets_config::battery::BatteryAppletConfig; +use cosmic_config::{Config, CosmicConfigEntry}; use cosmic_settings_daemon_subscription as settings_daemon; use cosmic_settings_upower_subscription::{ device::{DeviceDbusEvent, device_subscription}, @@ -73,6 +77,7 @@ struct GPUData { #[derive(Clone, Default)] struct CosmicBatteryApplet { core: cosmic::app::Core, + config: BatteryAppletConfig, icon_name: String, display_icon_name: String, charging_limit: Option, @@ -193,6 +198,7 @@ enum Message { Profile(Power), SelectProfile(Power), Frame(Instant), + ConfigChanged(BatteryAppletConfig), Token(TokenUpdate), OpenSettings, SettingsDaemon(settings_daemon::Event), @@ -207,6 +213,11 @@ impl cosmic::Application for CosmicBatteryApplet { const APP_ID: &'static str = config::APP_ID; fn init(core: cosmic::app::Core, _flags: Self::Flags) -> (Self, app::Task) { + let config = Config::new(Self::APP_ID, BatteryAppletConfig::VERSION) + .ok() + .and_then(|c| BatteryAppletConfig::get_entry(&c).ok()) + .unwrap_or_default(); + let zbus_session_cmd = Task::perform(zbus::Connection::session(), |res| { cosmic::Action::App(Message::ZbusConnection(res)) }); @@ -216,6 +227,7 @@ impl cosmic::Application for CosmicBatteryApplet { ( Self { core, + config, icon_name: "battery-symbolic".to_string(), display_icon_name: "display-brightness-symbolic".to_string(), token_tx: None, @@ -421,6 +433,9 @@ impl cosmic::Application for CosmicBatteryApplet { self.popup = None; } } + Message::ConfigChanged(config) => { + self.config = config; + } Message::OpenSettings => { let exec = "cosmic-settings power".to_string(); if let Some(tx) = self.token_tx.as_ref() { @@ -499,12 +514,69 @@ impl cosmic::Application for CosmicBatteryApplet { Task::none() } - fn view(&self) -> Element<'_, Message> { - let btn: Element<'_, Message> = self - .core - .applet - .icon_button(&self.icon_name) + fn view(&self) -> Element { + let is_horizontal = match self.core.applet.anchor { + PanelAnchor::Top | PanelAnchor::Bottom => true, + PanelAnchor::Left | PanelAnchor::Right => false, + }; + + let suggested_size = self.core.applet.suggested_size(true); + let applet_padding = self.core.applet.suggested_padding(true); + + let mut children = vec![ + icon::from_name(self.icon_name.as_str()) + .size(suggested_size.0) + .into(), + ]; + + if self.config.show_percentage { + let text = format!("{:.0}%", self.battery_percent); + + let t = if is_horizontal { + self.core.applet.text(text) + } else { + match &self.core.applet.size { + Size::Hardcoded(_) => text::text(text), + Size::PanelSize(PanelSize::XS) => text::caption(text), + Size::PanelSize(PanelSize::S) => text::body(text), + Size::PanelSize(PanelSize::M) => text::title4(text), + Size::PanelSize(PanelSize::L) => text::title3(text), + _ => text::title2(text), + } + .width(Length::Fixed(suggested_size.0 as f32)) + }; + + children.push( + t.font(cosmic::font::default()) + .height(Length::Fixed(suggested_size.1 as f32)) + .align_x(Alignment::Center) + .align_y(Alignment::Center) + .into(), + ); + } + + let btn_content: Element<_> = if is_horizontal { + row(children) + .spacing(applet_padding.1) + .align_y(Alignment::Center) + .into() + } else { + column(children) + .spacing(applet_padding.1) + .align_x(Alignment::Center) + .into() + }; + + let (horizontal_padding, vertical_padding) = if is_horizontal { + (applet_padding.0, applet_padding.1) + } else { + (applet_padding.1, applet_padding.0) + }; + + let btn: Element<'_, Message> = button::custom(btn_content) .on_press_down(Message::TogglePopup) + .class(Button::AppletIcon) + .padding([vertical_padding, horizontal_padding]) .into(); let content = if self.gpus.is_empty() { @@ -884,6 +956,12 @@ impl cosmic::Application for CosmicBatteryApplet { .as_subscription() .map(|(_, now)| Message::Frame(now)), activation_token_subscription(0).map(Message::Token), + self.core.watch_config(Self::APP_ID).map(|u| { + for err in u.errors { + tracing::error!(?err, "Error watching config"); + } + Message::ConfigChanged(u.config) + }), ]; if let Some(conn) = self.zbus_connection.clone() { subscriptions.push(settings_daemon::subscription(conn).map(Message::SettingsDaemon)); diff --git a/cosmic-applet-battery/src/config.rs b/cosmic-applet-battery/src/config.rs index 737ffc7a..5c5b9894 100644 --- a/cosmic-applet-battery/src/config.rs +++ b/cosmic-applet-battery/src/config.rs @@ -1,4 +1,4 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only -pub const APP_ID: &str = "com.system76.CosmicAppletButton"; +pub const APP_ID: &str = "com.system76.CosmicAppletBattery"; diff --git a/cosmic-applets-config/src/battery.rs b/cosmic-applets-config/src/battery.rs new file mode 100644 index 00000000..49979223 --- /dev/null +++ b/cosmic-applets-config/src/battery.rs @@ -0,0 +1,11 @@ +// Copyright 2026 System76 +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic_config::{CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, CosmicConfigEntry, Default)] +#[version = 1] +pub struct BatteryAppletConfig { + pub show_percentage: bool, +} diff --git a/cosmic-applets-config/src/lib.rs b/cosmic-applets-config/src/lib.rs index 077885d7..ed71b971 100644 --- a/cosmic-applets-config/src/lib.rs +++ b/cosmic-applets-config/src/lib.rs @@ -1 +1,2 @@ +pub mod battery; pub mod time;