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 <levi@system76.com>
This commit is contained in:
Ilya Zlobintsev 2026-02-02 23:47:38 +02:00 committed by GitHub
parent fe2ebe7a89
commit a1a6caf5e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 106 additions and 10 deletions

3
Cargo.lock generated
View file

@ -1243,6 +1243,8 @@ name = "cosmic-applet-battery"
version = "1.0.2" version = "1.0.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cosmic-applets-config",
"cosmic-config",
"cosmic-settings-daemon-subscription", "cosmic-settings-daemon-subscription",
"cosmic-settings-upower-subscription", "cosmic-settings-upower-subscription",
"cosmic-time", "cosmic-time",
@ -1253,6 +1255,7 @@ dependencies = [
"libcosmic", "libcosmic",
"rust-embed", "rust-embed",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
"serde",
"tokio", "tokio",
"tracing", "tracing",
"tracing-log", "tracing-log",

View file

@ -7,6 +7,8 @@ license = "GPL-3.0-only"
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
cosmic-time.workspace = true cosmic-time.workspace = true
cosmic-config.workspace = true
cosmic-applets-config.workspace = true
drm = "0.14.1" drm = "0.14.1"
futures.workspace = true futures.workspace = true
i18n-embed-fl.workspace = true i18n-embed-fl.workspace = true
@ -20,6 +22,7 @@ tracing-subscriber.workspace = true
tracing.workspace = true tracing.workspace = true
udev = "0.9" udev = "0.9"
zbus.workspace = true zbus.workspace = true
serde.workspace = true
[dependencies.cosmic-settings-upower-subscription] [dependencies.cosmic-settings-upower-subscription]
git = "https://github.com/pop-os/cosmic-settings" git = "https://github.com/pop-os/cosmic-settings"

View file

@ -13,7 +13,8 @@ use crate::{
use cosmic::{ use cosmic::{
Element, Task, app, Element, Task, app,
applet::{ applet::{
cosmic_panel_config::PanelAnchor, Size,
cosmic_panel_config::{PanelAnchor, PanelSize},
menu_button, padded_control, menu_button, padded_control,
token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription}, token::subscription::{TokenRequest, TokenUpdate, activation_token_subscription},
}, },
@ -22,13 +23,16 @@ use cosmic::{
iced::{ iced::{
Length, Subscription, Length, Subscription,
platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup}, platform_specific::shell::wayland::commands::popup::{destroy_popup, get_popup},
widget::{Column, Row, column, container, row}, widget::{Column, column, container, row},
window, window,
}, },
iced_core::{Alignment, Background, Border, Color, Shadow}, iced_core::{Alignment, Background, Border, Color, Shadow},
surface, theme, surface,
widget::{divider, horizontal_space, icon, scrollable, slider, text, vertical_space}, 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_daemon_subscription as settings_daemon;
use cosmic_settings_upower_subscription::{ use cosmic_settings_upower_subscription::{
device::{DeviceDbusEvent, device_subscription}, device::{DeviceDbusEvent, device_subscription},
@ -73,6 +77,7 @@ struct GPUData {
#[derive(Clone, Default)] #[derive(Clone, Default)]
struct CosmicBatteryApplet { struct CosmicBatteryApplet {
core: cosmic::app::Core, core: cosmic::app::Core,
config: BatteryAppletConfig,
icon_name: String, icon_name: String,
display_icon_name: String, display_icon_name: String,
charging_limit: Option<bool>, charging_limit: Option<bool>,
@ -193,6 +198,7 @@ enum Message {
Profile(Power), Profile(Power),
SelectProfile(Power), SelectProfile(Power),
Frame(Instant), Frame(Instant),
ConfigChanged(BatteryAppletConfig),
Token(TokenUpdate), Token(TokenUpdate),
OpenSettings, OpenSettings,
SettingsDaemon(settings_daemon::Event), SettingsDaemon(settings_daemon::Event),
@ -207,6 +213,11 @@ impl cosmic::Application for CosmicBatteryApplet {
const APP_ID: &'static str = config::APP_ID; const APP_ID: &'static str = config::APP_ID;
fn init(core: cosmic::app::Core, _flags: Self::Flags) -> (Self, app::Task<Self::Message>) { fn init(core: cosmic::app::Core, _flags: Self::Flags) -> (Self, app::Task<Self::Message>) {
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| { let zbus_session_cmd = Task::perform(zbus::Connection::session(), |res| {
cosmic::Action::App(Message::ZbusConnection(res)) cosmic::Action::App(Message::ZbusConnection(res))
}); });
@ -216,6 +227,7 @@ impl cosmic::Application for CosmicBatteryApplet {
( (
Self { Self {
core, core,
config,
icon_name: "battery-symbolic".to_string(), icon_name: "battery-symbolic".to_string(),
display_icon_name: "display-brightness-symbolic".to_string(), display_icon_name: "display-brightness-symbolic".to_string(),
token_tx: None, token_tx: None,
@ -421,6 +433,9 @@ impl cosmic::Application for CosmicBatteryApplet {
self.popup = None; self.popup = None;
} }
} }
Message::ConfigChanged(config) => {
self.config = config;
}
Message::OpenSettings => { Message::OpenSettings => {
let exec = "cosmic-settings power".to_string(); let exec = "cosmic-settings power".to_string();
if let Some(tx) = self.token_tx.as_ref() { if let Some(tx) = self.token_tx.as_ref() {
@ -499,12 +514,69 @@ impl cosmic::Application for CosmicBatteryApplet {
Task::none() Task::none()
} }
fn view(&self) -> Element<'_, Message> { fn view(&self) -> Element<Message> {
let btn: Element<'_, Message> = self let is_horizontal = match self.core.applet.anchor {
.core PanelAnchor::Top | PanelAnchor::Bottom => true,
.applet PanelAnchor::Left | PanelAnchor::Right => false,
.icon_button(&self.icon_name) };
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) .on_press_down(Message::TogglePopup)
.class(Button::AppletIcon)
.padding([vertical_padding, horizontal_padding])
.into(); .into();
let content = if self.gpus.is_empty() { let content = if self.gpus.is_empty() {
@ -884,6 +956,12 @@ impl cosmic::Application for CosmicBatteryApplet {
.as_subscription() .as_subscription()
.map(|(_, now)| Message::Frame(now)), .map(|(_, now)| Message::Frame(now)),
activation_token_subscription(0).map(Message::Token), 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() { if let Some(conn) = self.zbus_connection.clone() {
subscriptions.push(settings_daemon::subscription(conn).map(Message::SettingsDaemon)); subscriptions.push(settings_daemon::subscription(conn).map(Message::SettingsDaemon));

View file

@ -1,4 +1,4 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
pub const APP_ID: &str = "com.system76.CosmicAppletButton"; pub const APP_ID: &str = "com.system76.CosmicAppletBattery";

View file

@ -0,0 +1,11 @@
// Copyright 2026 System76 <info@system76.com>
// 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,
}

View file

@ -1 +1,2 @@
pub mod battery;
pub mod time; pub mod time;