Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cosmic-applet-battery/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ cosmic-settings-subscriptions = { workspace = true, features = [
"settings_daemon",
] }
cosmic-time.workspace = true
cosmic-config.workspace = true
drm = "0.14.1"
futures.workspace = true
i18n-embed-fl.workspace = true
Expand All @@ -23,3 +24,4 @@ tracing-subscriber.workspace = true
tracing.workspace = true
udev = "0.9"
zbus.workspace = true
serde.workspace = true
1 change: 1 addition & 0 deletions cosmic-applet-battery/i18n/en/cosmic_applet_battery.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ balanced-desc = Standard performance and battery usage.
performance = High Performance
performance-desc = High performance and power usage.
max-charge = Increase the lifespan of your battery by setting a maximum charge value of 80%
show-percentage = Show battery percentage on panel
seconds = s
minutes = m
hours = h
Expand Down
106 changes: 93 additions & 13 deletions cosmic-applet-battery/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
Power, PowerProfileRequest, PowerProfileUpdate, get_charging_limit,
power_profile_subscription, set_charging_limit,
},
config,
config::{self, BatteryConfig},
dgpu::{Entry, GpuUpdate, dgpu_subscription},
fl,
};
Expand All @@ -26,9 +26,11 @@ use cosmic::{
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_config::{Config, CosmicConfigEntry};
use cosmic_settings_subscriptions::{
settings_daemon,
upower::{
Expand Down Expand Up @@ -64,6 +66,7 @@ pub fn run() -> cosmic::iced::Result {
}

static MAX_CHARGE: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);
static SHOW_PERCENTAGE: LazyLock<id::Toggler> = LazyLock::new(id::Toggler::unique);

#[derive(Clone, Default)]
struct GPUData {
Expand All @@ -75,6 +78,7 @@ struct GPUData {
#[derive(Clone, Default)]
struct CosmicBatteryApplet {
core: cosmic::app::Core,
config: BatteryConfig,
icon_name: String,
display_icon_name: String,
charging_limit: Option<bool>,
Expand Down Expand Up @@ -179,6 +183,7 @@ enum Message {
ReleaseScreenBrightness,
InitChargingLimit(Option<bool>),
SetChargingLimit(chain::Toggler, bool),
ShowBatteryPercentage(chain::Toggler, bool),
KeyboardBacklight(KeyboardBacklightUpdate),
UpowerDevice(DeviceDbusEvent),
GpuInit(UnboundedSender<()>),
Expand All @@ -204,6 +209,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(config::APP_ID, BatteryConfig::VERSION)
.ok()
.and_then(|c| BatteryConfig::get_entry(&c).ok())
.unwrap_or_default();

let zbus_session_cmd = Task::perform(zbus::Connection::session(), |res| {
cosmic::Action::App(Message::ZbusConnection(res))
});
Expand All @@ -213,6 +223,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,
Expand Down Expand Up @@ -312,6 +323,17 @@ impl cosmic::Application for CosmicBatteryApplet {
});
}
}
Message::ShowBatteryPercentage(chain, show) => {
self.timeline.set_chain(chain).start();

if let Ok(helper) =
cosmic::cosmic_config::Config::new(Self::APP_ID, BatteryConfig::VERSION)
{
if let Err(err) = self.config.set_show_percentage(&helper, show) {
tracing::error!(?err, "Error writing config");
}
}
}
Message::Errored(why) => {
tracing::error!("{}", why);
}
Expand Down Expand Up @@ -484,11 +506,47 @@ impl cosmic::Application for CosmicBatteryApplet {
}

fn view(&self) -> Element<Message> {
let btn = self
.core
.applet
.icon_button(&self.icon_name)
let Spacing { space_xs, .. } = theme::active().cosmic().spacing;

let is_horizontal = match self.core.applet.anchor {
PanelAnchor::Top | PanelAnchor::Bottom => true,
PanelAnchor::Left | PanelAnchor::Right => false,
};

let mut children = vec![icon::from_name(self.icon_name.as_str()).into()];

let suggested_size = self.core.applet.suggested_size(true);
let applet_padding = self.core.applet.suggested_padding(true);

if self.config.show_percentage {
children.push(
self.core
.applet
.text(format!("{:.0}%", self.battery_percent))
.width(Length::Fixed(suggested_size.0 as f32))
.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(space_xs)
.align_y(Alignment::Center)
.into()
} else {
column(children)
.spacing(space_xs)
.align_x(Alignment::Center)
.into()
};

let btn = button::custom(btn_content)
.on_press_down(Message::TogglePopup)
.class(Button::AppletIcon)
.padding(applet_padding)
.into();

let content = if !self.gpus.is_empty() {
Expand All @@ -509,13 +567,14 @@ impl cosmic::Application for CosmicBatteryApplet {
})))
.into();

match self.core.applet.anchor {
PanelAnchor::Left | PanelAnchor::Right => Column::with_children(vec![btn, dot])
.align_x(Alignment::Center)
.into(),
PanelAnchor::Top | PanelAnchor::Bottom => Row::with_children(vec![btn, dot])
if is_horizontal {
Row::with_children(vec![btn, dot])
Copy link
Contributor

@Cheong-Lau Cheong-Lau Oct 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤓 if Row::with_children and Column::with_children takes an impl IntoIterator, then the vec! macro isn't really needed, you could just pass the array itself

... 😅 sorry, besides that lgtm, looking forward to seeing this merged

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I changed it to from_vec instead, since that's what with_children does internally regardless.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to use with_children (since that's used basically everywhere applicable). Just remove the vec!.

.align_y(Alignment::Center)
.into(),
.into()
} else {
Column::with_children(vec![btn, dot])
.align_x(Alignment::Center)
.into()
}
} else {
btn
Expand Down Expand Up @@ -647,6 +706,27 @@ impl cosmic::Application for CosmicBatteryApplet {
);
}

content.push(
padded_control(
anim!(
//toggler
SHOW_PERCENTAGE,
&self.timeline,
fl!("show-percentage"),
self.config.show_percentage,
Message::ShowBatteryPercentage,
)
.text_size(14)
.width(Length::Fill),
)
.into(),
);
content.push(
padded_control(divider::horizontal::default())
.padding([space_xxs, space_s])
.into(),
);

if let Some(max_screen_brightness) = self.max_screen_brightness {
if let Some(screen_brightness) = self.screen_brightness {
content.push(
Expand Down
9 changes: 9 additions & 0 deletions cosmic-applet-battery/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
// Copyright 2023 System76 <[email protected]>
// SPDX-License-Identifier: GPL-3.0-only

use cosmic_config::{cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry};
use serde::{Deserialize, Serialize};

pub const APP_ID: &str = "com.system76.CosmicAppletButton";

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, CosmicConfigEntry, Default)]
#[version = 1]
pub struct BatteryConfig {
pub show_percentage: bool,
}