From d71fab710257f8d16315e47d9ea8e075e291bcc0 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Sun, 27 Sep 2026 06:38:49 +0100 Subject: [PATCH] Refine Settings performance, theme gallery, and shell popups --- README.md | 2 + docs/configuration.md | 2 + shell/ferese-settings/src/main.rs | 88 ++++++++---- shell/ferese-settings/src/visuals.rs | 61 +++++++-- shell/ferese-shell/src/main.rs | 79 +++++++---- shell/ferese-shell/src/motion.rs | 5 +- shell/ferese-shell/src/status_ui.rs | 196 ++++++++++++++++++++------- 7 files changed, 313 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index 116718a..596bc20 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ Built-in defaults (your configuration can override them): The bar shows workspaces 1–9 in a compact numbered strip, including empty ones. Click a number to switch using the same behavior as Super+1–9. A filled button marks this monitor's active workspace; an outlined button marks one active on another monitor. +Status controls and the clock use matching compact groups. The control center +opens as one rounded panel with grouped controls and a Settings shortcut. Three-finger swipes up/down navigate workspaces on this monitor; left/right navigate windows. Assign swipes to any keyboard-binding action (including overview, move, or a launcher command) in **Settings → Shortcuts**. diff --git a/docs/configuration.md b/docs/configuration.md index 95b83e2..5e525fc 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -107,6 +107,8 @@ Changing default width preserves manually resized columns. Settings → Appearance offers six presets: **Ferese Blue** (default), **Monochrome**, **Gruvbox**, **Dracula**, **Ayu Light**, and **Monokai**. +The two-row gallery previews each palette on a miniature desktop; a checkmark +identifies the active preset. Ferese Blue uses the README logo's `#3D7BE6`. Each preset coordinates surfaces, text, menu-bar colors, borders and the focus ring. Ayu Light also switches native Settings controls to light styling. Presets preserve wallpaper, typography, diff --git a/shell/ferese-settings/src/main.rs b/shell/ferese-settings/src/main.rs index c24c327..66b0e68 100644 --- a/shell/ferese-settings/src/main.rs +++ b/shell/ferese-settings/src/main.rs @@ -14,14 +14,6 @@ use std::{collections::HashMap, path::PathBuf}; use store::{Edit, Snapshot, set}; fn main() -> cosmic::iced::Result { - // Set before libcosmic creates worker threads. The software backend is - // efficient for this mostly-static interface; WGPU remains a fallback. - if std::env::var_os("ICED_BACKEND").is_none() { - unsafe { - std::env::set_var("ICED_BACKEND", "tiny-skia,wgpu"); - } - } - let mut args = std::env::args_os().skip(1); let path = match args.next() { Some(arg) if arg == "--config" => { @@ -92,6 +84,7 @@ struct App { ranges: HashMap, new_command: String, font: cosmic::font::Font, + native_palette: visuals::Palette, thumbnail: Option, thumbnail_path: String, thumbnail_loading: bool, @@ -130,6 +123,7 @@ impl cosmic::Application for App { let error = initial.as_ref().err().cloned(); let current = initial.unwrap_or_else(|_| Snapshot::parse(String::new()).unwrap()); let font = visuals::configured_font(¤t); + let native_palette = visuals::Palette::from(¤t); let mut app = Self { note_editors: HashMap::new(), core, @@ -148,6 +142,7 @@ impl cosmic::Application for App { ranges: HashMap::new(), new_command: String::new(), font, + native_palette, thumbnail: None, thumbnail_path: String::new(), thumbnail_loading: false, @@ -241,16 +236,12 @@ impl cosmic::Application for App { self.error = None; self.status = "Updated from your config".into(); - return Task::batch([ - cosmic::command::set_theme(visuals::native_theme(Some(&self.draft))), - self.load_thumbnail(), - ]); + return Task::batch([self.update_theme(), self.load_thumbnail()]); } } Err(error) => self.error = Some(format!("Config reload failed: {error}")), }, Message::Page(page) => { - self.sync_notes(); self.page = page; self.search.clear(); if page == Page::Wallpaper { @@ -321,10 +312,8 @@ impl cosmic::Application for App { } .into(); - let theme = visuals::native_theme(Some(&self.draft)); - return Task::batch([ - cosmic::command::set_theme(theme), + self.update_theme(), self.flush(), self.load_thumbnail(), ]); @@ -351,10 +340,7 @@ impl cosmic::Application for App { self.undo = None; self.status = "Reloaded from your config".into(); - return Task::batch([ - cosmic::command::set_theme(visuals::native_theme(Some(&self.draft))), - self.load_thumbnail(), - ]); + return Task::batch([self.update_theme(), self.load_thumbnail()]); } Err(error) => self.error = Some(error), }, @@ -591,28 +577,46 @@ impl cosmic::Application for App { ); } } else { - if matches!(self.page, Page::Appearance | Page::Bar | Page::Windows) { + if matches!(self.page, Page::Bar | Page::Windows) { body = body.push(visuals::preview(&self.draft)); } if self.page == Page::Appearance { + body = body.push( + column([]) + .spacing(4) + .push(self.label("Theme", 15.)) + .push(self.note("Choose a look for your desktop.")), + ); let mut presets = column([]).spacing(10); for (row_index, choices) in visuals::PRESETS.chunks(3).enumerate() { let mut tiles = row([]).spacing(10); for (column_index, preset) in choices.iter().enumerate() { let index = row_index * 3 + column_index; + let selected = visuals::preset_selected(&self.draft, index); + let indicator: Element<'_, Message> = if selected { + visuals::action_icon("M5 12l4 4L19 6", palette.accent).into() + } else { + widget::Space::new().width(16).height(16).into() + }; let tile = button::custom( column([]) - .spacing(6) - .push(visuals::swatches(index)) - .push(self.label(preset.name, 12.)) - .push(self.label(preset.description, 10.)), + .spacing(8) + .push(visuals::preset_preview(index)) + .push( + row([]) + .spacing(6) + .align_y(Alignment::Center) + .push(self.label(preset.name, 12.).width(Length::Fill)) + .push(indicator), + ) + .push( + self.label(preset.description, 10.) + .class(cosmic::theme::Text::Color(palette.muted)), + ), ) .width(Length::Fill) .padding(10) - .class(visuals::button_style( - palette, - visuals::preset_selected(&self.draft, index), - )); + .class(visuals::button_style(palette, selected)); tiles = tiles.push(if self.saving { tile } else { @@ -866,7 +870,22 @@ impl cosmic::Application for App { } content = content.push( - scrollable(container(body).padding([0, 2]).width(Length::Fill)).height(Length::Fill), + scrollable( + container(body) + .padding(cosmic::iced::Padding { + top: 0., + right: 12., + bottom: 0., + left: 2., + }) + .width(Length::Fill), + ) + .direction(cosmic::iced::widget::scrollable::Direction::Vertical( + cosmic::iced::widget::scrollable::Scrollbar::new() + .width(10) + .scroller_width(3), + )) + .height(Length::Fill), ); let footer = row([]) .spacing(4) @@ -945,6 +964,15 @@ impl cosmic::Application for App { } impl App { + fn update_theme(&mut self) -> Task { + let palette = visuals::Palette::from(&self.draft); + if palette == self.native_palette { + return Task::none(); + } + self.native_palette = palette; + cosmic::command::set_theme(visuals::native_theme(Some(&self.draft))) + } + fn note_index(&self, id: &str) -> Option { (0..self.draft.records("desktop_widgets.notes")).find(|index| { self.draft diff --git a/shell/ferese-settings/src/visuals.rs b/shell/ferese-settings/src/visuals.rs index 34d1fcf..20b4a96 100644 --- a/shell/ferese-settings/src/visuals.rs +++ b/shell/ferese-settings/src/visuals.rs @@ -7,10 +7,10 @@ use cosmic::{ Element, iced::{Background, Border, Color, Length, Vector}, theme, - widget::{self, button, container, icon as svg_icon}, + widget::{button, container, icon as svg_icon}, }; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub struct Palette { pub background: Color, pub sidebar: Color, @@ -376,16 +376,53 @@ pub fn preset_selected(snapshot: &Snapshot, index: usize) -> bool { }) && snapshot.number("theme.focus_ring.gradient.angle", 0.) == 0. } -pub fn swatches(index: usize) -> Element<'static, Message> { - let preset = &PRESETS[index]; - let mut row = widget::row([]).spacing(3); - for value in [preset.base, preset.gradient_end, preset.accent] { - row = row.push( - container(widget::Space::new().width(12).height(22)) - .class(surface(color(value, Color::WHITE), 4.)), - ); - } - row.into() +fn preset_preview_handle(preset: &Preset) -> svg_icon::Handle { + let base = preset.base; + let accent = preset.accent; + let end = preset.gradient_end; + let text = preset.text; + let muted = preset.muted; + let surface = color(base, Color::BLACK); + let card = hex(mix(surface, surface_shade(surface), 0.055)); + let svg = format!( + r##" + + + + + + + + + + + + + + + + + + + + + + + +"##, + ); + svg_icon::from_svg_bytes(svg.into_bytes()).symbolic(false) +} + +pub fn preset_preview(index: usize) -> Element<'static, Message> { + static HANDLES: std::sync::OnceLock> = std::sync::OnceLock::new(); + HANDLES.get_or_init(|| PRESETS.iter().map(preset_preview_handle).collect())[index] + .clone() + .icon() + .width(Length::Fill) + .height(Length::Fixed(90.)) + .content_fit(cosmic::iced::ContentFit::Contain) + .into() } pub fn preview(snapshot: &Snapshot) -> Element<'static, Message> { diff --git a/shell/ferese-shell/src/main.rs b/shell/ferese-shell/src/main.rs index 9192326..e24eba7 100644 --- a/shell/ferese-shell/src/main.rs +++ b/shell/ferese-shell/src/main.rs @@ -436,7 +436,11 @@ impl cosmic::Application for FereseShell { && menu.id == id && menu.effects.is_none() { - match EffectsBinding::attach_role(&surface, menu.kind.material_role()) { + match EffectsBinding::attach_role( + &surface, + menu.kind.material_role(), + menu.progress(), + ) { Ok(binding) => menu.effects = Some(binding), Err(error) => { eprintln!("ferese-shell: popover material unavailable: {error}") @@ -1546,6 +1550,7 @@ impl FereseShell { match EffectsBinding::attach_role( &surface, menu.kind.material_role(), + menu.progress(), ) { Ok(binding) => menu.effects = Some(binding), Err(error) => eprintln!( @@ -1561,6 +1566,7 @@ impl FereseShell { match EffectsBinding::attach_role( &surface, menu.kind.material_role(), + menu.progress(), ) { Ok(binding) => menu.effects = Some(binding), Err(error) => eprintln!( @@ -1593,7 +1599,11 @@ impl FereseShell { ))) if self.menu.as_ref().is_some_and(|menu| menu.id == frame_id) => { if let Some(menu) = &mut self.menu { if menu.effects.is_none() { - match EffectsBinding::attach_role(&surface, menu.kind.material_role()) { + match EffectsBinding::attach_role( + &surface, + menu.kind.material_role(), + menu.progress(), + ) { Ok(binding) => menu.effects = Some(binding), Err(error) => { eprintln!("ferese-shell: popover material unavailable: {error}") @@ -1657,7 +1667,7 @@ impl FereseShell { .align_y(cosmic::iced::Alignment::Center); workspace_row = workspace_row.push(motion::button( - button::custom(overview_control(bar, color(shell_theme.text_primary))) + button::custom(overview_control(bar, color(shell_theme.accent))) .height(bar.control_height) .padding([0, 7]) .on_press(cosmic::Action::App(Message::ToggleOverview)), @@ -1715,18 +1725,7 @@ impl FereseShell { workspace_buttons = workspace_buttons.push(selector); } workspace_row = workspace_row.push(container(workspace_buttons).padding([0, 3]).class( - theme::Container::custom(move |_| container::Style { - background: Some(Background::Color(color_with_opacity( - shell_theme.text_primary, - 0.035, - ))), - border: Border { - color: color_with_opacity(shell_theme.text_muted, 0.12), - width: 1.0, - radius: 7.0.into(), - }, - ..Default::default() - }), + theme::Container::custom(move |_| bar_group_style(shell_theme)), )); let foreground = color(shell_theme.text_primary); @@ -1738,13 +1737,25 @@ impl FereseShell { )) .width(Length::Fill) .height(bar.control_height); + let (date, time) = self.clock.split_once(", ").unwrap_or(("", &self.clock)); let clock = container( - text(&self.clock) - .size(bar.text_size) - .wrapping(cosmic::iced::core::text::Wrapping::None) - .class(theme::Text::Color(foreground)), + row![ + text(date) + .size(12) + .class(theme::Text::Color(color(shell_theme.text_muted))), + text(time) + .size(bar.text_size) + .class(theme::Text::Color(foreground)), + ] + .spacing(8) + .align_y(cosmic::iced::Alignment::Center), ) - .padding([0, 6]); + .height(bar.control_height) + .align_y(alignment::Vertical::Center) + .padding([0, 8]) + .class(theme::Container::custom(move |_| { + bar_group_style(shell_theme) + })); let right = row![ self.view_status_bar().map(move |action| match action { cosmic::Action::App(Message::OpenMenu(kind, anchor)) => @@ -1753,7 +1764,7 @@ impl FereseShell { }), clock ] - .spacing(6) + .spacing(8) .align_y(cosmic::iced::Alignment::Center); let content = row![ container(left).width(Length::Fill), @@ -2127,6 +2138,21 @@ fn bar_style(theme: ShellTheme, compositor_material: bool) -> container::Style { } } +fn bar_group_style(theme: ShellTheme) -> container::Style { + container::Style { + background: Some(Background::Color(color_with_opacity( + theme.text_primary, + 0.035, + ))), + border: Border { + color: color_with_opacity(theme.text_muted, 0.12), + width: 1.0, + radius: 7.0.into(), + }, + ..Default::default() + } +} + fn color([red, green, blue, alpha]: [u8; 4]) -> Color { Color::from_rgba8(red, green, blue, f32::from(alpha) / 255.0) } @@ -2195,12 +2221,14 @@ impl EffectsBinding { Self::attach_role( surface, visible.then_some(ferese_surface_effects_v1::Role::Panel), + if visible { 1.0 } else { 0.0 }, ) } fn attach_role( surface: &wl_surface::WlSurface, role: Option, + opacity: f32, ) -> Result> { let backend = surface .backend() @@ -2212,13 +2240,14 @@ impl EffectsBinding { let manager = globals.bind::(&qh, 1..=3, ())?; let effects = manager.get_surface_effects(surface, &qh, ()); + let opacity = (opacity.clamp(0.0, 1.0) * 1000.0).round() as u32; + if effects.version() >= 3 { + effects.set_opacity(opacity); + } if let Some(role) = role { effects.set_role(role); } else { effects.clear_role(); - if effects.version() >= 3 { - effects.set_opacity(0); - } } connection.flush()?; @@ -2228,7 +2257,7 @@ impl EffectsBinding { surface: effects, _queue: queue, regions: Default::default(), - opacity: std::cell::Cell::new(None), + opacity: std::cell::Cell::new(Some(opacity)), }) } diff --git a/shell/ferese-shell/src/motion.rs b/shell/ferese-shell/src/motion.rs index 824289c..48641db 100644 --- a/shell/ferese-shell/src/motion.rs +++ b/shell/ferese-shell/src/motion.rs @@ -351,11 +351,13 @@ pub fn animated<'a, M: 'a>( content: Element<'a, M>, progress: f32, regions: Regions, + radius: f32, ) -> Element<'a, M> { Element::new(Motion { content, progress, regions, + radius, }) } @@ -363,6 +365,7 @@ struct Motion<'a, M> { content: Element<'a, M>, progress: f32, regions: Regions, + radius: f32, } impl Motion<'_, M> { @@ -442,7 +445,7 @@ impl Widget for Motion<'_, M> { (r.y - origin.y + translation.y).round() as i32, r.width.round() as i32, r.height.round() as i32, - 11, + self.radius.round().max(0.0) as i32, ] }) .collect(); diff --git a/shell/ferese-shell/src/status_ui.rs b/shell/ferese-shell/src/status_ui.rs index 1d178fa..97d1128 100644 --- a/shell/ferese-shell/src/status_ui.rs +++ b/shell/ferese-shell/src/status_ui.rs @@ -23,16 +23,25 @@ pub enum Menu { impl Menu { fn width(self) -> f32 { match self { - Self::System => 320.0, - Self::Network | Self::Bluetooth => 280.0, - Self::Battery => 250.0, - Self::Audio | Self::Notifications => 290.0, + Self::System => 360.0, + Self::Battery => 280.0, + _ => 300.0, } } pub(super) fn material_role(self) -> Option { - // Popovers contain independent cards, with no shared material backing. - None + Some(ferese_surface_effects_v1::Role::Popover) + } + + fn title(self) -> &'static str { + match self { + Self::System => "Control Center", + Self::Network => "Wi-Fi", + Self::Bluetooth => "Bluetooth", + Self::Audio => "Sound", + Self::Battery => "Battery", + Self::Notifications => "Notifications", + } } fn available(self, status: &Snapshot) -> bool { @@ -93,6 +102,28 @@ impl FereseShell { super::EFFECT_FRAME_PENDING.store(true, std::sync::atomic::Ordering::Relaxed); self.status_error = None; let parent = self.bar_surface_id; + let anchor = if kind == Menu::System { + self.outputs + .iter() + .find(|output| output.bar == parent) + .and_then(|output| output.size) + .map(|(width, _)| { + let theme = self.config.theme; + Rectangle { + x: (width + - theme.bar_margin_horizontal * 2 + - theme.panel_padding.round() as i32 + - 1) + .max(0), + y: 0, + width: 1, + height: theme.bar_height.round() as i32, + } + }) + .unwrap_or(anchor) + } else { + anchor + }; let action = cosmic::surface::action::app_popup::( |_| Default::default(), move |_| SctkPopupSettings { @@ -107,7 +138,7 @@ impl FereseShell { // Numeric protocol values avoid depending on libcosmic's private SCTK reexport. anchor: 2u32.try_into().unwrap(), // bottom gravity: 6u32.try_into().unwrap(), // bottom-left - offset: (anchor.width / 2, 4), + offset: (anchor.width / 2, 8), size_limits: Limits::NONE .min_width(kind.width()) .max_width(kind.width()) @@ -209,8 +240,15 @@ impl FereseShell { if !kind.available(&self.status) { continue; } - let (source, _) = status_icon(kind, &self.status); - let foreground = color(theme.text_primary); + let (source, enabled) = status_icon(kind, &self.status); + let selected = self.menu.as_ref().is_some_and(|m| m.kind == kind); + let foreground = color(if selected { + theme.accent + } else if enabled { + theme.text_primary + } else { + theme.text_muted + }); let mut content = row![accented_icon( source, metrics.icon_size, @@ -246,12 +284,11 @@ impl FereseShell { }), )); } - let selected = self.menu.as_ref().is_some_and(|m| m.kind == kind); // A fixed button height does not center its child in libcosmic. // Keep the visual content centered inside the entire click target. let content = container(content) .align_x(alignment::Horizontal::Center) - .height(metrics.height) + .height(metrics.control_height) .align_y(alignment::Vertical::Center); let control = button::custom(content) .name(status_label(kind, &self.status)) @@ -259,7 +296,7 @@ impl FereseShell { 0.0, ((metrics.height - f32::from(metrics.icon_size)) * 0.5).max(4.0), ]) - .height(metrics.height) + .height(metrics.control_height) .on_press_with_rectangle(move |offset, bounds| { cosmic::Action::App(Message::OpenMenu( kind, @@ -273,7 +310,10 @@ impl FereseShell { }); controls = controls.push(motion::button(control, foreground, selected, 1.0)); } - controls.into() + container(controls) + .padding([0, 3]) + .class(theme::Container::custom(move |_| bar_group_style(theme))) + .into() } fn view_status_menu(&self) -> Element<'_, cosmic::Action> { @@ -281,12 +321,42 @@ impl FereseShell { return text("").into(); }; let theme = self.config.theme; - // The compositor fades the complete surface, not individual caches. - let p = 1.0; + let p = if menu.effects.is_some() { + 1.0 + } else { + menu.progress() + }; let kind = menu.kind; let primary = color_with_opacity(theme.text_primary, p); let muted = color_with_opacity(theme.text_muted, p); - let mut rows = column::with_capacity(12).spacing(8).width(Length::Fill); + let badge = container(accented_icon( + status_icon(kind, &self.status).0, + 22, + primary, + color_with_opacity(theme.accent, p), + )) + .width(36) + .height(36) + .align_x(alignment::Horizontal::Center) + .align_y(alignment::Vertical::Center) + .class(theme::Container::custom(move |_| container::Style { + background: Some(Background::Color(color_with_opacity( + theme.accent, + 0.12 * p, + ))), + border: Border { + radius: 10.0.into(), + ..Default::default() + }, + ..Default::default() + })); + let heading = row![badge, text(kind.title()).size(18)] + .spacing(10) + .align_y(Alignment::Center); + let mut rows = column::with_capacity(12) + .push(heading) + .spacing(12) + .width(Length::Fill); if let Some(action) = &menu.confirm { let title = match action { Action::Poweroff => "Power off this computer?", @@ -577,7 +647,8 @@ impl FereseShell { } } if combined { - let mut actions = row::with_capacity(4).spacing(4); + let mut actions = column::with_capacity(2).spacing(8); + let mut power = row::with_capacity(3).spacing(6).width(Length::Fill); if self .config .status @@ -586,15 +657,15 @@ impl FereseShell { .and_then(|a| a.first()) .is_some_and(|p| status::available(p)) { - actions = actions.push(menu_button( - "Settings", + actions = actions.push(footer_button( + "Open Settings", Message::Control(Action::Settings), primary, p, )); } if self.status.poweroff { - actions = actions.push(menu_button( + power = power.push(footer_button( "Power off…", Message::ConfirmPower(Action::Poweroff), primary, @@ -602,7 +673,7 @@ impl FereseShell { )); } if self.status.reboot { - actions = actions.push(menu_button( + power = power.push(footer_button( "Restart…", Message::ConfirmPower(Action::Reboot), primary, @@ -610,14 +681,17 @@ impl FereseShell { )); } if self.status.suspend { - actions = actions.push(menu_button( + power = power.push(footer_button( "Suspend…", Message::ConfirmPower(Action::Suspend), primary, p, )); } - rows = rows.push(control_card(actions.wrap().into(), primary, p)); + if self.status.poweroff || self.status.reboot || self.status.suspend { + actions = actions.push(power); + } + rows = rows.push(control_card(actions.into(), primary, p)); } if !menu.kind.available(&self.status) { rows = rows.push( @@ -633,28 +707,32 @@ impl FereseShell { ..Color::from_rgb8(230, 172, 90) }))); } - let content: Element<'_, cosmic::Action> = if kind == Menu::System { - rows.into() - } else { - control_card(rows.into(), primary, p) - }; - let panel = - container(content) - .width(kind.width()) - .padding(12) - .class(theme::Container::custom(move |_| container::Style { - background: None, - text_color: Some(primary), - icon_color: Some(primary), - border: Border { - radius: theme.material_radius.into(), - ..Default::default() - }, + let compositor_material = menu.effects.is_some(); + let panel = container(rows) + .id("ferese-blur-card") + .width(kind.width()) + .padding(16) + .class(theme::Container::custom(move |_| container::Style { + background: (!compositor_material).then_some(Background::Color( + color_with_opacity(theme.surface_popover, p), + )), + text_color: Some(primary), + icon_color: Some(primary), + border: Border { + color: color_with_opacity(theme.text_muted, 0.18 * p), + width: 1.0, + radius: theme.material_radius.into(), ..Default::default() - })); - // Keep the outer surface transparent; only inner cards paint a background. + }, + ..Default::default() + })); cosmic::widget::autosize::autosize( - super::motion::animated(panel.into(), menu.progress(), menu.regions.clone()), + super::motion::animated( + panel.into(), + menu.progress(), + menu.regions.clone(), + theme.material_radius, + ), cosmic::iced::advanced::widget::Id::new("ferese-status-menu"), ) .limits( @@ -768,7 +846,6 @@ fn control_card<'a>( opacity: f32, ) -> Element<'a, cosmic::Action> { container(content) - .id("ferese-blur-card") .padding(10) .width(Length::Fill) .class(theme::Container::custom(move |_| container::Style { @@ -837,7 +914,6 @@ fn connection_control<'a>( // giving it a misleading clickable/disabled-button appearance. let Some(action) = action else { return container(content) - .id("ferese-blur-card") .width(Length::FillPortion(1)) .padding(4) .into(); @@ -851,7 +927,6 @@ fn connection_control<'a>( false, opacity, )) - .id("ferese-blur-card") .width(Length::FillPortion(1)) .into() } @@ -872,6 +947,23 @@ fn menu_button<'a>( ) } +fn footer_button<'a>( + label: &'a str, + message: Message, + foreground: Color, + opacity: f32, +) -> Element<'a, cosmic::Action> { + motion::button( + button::custom(text(label).size(12)) + .width(Length::Fill) + .padding([8, 6]) + .on_press(cosmic::Action::App(message)), + foreground, + false, + opacity, + ) +} + fn toggle_row<'a>( label: &'a str, on: bool, @@ -1081,12 +1173,12 @@ fn status_icon(kind: Menu, s: &Snapshot) -> (&'static [u8], bool) { mod tests { #[test] fn smaller_popups_have_content_specific_widths() { - assert_eq!(Menu::System.width(), 320.0); - assert_eq!(Menu::Network.width(), 280.0); - assert_eq!(Menu::Bluetooth.width(), 280.0); - assert_eq!(Menu::Battery.width(), 250.0); - assert_eq!(Menu::Audio.width(), 290.0); - assert_eq!(Menu::Notifications.width(), 290.0); + assert_eq!(Menu::System.width(), 360.0); + assert_eq!(Menu::Network.width(), 300.0); + assert_eq!(Menu::Bluetooth.width(), 300.0); + assert_eq!(Menu::Battery.width(), 280.0); + assert_eq!(Menu::Audio.width(), 300.0); + assert_eq!(Menu::Notifications.width(), 300.0); } #[test]