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"
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",

View file

@ -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"

View file

@ -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<bool>,
@ -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<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| {
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<Message> {
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));

View file

@ -1,4 +1,4 @@
// Copyright 2023 System76 <info@system76.com>
// 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;