From c6b548349da33334bf848b5cd01d2dd6464e2ac1 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Tue, 29 Sep 2026 12:00:08 +0100 Subject: [PATCH] Restore the battery menu sections and center their icons A let-chain collapse in the status menu left the battery blocks inside the notifications arm, so the percentage, level meter, power mode row, and power actions only rendered when notifications were present. Close the notifications block before the battery checks so each menu kind owns its own sections again. Use center_x/center_y on the fixed-size icon containers rather than align_x/align_y, which left badges and the top bar slot sitting off center in libcosmic, and constrain the menu icon to a square 22px slot so the battery glyph is not letterboxed by its own width. Give the power tiles a Fill-width centering container inside their FillPortion(1) buttons so the label and icon stay centered on hover. Drop the two column-alignment TODO wrappers the collapse introduced. --- shell/ferese-shell/src/status_ui.rs | 296 ++++++++++++++-------------- 1 file changed, 149 insertions(+), 147 deletions(-) diff --git a/shell/ferese-shell/src/status_ui.rs b/shell/ferese-shell/src/status_ui.rs index 781e991..45c2f9a 100644 --- a/shell/ferese-shell/src/status_ui.rs +++ b/shell/ferese-shell/src/status_ui.rs @@ -348,11 +348,12 @@ impl FereseShell { )); } // A fixed button height does not center its child in libcosmic. - // Keep the visual content centered inside the entire click target. + // center_y/center_x wrap the content in a centering layout, so use + // those rather than align_y/align_x on a fixed-size container. let content = container(content) .align_x(alignment::Horizontal::Center) .height(metrics.control_height) - .align_y(alignment::Vertical::Center); + .center_y(metrics.control_height); let recording_busy = kind == Menu::Recording && self.recorder.busy(); let control = button::custom(content) .name(if recording_busy { @@ -419,16 +420,19 @@ impl FereseShell { let kind = menu.kind; let primary = color_with_opacity(theme.text_primary, p); let muted = color_with_opacity(theme.text_muted, p); - let badge = container(accented_icon( - status_icon(kind, &self.status).0, - 22, - primary, - color_with_opacity(theme.accent, p), - )) + let badge = container( + accented_icon( + status_icon(kind, &self.status).0, + 22, + primary, + color_with_opacity(theme.accent, p), + ) + .width(Length::Fixed(22.0)), + ) .width(36) .height(36) - .align_x(alignment::Horizontal::Center) - .align_y(alignment::Vertical::Center) + .center_x(36) + .center_y(36) .class(theme::Container::custom(move |_| container::Style { background: Some(Background::Color(color_with_opacity( theme.accent, @@ -725,139 +729,137 @@ impl FereseShell { p, )); } + } - if menu.kind == Menu::Battery - && let Some(b) = &self.status.battery + if menu.kind == Menu::Battery + && let Some(b) = &self.status.battery + { + let battery_color = if b.percent < self.config.status.low_battery_threshold + && b.status != "Charging" { - let battery_color = if b.percent < self.config.status.low_battery_threshold - && b.status != "Charging" - { - Color { - a: p, - ..Color::from_rgb8(230, 172, 90) - } - } else { - primary - }; - rows = rows.push( - container( - row![ - text(format!("{}%", b.percent)).size(24).width(Length::Fill), - accented_icon( - status_icon(Menu::Battery, &self.status).0, - 24, - battery_color, - color_with_opacity(theme.accent, p), - ), - ] - .align_y(Alignment::Center), - ) - .padding([2, 0]) - .class(theme::Container::custom(move |_| container::Style { - text_color: Some(battery_color), - icon_color: Some(battery_color), - ..Default::default() - })), - ); - rows = rows.push(level_meter(b.percent, battery_color, p)); - rows = rows.push( - text(match b.status.as_str() { - "Charging" => "Connected to power", - "Discharging" => "Running on battery power", - "Full" => "Battery fully charged", - _ => "Battery status reported by the system", - }) - .size(12) - .class(theme::Text::Color(muted)), - ); - } - - if menu.kind == Menu::Battery { - rows = rows.push(text("Power mode").size(13).class(theme::Text::Color(muted))); - if let Some(profiles) = &self.status.power_profiles { - let mut modes = row::with_capacity(3).spacing(6).width(Length::Fill); - for (index, (label, icon, profile)) in [ - ( - "Power saver", - include_bytes!("../assets/icons/status/power-saver.svg").as_slice(), - "power-saver", - ), - ( - "Balanced", - include_bytes!("../assets/icons/status/power-balanced.svg") - .as_slice(), - "balanced", - ), - ( - "Performance", - include_bytes!("../assets/icons/status/power-performance.svg") - .as_slice(), - "performance", + Color { + a: p, + ..Color::from_rgb8(230, 172, 90) + } + } else { + primary + }; + rows = rows.push( + container( + row![ + text(format!("{}%", b.percent)).size(24).width(Length::Fill), + accented_icon( + status_icon(Menu::Battery, &self.status).0, + 24, + battery_color, + color_with_opacity(theme.accent, p), ), ] - .into_iter() - .enumerate() - { - if profiles.available[index] { - modes = modes.push(power_tile( - label, - icon, - profile, - profiles.active == profile, - theme, - p, - )); - } - } - // TODO: confirm column align fix - rows = rows.push(modes.width(Length::Fill).align_y(Alignment::Center)); - } else { - rows = rows.push( - text("Power Profiles service unavailable") - .size(12) - .class(theme::Text::Color(muted)), - ); - } + .align_y(Alignment::Center), + ) + .padding([2, 0]) + .class(theme::Container::custom(move |_| container::Style { + text_color: Some(battery_color), + icon_color: Some(battery_color), + ..Default::default() + })), + ); + rows = rows.push(level_meter(b.percent, battery_color, p)); + rows = rows.push( + text(match b.status.as_str() { + "Charging" => "Connected to power", + "Discharging" => "Running on battery power", + "Full" => "Battery fully charged", + _ => "Battery status reported by the system", + }) + .size(12) + .class(theme::Text::Color(muted)), + ); + } - let mut power = row::with_capacity(3).spacing(6).width(Length::Fill); - for (enabled, label, icon, action) in [ + if menu.kind == Menu::Battery { + rows = rows.push(text("Power mode").size(13).class(theme::Text::Color(muted))); + if let Some(profiles) = &self.status.power_profiles { + let mut modes = row::with_capacity(3).spacing(6).width(Length::Fill); + for (index, (label, icon, profile)) in [ ( - self.status.reboot, - "Restart", - include_bytes!("../assets/icons/status/restart.svg").as_slice(), - Action::Reboot, + "Power saver", + include_bytes!("../assets/icons/status/power-saver.svg").as_slice(), + "power-saver", ), ( - self.status.poweroff, - "Power off", - include_bytes!("../assets/icons/status/power-off.svg").as_slice(), - Action::Poweroff, + "Balanced", + include_bytes!("../assets/icons/status/power-balanced.svg").as_slice(), + "balanced", ), ( - self.status.suspend, - "Suspend", - include_bytes!("../assets/icons/status/suspend.svg").as_slice(), - Action::Suspend, + "Performance", + include_bytes!("../assets/icons/status/power-performance.svg") + .as_slice(), + "performance", ), - ] { - if enabled { - power = power.push(power_tile_action(label, icon, action, theme, p)); + ] + .into_iter() + .enumerate() + { + if profiles.available[index] { + modes = modes.push(power_tile( + label, + icon, + profile, + profiles.active == profile, + theme, + p, + )); } } - - if self.status.reboot || self.status.poweroff || self.status.suspend { - rows = rows.push(text("Power").size(13).class(theme::Text::Color(muted))); - rows = rows.push(power.align_y(Alignment::Center).width(Length::Fill)); - } - } - - if !menu.kind.available(&self.status) { + rows = rows.push(modes); + } else { rows = rows.push( - text("Service unavailable") - .size(13) + text("Power Profiles service unavailable") + .size(12) .class(theme::Text::Color(muted)), ); } + + let mut power = row::with_capacity(3).spacing(6).width(Length::Fill); + for (enabled, label, icon, action) in [ + ( + self.status.reboot, + "Restart", + include_bytes!("../assets/icons/status/restart.svg").as_slice(), + Action::Reboot, + ), + ( + self.status.poweroff, + "Power off", + include_bytes!("../assets/icons/status/power-off.svg").as_slice(), + Action::Poweroff, + ), + ( + self.status.suspend, + "Suspend", + include_bytes!("../assets/icons/status/suspend.svg").as_slice(), + Action::Suspend, + ), + ] { + if enabled { + power = power.push(power_tile_action(label, icon, action, theme, p)); + } + } + + if self.status.reboot || self.status.poweroff || self.status.suspend { + rows = rows.push(text("Power").size(13).class(theme::Text::Color(muted))); + rows = rows.push(power); + } + } + + if !menu.kind.available(&self.status) { + rows = rows.push( + text("Service unavailable") + .size(13) + .class(theme::Text::Color(muted)), + ); } } @@ -921,8 +923,8 @@ fn status_summary<'a>( let badge = container(accented_icon(source, 22, primary, accent)) .width(32) .height(32) - .align_x(alignment::Horizontal::Center) - .align_y(alignment::Vertical::Center) + .center_x(32) + .center_y(32) .class(theme::Container::custom(move |_| container::Style { background: Some(Background::Color(Color { a: if enabled { @@ -1138,13 +1140,14 @@ fn power_tile<'a>( }, opacity, ); - let tile = column![ - bar_icon(icon, 19, foreground), - text(label).size(11).align_x(Alignment::Center) - ] - .spacing(3) - .align_x(Alignment::Center); - let btn = button::custom(tile.align_x(Alignment::Center)) + let tile = container( + column![bar_icon(icon, 19, foreground), text(label).size(11)] + .spacing(3) + .align_x(Alignment::Center), + ) + .width(Length::Fill) + .center_x(Length::Fill); + let btn = button::custom(tile) .width(Length::FillPortion(1)) .padding([8, 2]) .on_press(cosmic::Action::App(Message::Control(Action::PowerProfile( @@ -1162,20 +1165,19 @@ fn power_tile_action<'a>( opacity: f32, ) -> Element<'a, cosmic::Action> { let foreground = color_with_opacity(theme.text_primary, opacity); - // TODO: confirm column align fix - motion::button( - button::custom( - column![bar_icon(icon, 19, foreground), text(label).size(11)] - .spacing(3) - .align_x(Alignment::Center), - ) + let tile = container( + column![bar_icon(icon, 19, foreground), text(label).size(11)] + .spacing(3) + .align_x(Alignment::Center), + ) + .width(Length::Fill) + .center_x(Length::Fill); + let btn = button::custom(tile) .width(Length::FillPortion(1)) .padding([8, 2]) - .on_press(cosmic::Action::App(Message::ConfirmPower(action))), - foreground, - false, - opacity, - ) + .on_press(cosmic::Action::App(Message::ConfirmPower(action))); + + motion::button(btn, foreground, false, opacity) } fn calendar_month(today: jiff::civil::Date, offset: i32) -> (jiff::civil::Date, usize, usize) {