From 833c72976b3e8a02cd1a1c36475ca87800776954 Mon Sep 17 00:00:00 2001 From: Sulieman <168682205+sulabi@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:27:14 +0100 Subject: [PATCH 01/11] create the option for spacing in settings module --- src/config.rs | 17 +++++++++++++++++ src/modules/settings/mod.rs | 6 ++++-- src/theme.rs | 9 ++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index 3c5eb1383..529275167 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1033,6 +1033,20 @@ impl Default for MenuAppearance { } } +#[derive(Deserialize, Clone, Copy, Debug)] +#[serde(default)] +pub struct SettingsAppearance { + pub spacing: SpaceSize, +} + +impl Default for SettingsAppearance { + fn default() -> Self { + Self { + spacing: SpaceSize::Xs, + } + } +} + #[derive(Deserialize, Clone, Debug)] #[serde(default)] pub struct Appearance { @@ -1049,6 +1063,8 @@ pub struct Appearance { pub text_color: AppearanceColor, pub workspace_colors: Vec, pub special_workspace_colors: Option>, + + pub settings: SettingsAppearance, } static PRIMARY: HexColor = HexColor::rgb(122, 162, 247); @@ -1104,6 +1120,7 @@ impl Default for Appearance { scale_factor: 1.0, bar: BarAppearance::default(), menu: MenuAppearance::default(), + settings: SettingsAppearance::default(), background_color: BackgroundAppearanceColor::Complete { base: HexColor::rgb(26, 27, 38), weakest: None, diff --git a/src/modules/settings/mod.rs b/src/modules/settings/mod.rs index 4d9532e1c..656bea19d 100644 --- a/src/modules/settings/mod.rs +++ b/src/modules/settings/mod.rs @@ -754,7 +754,9 @@ impl Settings { } pub fn view<'a>(&'a self, id: SurfaceId) -> Element<'a, Message> { - let space = use_theme(|t| t.space); + let (theme_space, spacing_size) = use_theme(|t| (t.space, t.settings.spacing)); + let space = theme_space.resolve(spacing_size); + let mut row = Row::with_capacity(self.indicators.len()); for indicator in &self.indicators { @@ -868,7 +870,7 @@ impl Settings { } } - row.spacing(space.xs).into() + row.spacing(space).into() } pub fn subscription(&self) -> Subscription { diff --git a/src/theme.rs b/src/theme.rs index c18ed5e83..e2a6fdb03 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -4,7 +4,7 @@ use crate::{ components::button::{ButtonHierarchy, ButtonKind}, config::{ Appearance, AppearanceColor, BackgroundLevel, BarAppearance, BarMargin, BarRadius, - BarSurface, MenuAppearance, Position, RadiusSize, SpaceSize, + BarSurface, MenuAppearance, Position, RadiusSize, SettingsAppearance, SpaceSize, }, }; use iced::{ @@ -117,6 +117,7 @@ impl BarLayout { fn new(surface: BarSurface, margin: BarMargin) -> Self { let space = Space::default(); + Self { surface, margin: ( @@ -159,6 +160,9 @@ impl Default for FontSize { pub struct AshellTheme { pub iced_theme: Theme, pub space: Space, + + pub settings: SettingsAppearance, + pub radius: Radius, pub font_size: FontSize, pub bar_position: Position, @@ -303,6 +307,9 @@ fn base_theme_from_appearance( bar_margin: appearance.bar.margin, opacity: appearance.bar.opacity, menu: appearance.menu, + + settings: appearance.settings, + workspace_colors: appearance.workspace_colors.clone(), special_workspace_colors: appearance.special_workspace_colors.clone(), scale_factor: appearance.scale_factor, From 616013c5431801389510857b46634ef44078c009 Mon Sep 17 00:00:00 2001 From: Sulieman <168682205+sulabi@users.noreply.github.com> Date: Sat, 25 Jul 2026 22:08:12 +0100 Subject: [PATCH 02/11] apply spacing config for sys_info and workpaces module --- src/config.rs | 16 ++++++++++++---- src/modules/system_info.rs | 6 ++++-- src/modules/workspaces.rs | 5 ++++- src/theme.rs | 8 ++++++-- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/config.rs b/src/config.rs index 529275167..53e39fdc0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1035,11 +1035,11 @@ impl Default for MenuAppearance { #[derive(Deserialize, Clone, Copy, Debug)] #[serde(default)] -pub struct SettingsAppearance { +pub struct ModuleAppearance { pub spacing: SpaceSize, } -impl Default for SettingsAppearance { +impl Default for ModuleAppearance { fn default() -> Self { Self { spacing: SpaceSize::Xs, @@ -1064,7 +1064,9 @@ pub struct Appearance { pub workspace_colors: Vec, pub special_workspace_colors: Option>, - pub settings: SettingsAppearance, + pub settings: ModuleAppearance, + pub system_info: ModuleAppearance, + pub workspaces: ModuleAppearance, } static PRIMARY: HexColor = HexColor::rgb(122, 162, 247); @@ -1120,7 +1122,13 @@ impl Default for Appearance { scale_factor: 1.0, bar: BarAppearance::default(), menu: MenuAppearance::default(), - settings: SettingsAppearance::default(), + settings: ModuleAppearance::default(), + system_info: ModuleAppearance { + spacing: SpaceSize::Xxs, + }, + workspaces: ModuleAppearance { + spacing: SpaceSize::Xxs, + }, background_color: BackgroundAppearanceColor::Complete { base: HexColor::rgb(26, 27, 38), weakest: None, diff --git a/src/modules/system_info.rs b/src/modules/system_info.rs index 24c1a0d5e..2bd93350c 100644 --- a/src/modules/system_info.rs +++ b/src/modules/system_info.rs @@ -637,7 +637,9 @@ impl SystemInfo { } pub fn view(&'_ self) -> Element<'_, Message> { - let space = use_theme(|t| t.space); + let (theme_space, space_sizing) = use_theme(|t| (t.space, t.system_info.spacing)); + let space = theme_space.resolve(space_sizing); + let indicators = self.config.indicators.iter().filter_map(|i| match i { SystemInfoIndicator::Cpu => Some(Self::indicator_info_element( StaticIcon::Cpu, @@ -775,7 +777,7 @@ impl SystemInfo { Row::with_children(indicators) .align_y(Alignment::Center) - .spacing(space.xxs) + .spacing(space) .into() } diff --git a/src/modules/workspaces.rs b/src/modules/workspaces.rs index 75ce4ce46..c6ba4d43d 100644 --- a/src/modules/workspaces.rs +++ b/src/modules/workspaces.rs @@ -513,6 +513,9 @@ impl Workspaces { let monitor_name = outputs.get_monitor_name(id); let row = use_theme(|theme| { + let (theme_space, space_sizing) = (theme.space, theme.workspaces.spacing); + let space = theme_space.resolve(space_sizing); + Row::with_children( self.ui_workspaces .iter() @@ -685,7 +688,7 @@ impl Workspaces { }) .collect::>(), ) - .spacing(theme.space.xxs) + .spacing(space) }); let scroll_monitor = monitor_name.map(str::to_owned); diff --git a/src/theme.rs b/src/theme.rs index e2a6fdb03..08ed08877 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -4,7 +4,7 @@ use crate::{ components::button::{ButtonHierarchy, ButtonKind}, config::{ Appearance, AppearanceColor, BackgroundLevel, BarAppearance, BarMargin, BarRadius, - BarSurface, MenuAppearance, Position, RadiusSize, SettingsAppearance, SpaceSize, + BarSurface, MenuAppearance, ModuleAppearance, Position, RadiusSize, SpaceSize, }, }; use iced::{ @@ -161,7 +161,9 @@ pub struct AshellTheme { pub iced_theme: Theme, pub space: Space, - pub settings: SettingsAppearance, + pub settings: ModuleAppearance, + pub system_info: ModuleAppearance, + pub workspaces: ModuleAppearance, pub radius: Radius, pub font_size: FontSize, @@ -309,6 +311,8 @@ fn base_theme_from_appearance( menu: appearance.menu, settings: appearance.settings, + system_info: appearance.system_info, + workspaces: appearance.workspaces, workspace_colors: appearance.workspace_colors.clone(), special_workspace_colors: appearance.special_workspace_colors.clone(), From 61553a3dccb49fe4ef984634763521a378262142 Mon Sep 17 00:00:00 2001 From: Sulieman <168682205+sulabi@users.noreply.github.com> Date: Sun, 26 Jul 2026 12:33:23 +0100 Subject: [PATCH 03/11] add panel bar surface with configurable opacity, border --- src/app.rs | 47 ++++++++++++----------- src/components/menu.rs | 4 +- src/components/module_group.rs | 40 ++++++++++++++++++-- src/components/sub_menu_wrapper.rs | 5 ++- src/config.rs | 60 +++++++++++++++++++++++++----- src/modules/media_player.rs | 6 +-- src/modules/tempo/weather.rs | 10 ++--- src/outputs.rs | 2 +- src/theme.rs | 53 ++++++++++---------------- 9 files changed, 145 insertions(+), 82 deletions(-) diff --git a/src/app.rs b/src/app.rs index 831660edf..f08100358 100644 --- a/src/app.rs +++ b/src/app.rs @@ -577,38 +577,31 @@ impl App { let [left, center, right] = self.modules_section(id); - let (space, bar_surface, opacity, menu, animations_enabled, bar_radius) = - use_theme(|t| { - ( - t.space, - t.bar_surface, - t.opacity, - t.menu, - t.animations_enabled, - t.bar_border_radius(), - ) - }); + let (space, bar, menu, animations_enabled, theme_radius) = + use_theme(|t| (t.space, t.bar, t.menu, t.animations_enabled, t.radius)); + let (bar_surface, bar_bg_opacity, bar_border) = + (bar.surface, bar.opacity.background, bar.border); + let radius = bar_border.radius.resolve(theme_radius); + let centerbox = Centerbox::new([left, center, right]) .animated(animations_enabled) .spacing(space.xxs) .width(Length::Fill) .align_items(Alignment::Center) - .height(if bar_surface == BarSurface::Transparent { - HEIGHT - } else { - HEIGHT - space.xs as f64 + .height(match bar_surface { + BarSurface::Transparent | BarSurface::Panel => HEIGHT, + _ => HEIGHT - space.xs as f64, } as f32) - .padding(if bar_surface == BarSurface::Transparent { - [space.xxs, space.xxs] - } else { - [0.0, 0.0] + .padding(match bar_surface { + BarSurface::Transparent | BarSurface::Panel => [space.xxs, space.xxs], + _ => [0.0, 0.0], }); let menu_is_open = self.outputs.menu_is_open(); let status_bar = container(centerbox).style(move |t: &Theme| container::Style { background: match bar_surface { BarSurface::Solid => Some({ - let bg = t.palette().background.scale_alpha(opacity); + let bg = t.palette().background.scale_alpha(bar_bg_opacity); if menu_is_open { darken_color(bg, menu.backdrop) } else { @@ -623,10 +616,20 @@ impl App { None } } + BarSurface::Panel => Some({ + let bg = t.palette().background.scale_alpha(bar_bg_opacity); + if menu_is_open { + darken_color(bg, menu.backdrop) + } else { + bg + } + .into() + }), }, border: iced::Border { - radius: bar_radius, - ..Default::default() + radius, + color: bar_border.color.get_base(), + width: bar_border.width, }, ..Default::default() }); diff --git a/src/components/menu.rs b/src/components/menu.rs index 49c703945..a61651c0b 100644 --- a/src/components/menu.rs +++ b/src/components/menu.rs @@ -304,7 +304,7 @@ impl App { t.space, t.menu.opacity, t.radius, - t.bar_surface, + t.bar.surface, t.bar_position, t.menu.backdrop, ) @@ -334,7 +334,7 @@ impl App { .padding({ let v_padding = match bar_surface { BarSurface::Solid => 2, - BarSurface::Transparent => 0, + BarSurface::Transparent | BarSurface::Panel => 0, }; Padding::new(0.) diff --git a/src/components/module_group.rs b/src/components/module_group.rs index 8437094ef..48728a45d 100644 --- a/src/components/module_group.rs +++ b/src/components/module_group.rs @@ -5,22 +5,54 @@ use iced::{Border, Color, Element, widget::container}; /// /// - `Solid` → pass through as-is (the bar itself carries the background) /// - `Transparent` → wrap in a container with background color + rounded border +/// - `Panel` → wrap in a container with customisation from config pub fn module_group<'a, Msg: 'static>(content: Element<'a, Msg>) -> Element<'a, Msg> { - let (bar_surface, opacity, radius) = - use_theme(|theme| (theme.bar_surface, theme.opacity, theme.radius)); + let (bar_surface, theme_radius, module_opacity, module_border) = use_theme(|theme| { + ( + theme.bar.surface, + theme.radius, + theme.bar.opacity.module, + theme.bar.module_border, + ) + }); + + let radius = module_border.radius.resolve(theme_radius); match bar_surface { BarSurface::Solid => content, BarSurface::Transparent => container(content) .style(move |iced_theme: &iced::Theme| container::Style { - background: Some(iced_theme.palette().background.scale_alpha(opacity).into()), + background: Some( + iced_theme + .palette() + .background + .scale_alpha(module_opacity) + .into(), + ), border: Border { width: 0.0, - radius: radius.lg.into(), + radius, color: Color::TRANSPARENT, }, ..container::Style::default() }) .into(), + BarSurface::Panel => container(content) + .style(move |iced_theme: &iced::Theme| container::Style { + background: Some( + iced_theme + .palette() + .background + .scale_alpha(module_opacity) + .into(), + ), + border: Border { + width: module_border.width, + radius, + color: module_border.color.get_base(), + }, + ..container::Style::default() + }) + .into(), } } diff --git a/src/components/sub_menu_wrapper.rs b/src/components/sub_menu_wrapper.rs index 07f52ddd4..74fc6c329 100644 --- a/src/components/sub_menu_wrapper.rs +++ b/src/components/sub_menu_wrapper.rs @@ -2,7 +2,8 @@ use crate::theme::use_theme; use iced::{Background, Border, Element, Length, Theme, widget::container}; pub fn sub_menu_wrapper<'a, Msg: 'static>(content: Element<'a, Msg>) -> Element<'a, Msg> { - let (opacity, radius, space) = use_theme(|theme| (theme.opacity, theme.radius, theme.space)); + let (bg_opacity, radius, space) = + use_theme(|theme| (theme.menu.opacity, theme.radius, theme.space)); container(content) .style(move |theme: &Theme| container::Style { @@ -12,7 +13,7 @@ pub fn sub_menu_wrapper<'a, Msg: 'static>(content: Element<'a, Msg>) -> Element< .background .weak .color - .scale_alpha(opacity), + .scale_alpha(bg_opacity), ) .into(), border: Border::default().rounded(radius.lg), diff --git a/src/config.rs b/src/config.rs index 53e39fdc0..beac039a9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use crate::app::Message; use crate::i18n::{UnitSystem, unit_system}; use crate::services::upower::PeripheralDeviceKind; +use crate::theme::Radius; use crate::utils::celsius_to_fahrenheit; use hex_color::HexColor; use iced::futures::StreamExt; @@ -899,6 +900,7 @@ pub enum BarSurface { #[default] Transparent, Solid, + Panel, } #[derive(Deserialize, Default, Copy, Clone, Eq, PartialEq, Debug)] @@ -972,6 +974,17 @@ impl<'de> Deserialize<'de> for BarRadius { } } +impl BarRadius { + pub fn resolve(&self, scale: Radius) -> iced::border::Radius { + iced::border::Radius { + top_left: scale.resolve(self.top_left), + top_right: scale.resolve(self.top_right), + bottom_left: scale.resolve(self.bottom_left), + bottom_right: scale.resolve(self.bottom_right), + } + } +} + /// Per-edge margin selection, deserialized with CSS `margin` shorthand: /// 1 value = all edges, 2 = `[vertical, horizontal]`, 4 = `[top, right, bottom, left]`. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] @@ -995,27 +1008,54 @@ impl<'de> Deserialize<'de> for BarMargin { } } -#[derive(Deserialize, Clone, Copy, Debug, PartialEq)] +#[derive(Deserialize, Clone, Copy, Debug)] #[serde(default)] -pub struct BarAppearance { - pub surface: BarSurface, - #[serde(deserialize_with = "opacity_deserializer")] - pub opacity: f32, +pub struct BorderAppearance { pub radius: BarRadius, - pub margin: BarMargin, + pub width: f32, + pub color: AppearanceColor, } -impl Default for BarAppearance { +impl Default for BorderAppearance { fn default() -> Self { Self { - surface: BarSurface::default(), - opacity: default_opacity(), radius: BarRadius::default(), - margin: BarMargin::default(), + width: 0f32, + color: AppearanceColor::Simple(HexColor::default()), } } } +#[derive(Deserialize, Clone, Copy, Debug)] +#[serde(default)] +pub struct OpacityAppearance { + #[serde(deserialize_with = "opacity_deserializer")] + pub button: f32, + #[serde(deserialize_with = "opacity_deserializer")] + pub background: f32, + #[serde(deserialize_with = "opacity_deserializer")] + pub module: f32, +} +impl Default for OpacityAppearance { + fn default() -> Self { + Self { + button: default_opacity(), + background: default_opacity(), + module: default_opacity(), + } + } +} + +#[derive(Default, Deserialize, Clone, Copy, Debug)] +#[serde(default)] +pub struct BarAppearance { + pub surface: BarSurface, + pub opacity: OpacityAppearance, + pub module_border: BorderAppearance, + pub border: BorderAppearance, + pub margin: BarMargin, +} + #[derive(Deserialize, Clone, Copy, Debug)] #[serde(default)] pub struct MenuAppearance { diff --git a/src/modules/media_player.rs b/src/modules/media_player.rs index 646151f7d..03d574ab4 100644 --- a/src/modules/media_player.rs +++ b/src/modules/media_player.rs @@ -231,11 +231,11 @@ impl MediaPlayer { } pub fn menu_view<'a>(&'a self, is_closing: bool) -> Element<'a, Message> { - let (space, font_size, opacity, radius, palette) = use_theme(|theme| { + let (space, font_size, bg_opacity, radius, palette) = use_theme(|theme| { ( theme.space, theme.font_size, - theme.opacity, + theme.menu.opacity, theme.radius, theme.iced_theme.palette(), ) @@ -390,7 +390,7 @@ impl MediaPlayer { .background .weak .color - .scale_alpha(opacity), + .scale_alpha(bg_opacity), ) .into(), border: Border::default().rounded(radius.lg), diff --git a/src/modules/tempo/weather.rs b/src/modules/tempo/weather.rs index 64cd49ed8..d3b48b357 100644 --- a/src/modules/tempo/weather.rs +++ b/src/modules/tempo/weather.rs @@ -21,8 +21,8 @@ use super::{Message, Tempo}; impl Tempo { pub(super) fn weather<'a>(&'a self) -> Option> { - let (space, font_size, opacity, radius) = - use_theme(|t| (t.space, t.font_size, t.opacity, t.radius)); + let (space, font_size, bg_opacity, radius) = + use_theme(|t| (t.space, t.font_size, t.menu.opacity, t.radius)); let locale = chrono_locale(); let units = unit_system(); let temp = units.temperature_symbol(); @@ -152,7 +152,7 @@ impl Tempo { .background .weak .color - .scale_alpha(opacity), + .scale_alpha(bg_opacity), ) .into(), border: Border::default().rounded(radius.lg), @@ -215,7 +215,7 @@ impl Tempo { .background .weak .color - .scale_alpha(opacity), + .scale_alpha(bg_opacity), ) .into(), border: Border::default().rounded(radius.lg), @@ -283,7 +283,7 @@ impl Tempo { .background .weak .color - .scale_alpha(opacity), + .scale_alpha(bg_opacity), ) .into(), border: Border::default().rounded(iced::border::Radius { diff --git a/src/outputs.rs b/src/outputs.rs index 73901a8f7..637ca00a5 100644 --- a/src/outputs.rs +++ b/src/outputs.rs @@ -145,7 +145,7 @@ impl Outputs { (HEIGHT - match surface { BarSurface::Solid => 8., - BarSurface::Transparent => 0., + BarSurface::Transparent | BarSurface::Panel => 0., }) * scale_factor } diff --git a/src/theme.rs b/src/theme.rs index 08ed08877..b90247247 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -3,12 +3,12 @@ use std::cell::RefCell; use crate::{ components::button::{ButtonHierarchy, ButtonKind}, config::{ - Appearance, AppearanceColor, BackgroundLevel, BarAppearance, BarMargin, BarRadius, - BarSurface, MenuAppearance, ModuleAppearance, Position, RadiusSize, SpaceSize, + Appearance, AppearanceColor, BackgroundLevel, BarAppearance, BarMargin, BarSurface, + MenuAppearance, ModuleAppearance, Position, RadiusSize, SpaceSize, }, }; use iced::{ - Background, Border, Color, Theme, border, + Background, Border, Color, Theme, theme::{Palette, palette}, widget::{ button::{self, Status}, @@ -165,13 +165,11 @@ pub struct AshellTheme { pub system_info: ModuleAppearance, pub workspaces: ModuleAppearance, + pub bar: BarAppearance, + pub radius: Radius, pub font_size: FontSize, pub bar_position: Position, - pub bar_surface: BarSurface, - pub bar_radius: BarRadius, - pub bar_margin: BarMargin, - pub opacity: f32, pub menu: MenuAppearance, pub workspace_colors: Vec, pub special_workspace_colors: Option>, @@ -304,10 +302,8 @@ fn base_theme_from_appearance( radius: Radius::default(), font_size: FontSize::default(), bar_position, - bar_surface: appearance.bar.surface, - bar_radius: appearance.bar.radius, - bar_margin: appearance.bar.margin, - opacity: appearance.bar.opacity, + + bar: appearance.bar, menu: appearance.menu, settings: appearance.settings, @@ -332,16 +328,7 @@ impl AshellTheme { } pub fn bar_layout(&self) -> BarLayout { - BarLayout::new(self.bar_surface, self.bar_margin) - } - - pub fn bar_border_radius(&self) -> border::Radius { - border::Radius { - top_left: self.radius.resolve(self.bar_radius.top_left), - top_right: self.radius.resolve(self.bar_radius.top_right), - bottom_right: self.radius.resolve(self.bar_radius.bottom_right), - bottom_left: self.radius.resolve(self.bar_radius.bottom_left), - } + BarLayout::new(self.bar.surface, self.bar.margin) } pub fn button_style( @@ -353,7 +340,7 @@ impl AshellTheme { ButtonKind::Transparent => self.radius.sm, ButtonKind::Solid | ButtonKind::Outline => self.radius.xl, }; - let opacity = self.opacity; + let btn_opacity = self.bar.opacity.button; move |theme: &Theme, status: Status| { let palette = theme.palette(); @@ -385,7 +372,7 @@ impl AshellTheme { match (kind, status) { (ButtonKind::Solid, Status::Active) => button::Style { - background: Some(base_bg.scale_alpha(opacity).into()), + background: Some(base_bg.scale_alpha(btn_opacity).into()), border: Border { width: 0.0, radius: radius.into(), @@ -395,7 +382,7 @@ impl AshellTheme { ..button::Style::default() }, (ButtonKind::Solid, Status::Hovered) => button::Style { - background: Some(hover_bg.scale_alpha(opacity).into()), + background: Some(hover_bg.scale_alpha(btn_opacity).into()), border: Border { width: 0.0, radius: radius.into(), @@ -449,7 +436,7 @@ impl AshellTheme { ..button::Style::default() }, (ButtonKind::Outline, Status::Hovered) => button::Style { - background: Some(base_bg.scale_alpha(opacity).into()), + background: Some(base_bg.scale_alpha(btn_opacity).into()), border: Border { width: 2.0, radius: radius.into(), @@ -464,7 +451,7 @@ impl AshellTheme { match kind { ButtonKind::Solid => button::Style { background: Some( - base_bg.scale_alpha(opacity * disabled_opacity).into(), + base_bg.scale_alpha(btn_opacity * disabled_opacity).into(), ), border: Border { width: 0.0, @@ -516,7 +503,7 @@ impl AshellTheme { active: f32, ) -> impl Fn(&Theme, Status) -> button::Style + use<> { let radius_lg = self.radius.lg; - let opacity = self.opacity; + let bg_opacity = self.bar.opacity.background; move |theme: &Theme, status: Status| { let mut base = button::Style { background: None, @@ -541,7 +528,7 @@ impl AshellTheme { .background .weak .color - .scale_alpha(opacity) + .scale_alpha(bg_opacity) .into(), ); base.text_color = theme.palette().text; @@ -556,12 +543,12 @@ impl AshellTheme { &self, active: f32, ) -> impl Fn(&Theme, Status) -> button::Style + use<> { - let opacity = self.opacity; + let bg_opacity = self.bar.opacity.background; let radius = self.radius.xl; move |theme: &Theme, status: Status| { let inactive_bg = theme.extended_palette().background.weak.color; let active_bg = theme.palette().primary; - let bg = lerp_color(inactive_bg, active_bg, active).scale_alpha(opacity); + let bg = lerp_color(inactive_bg, active_bg, active).scale_alpha(bg_opacity); let mut base = button::Style { background: Some(bg.into()), @@ -584,7 +571,7 @@ impl AshellTheme { let active_hover = theme.extended_palette().primary.weak.color; base.background = Some( lerp_color(inactive_hover, active_hover, active) - .scale_alpha(opacity) + .scale_alpha(bg_opacity) .into(), ); base @@ -740,7 +727,7 @@ impl AshellTheme { /// The module-group background is handled by `module_group`, not the button. pub fn module_button_style(&self) -> impl Fn(&Theme, Status) -> button::Style + use<> { let radius_lg = self.radius.lg; - let opacity = self.opacity; + let btn_opacity = self.bar.opacity.button; move |theme, status| { let mut base = button::Style { background: None, @@ -761,7 +748,7 @@ impl AshellTheme { .background .weak .color - .scale_alpha(opacity) + .scale_alpha(btn_opacity) .into(), ); base From 23d2a544d5de4594a6ba59866b6c46f7d93048f5 Mon Sep 17 00:00:00 2001 From: Sulieman <168682205+sulabi@users.noreply.github.com> Date: Sun, 26 Jul 2026 19:24:10 +0100 Subject: [PATCH 04/11] add grouping and allow members to be individual --- src/components/module_group.rs | 19 ++- src/config.rs | 43 +++-- src/modules/mod.rs | 59 +++++-- src/modules/settings/mod.rs | 7 +- src/modules/system_info.rs | 288 ++++++++++++++++++--------------- src/modules/workspaces.rs | 10 +- src/theme.rs | 14 +- 7 files changed, 256 insertions(+), 184 deletions(-) diff --git a/src/components/module_group.rs b/src/components/module_group.rs index 48728a45d..b7a99c0b1 100644 --- a/src/components/module_group.rs +++ b/src/components/module_group.rs @@ -7,14 +7,16 @@ use iced::{Border, Color, Element, widget::container}; /// - `Transparent` → wrap in a container with background color + rounded border /// - `Panel` → wrap in a container with customisation from config pub fn module_group<'a, Msg: 'static>(content: Element<'a, Msg>) -> Element<'a, Msg> { - let (bar_surface, theme_radius, module_opacity, module_border) = use_theme(|theme| { - ( - theme.bar.surface, - theme.radius, - theme.bar.opacity.module, - theme.bar.module_border, - ) - }); + let (bar_surface, theme_radius, module_opacity, module_border, module_padding) = + use_theme(|theme| { + ( + theme.bar.surface, + theme.radius, + theme.bar.opacity.module, + theme.bar.module_border, + theme.space.xxs, + ) + }); let radius = module_border.radius.resolve(theme_radius); @@ -38,6 +40,7 @@ pub fn module_group<'a, Msg: 'static>(content: Element<'a, Msg>) -> Element<'a, }) .into(), BarSurface::Panel => container(content) + .padding(module_padding) .style(move |iced_theme: &iced::Theme| container::Style { background: Some( iced_theme diff --git a/src/config.rs b/src/config.rs index beac039a9..178849771 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1073,16 +1073,27 @@ impl Default for MenuAppearance { } } -#[derive(Deserialize, Clone, Copy, Debug)] +#[derive(Deserialize, Default, Clone, Copy, Debug, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ModuleGroup { + #[default] + Combined, // modules and members + Individual, // only members (have its own container) + None, // no containers +} + +#[derive(Deserialize, Clone, Debug)] #[serde(default)] pub struct ModuleAppearance { pub spacing: SpaceSize, + pub grouping: ModuleGroup, } impl Default for ModuleAppearance { fn default() -> Self { Self { spacing: SpaceSize::Xs, + grouping: ModuleGroup::default(), } } } @@ -1104,9 +1115,7 @@ pub struct Appearance { pub workspace_colors: Vec, pub special_workspace_colors: Option>, - pub settings: ModuleAppearance, - pub system_info: ModuleAppearance, - pub workspaces: ModuleAppearance, + pub modules: HashMap, } static PRIMARY: HexColor = HexColor::rgb(122, 162, 247); @@ -1162,13 +1171,23 @@ impl Default for Appearance { scale_factor: 1.0, bar: BarAppearance::default(), menu: MenuAppearance::default(), - settings: ModuleAppearance::default(), - system_info: ModuleAppearance { - spacing: SpaceSize::Xxs, - }, - workspaces: ModuleAppearance { - spacing: SpaceSize::Xxs, - }, + modules: HashMap::from([ + (ModuleName::Settings, ModuleAppearance::default()), + ( + ModuleName::SystemInfo, + ModuleAppearance { + spacing: SpaceSize::Xxs, + ..Default::default() + }, + ), + ( + ModuleName::Workspaces, + ModuleAppearance { + spacing: SpaceSize::Xxs, + ..Default::default() + }, + ), + ]), background_color: BackgroundAppearanceColor::Complete { base: HexColor::rgb(26, 27, 38), weakest: None, @@ -1209,7 +1228,7 @@ pub enum Layer { Overlay, } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum ModuleName { Updates, Workspaces, diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 85f63bfc2..77c0e490c 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -1,9 +1,7 @@ use crate::{ app::{App, Message}, - components::animated_size, - components::menu::MenuType, - components::{module_group, module_item}, - config::{ModuleDef, ModuleName}, + components::{animated_size, menu::MenuType, module_group, module_item}, + config::{ModuleAppearance, ModuleDef, ModuleGroup, ModuleName}, theme::use_theme, }; use iced::{Alignment, Element, Length, Subscription, SurfaceId, widget::Row}; @@ -158,8 +156,21 @@ impl App { id: SurfaceId, module_name: &'a ModuleName, ) -> Option> { + let module_appearances = use_theme(|t| t.modules.clone()); + let grouping = module_appearances + .get(module_name) + .unwrap_or(&ModuleAppearance::default()) + .grouping; + self.get_module_view(id, module_name) - .map(|(content, action)| module_group(self.build_module_item(id, content, action))) + .map(|(content, action)| { + let item = self.build_module_item(id, content, action); + + match grouping { + ModuleGroup::None | ModuleGroup::Individual => item, + ModuleGroup::Combined => module_group(item), + } + }) } fn group_module_wrapper<'a>( @@ -167,22 +178,36 @@ impl App { id: SurfaceId, group: &'a [ModuleName], ) -> Option> { - let modules: Vec<_> = group + let module_appearances = use_theme(|t| t.modules.clone()); + + let module_items: Vec<_> = group .iter() - .filter_map(|module| self.get_module_view(id, module)) + .filter_map(|module| self.get_module_view(id, module).map(|view| (module, view))) .collect(); - if modules.is_empty() { - None - } else { - let items = Row::with_children( - modules - .into_iter() - .map(|(content, action)| self.build_module_item(id, content, action)) - .collect::>(), - ); - Some(module_group(items.into())) + if module_items.is_empty() { + return None; } + + let items = module_items + .into_iter() + .map(|(module_name, (content, action))| { + let item = self.build_module_item(id, content, action); + let grouping = module_appearances + .get(module_name) + .unwrap_or(&ModuleAppearance::default()) + .grouping; + + match grouping { + ModuleGroup::None | ModuleGroup::Individual => item, + ModuleGroup::Combined => module_group(item), + } + }) + .collect::>(); + + let row = Row::with_children(items); + + Some(row.into()) } fn get_module_view<'a>( diff --git a/src/modules/settings/mod.rs b/src/modules/settings/mod.rs index 656bea19d..bbe4f6e9d 100644 --- a/src/modules/settings/mod.rs +++ b/src/modules/settings/mod.rs @@ -13,7 +13,7 @@ use crate::{ menu::MenuType, password_dialog, position_button, quick_setting_button, sub_menu_wrapper, }, - config::{Position, SettingsCustomButton, SettingsIndicator, SettingsModuleConfig}, + config::{ModuleName, Position, SettingsCustomButton, SettingsIndicator, SettingsModuleConfig}, modules::settings::{ audio::{AudioSettings, AudioSettingsConfig}, bluetooth::{BluetoothSettings, BluetoothSettingsConfig}, @@ -754,7 +754,10 @@ impl Settings { } pub fn view<'a>(&'a self, id: SurfaceId) -> Element<'a, Message> { - let (theme_space, spacing_size) = use_theme(|t| (t.space, t.settings.spacing)); + let (theme_space, spacing_size) = use_theme(|t| { + let module_appearance = t.modules.get(&ModuleName::Settings).unwrap_or(&t.module); + (t.space, module_appearance.spacing) + }); let space = theme_space.resolve(spacing_size); let mut row = Row::with_capacity(self.indicators.len()); diff --git a/src/modules/system_info.rs b/src/modules/system_info.rs index 2bd93350c..c53d199de 100644 --- a/src/modules/system_info.rs +++ b/src/modules/system_info.rs @@ -1,10 +1,12 @@ use crate::{ - components::MenuSize, - components::divider, - components::icons::{StaticIcon, icon}, + components::{ + MenuSize, divider, + icons::{StaticIcon, icon}, + module_group, + }, config::{ - CpuFormat, DiskFormat, MemoryFormat, SystemInfoIndicator, SystemInfoModuleConfig, - SystemInfoTemperature, TemperatureSensor, TemperatureSensorType, + CpuFormat, DiskFormat, MemoryFormat, ModuleGroup, ModuleName, SystemInfoIndicator, + SystemInfoModuleConfig, SystemInfoTemperature, TemperatureSensor, TemperatureSensorType, }, i18n::{UnitSystem, unit_system}, t, @@ -637,142 +639,160 @@ impl SystemInfo { } pub fn view(&'_ self) -> Element<'_, Message> { - let (theme_space, space_sizing) = use_theme(|t| (t.space, t.system_info.spacing)); + let (theme_space, space_sizing, grouping) = use_theme(|t| { + let module_appearance = t.modules.get(&ModuleName::SystemInfo).unwrap_or(&t.module); + ( + t.space, + module_appearance.spacing, + module_appearance.grouping, + ) + }); + let space = theme_space.resolve(space_sizing); - let indicators = self.config.indicators.iter().filter_map(|i| match i { - SystemInfoIndicator::Cpu => Some(Self::indicator_info_element( - StaticIcon::Cpu, - match self.config.cpu.format { - CpuFormat::Percentage => (self.data.cpu_usage.percentage.to_string(), "%"), - CpuFormat::Frequency => (self.data.cpu_usage.frequency.to_string(), " GHz"), - }, - Some(( - self.data.cpu_usage.percentage, - self.config.cpu.warn_threshold, - self.config.cpu.alert_threshold, - )), - None, - )), - - SystemInfoIndicator::Memory => Some(Self::indicator_info_element( - StaticIcon::Mem, - match self.config.memory.format { - MemoryFormat::Percentage => { - (self.data.memory_usage.percentage.to_string(), "%") - } - MemoryFormat::Fraction => (self.data.memory_usage.fraction.clone(), " GiB"), - }, - Some(( - self.data.memory_usage.percentage, - self.config.memory.warn_threshold, - self.config.memory.alert_threshold, - )), - None, - )), - - SystemInfoIndicator::MemorySwap => Some(Self::indicator_info_element( - StaticIcon::Mem, - match self.config.memory.format { - MemoryFormat::Percentage => { - (self.data.memory_swap_usage.percentage.to_string(), "%") - } - MemoryFormat::Fraction => { - (self.data.memory_swap_usage.fraction.clone(), " GiB") - } - }, - Some(( - self.data.memory_swap_usage.percentage, - self.config.memory.warn_threshold, - self.config.memory.alert_threshold, - )), - Some(t!("system-info-swap-indicator-prefix")), - )), - - SystemInfoIndicator::Temperature => self.data.temperature.celsius.map(|cel| { - let units = unit_system(); - let temp_value = match units { - UnitSystem::Metric => cel, - UnitSystem::Imperial => utils::celsius_to_fahrenheit(cel), - }; - Self::indicator_info_element( - StaticIcon::Temp, - (temp_value, units.temperature_symbol()), + let indicators = self.config.indicators.iter().filter_map(|i| { + let element = match i { + SystemInfoIndicator::Cpu => Some(Self::indicator_info_element( + StaticIcon::Cpu, + match self.config.cpu.format { + CpuFormat::Percentage => (self.data.cpu_usage.percentage.to_string(), "%"), + CpuFormat::Frequency => (self.data.cpu_usage.frequency.to_string(), " GHz"), + }, Some(( - temp_value, - self.config.temperature.warn_threshold(), - self.config.temperature.alert_threshold(), + self.data.cpu_usage.percentage, + self.config.cpu.warn_threshold, + self.config.cpu.alert_threshold, )), None, - ) - }), - SystemInfoIndicator::Disk(config) => { - self.data.disks.iter().find_map(|(disk_mount, disk)| { - if disk_mount == &config.path { - Some(Self::indicator_info_element( - StaticIcon::Drive, - match self.config.disk.format { - DiskFormat::Percentage => (disk.percentage.to_string(), "%"), - DiskFormat::Fraction => (disk.fraction.clone(), " GB"), - }, - Some(( - disk.percentage, - self.config.disk.warn_threshold, - self.config.disk.alert_threshold, - )), - Some(config.name.as_deref().unwrap_or(disk_mount).to_string()), - )) - } else { - None - } - }) - } - SystemInfoIndicator::IpAddress => self.data.network.as_ref().map(|network| { - Self::indicator_info_element( - StaticIcon::IpAddress, - (network.ip.to_string(), ""), - None::<(u32, u32, u32)>, - None, - ) - }), - SystemInfoIndicator::DownloadSpeed => self.data.network.as_ref().map(|network| { - Self::indicator_info_element( - StaticIcon::DownloadSpeed, - ( - if network.download_speed > 1000 { - network.download_speed / 1000 - } else { - network.download_speed - }, - if network.download_speed > 1000 { - "MB/s" - } else { - "KB/s" - }, - ), - None::<(u32, u32, u32)>, + )), + + SystemInfoIndicator::Memory => Some(Self::indicator_info_element( + StaticIcon::Mem, + match self.config.memory.format { + MemoryFormat::Percentage => { + (self.data.memory_usage.percentage.to_string(), "%") + } + MemoryFormat::Fraction => (self.data.memory_usage.fraction.clone(), " GiB"), + }, + Some(( + self.data.memory_usage.percentage, + self.config.memory.warn_threshold, + self.config.memory.alert_threshold, + )), None, - ) - }), - SystemInfoIndicator::UploadSpeed => self.data.network.as_ref().map(|network| { - Self::indicator_info_element( - StaticIcon::UploadSpeed, - ( - if network.upload_speed > 1000 { - network.upload_speed / 1000 - } else { - network.upload_speed - }, - if network.upload_speed > 1000 { - "MB/s" + )), + + SystemInfoIndicator::MemorySwap => Some(Self::indicator_info_element( + StaticIcon::Mem, + match self.config.memory.format { + MemoryFormat::Percentage => { + (self.data.memory_swap_usage.percentage.to_string(), "%") + } + MemoryFormat::Fraction => { + (self.data.memory_swap_usage.fraction.clone(), " GiB") + } + }, + Some(( + self.data.memory_swap_usage.percentage, + self.config.memory.warn_threshold, + self.config.memory.alert_threshold, + )), + Some(t!("system-info-swap-indicator-prefix")), + )), + + SystemInfoIndicator::Temperature => self.data.temperature.celsius.map(|cel| { + let units = unit_system(); + let temp_value = match units { + UnitSystem::Metric => cel, + UnitSystem::Imperial => utils::celsius_to_fahrenheit(cel), + }; + Self::indicator_info_element( + StaticIcon::Temp, + (temp_value, units.temperature_symbol()), + Some(( + temp_value, + self.config.temperature.warn_threshold(), + self.config.temperature.alert_threshold(), + )), + None, + ) + }), + SystemInfoIndicator::Disk(config) => { + self.data.disks.iter().find_map(|(disk_mount, disk)| { + if disk_mount == &config.path { + Some(Self::indicator_info_element( + StaticIcon::Drive, + match self.config.disk.format { + DiskFormat::Percentage => (disk.percentage.to_string(), "%"), + DiskFormat::Fraction => (disk.fraction.clone(), " GB"), + }, + Some(( + disk.percentage, + self.config.disk.warn_threshold, + self.config.disk.alert_threshold, + )), + Some(config.name.as_deref().unwrap_or(disk_mount).to_string()), + )) } else { - "KB/s" - }, - ), - None::<(u32, u32, u32)>, - None, - ) - }), + None + } + }) + } + SystemInfoIndicator::IpAddress => self.data.network.as_ref().map(|network| { + Self::indicator_info_element( + StaticIcon::IpAddress, + (network.ip.to_string(), ""), + None::<(u32, u32, u32)>, + None, + ) + }), + SystemInfoIndicator::DownloadSpeed => self.data.network.as_ref().map(|network| { + Self::indicator_info_element( + StaticIcon::DownloadSpeed, + ( + if network.download_speed > 1000 { + network.download_speed / 1000 + } else { + network.download_speed + }, + if network.download_speed > 1000 { + "MB/s" + } else { + "KB/s" + }, + ), + None::<(u32, u32, u32)>, + None, + ) + }), + SystemInfoIndicator::UploadSpeed => self.data.network.as_ref().map(|network| { + Self::indicator_info_element( + StaticIcon::UploadSpeed, + ( + if network.upload_speed > 1000 { + network.upload_speed / 1000 + } else { + network.upload_speed + }, + if network.upload_speed > 1000 { + "MB/s" + } else { + "KB/s" + }, + ), + None::<(u32, u32, u32)>, + None, + ) + }), + }; + + element.map(|el| { + if grouping == ModuleGroup::Individual { + module_group(el) + } else { + el + } + }) }); Row::with_children(indicators) diff --git a/src/modules/workspaces.rs b/src/modules/workspaces.rs index c6ba4d43d..811c5963c 100644 --- a/src/modules/workspaces.rs +++ b/src/modules/workspaces.rs @@ -1,8 +1,8 @@ use crate::{ components::icons::icon, config::{ - AppearanceColor, InvertScrollDirection, WorkspaceIndicatorFormat, WorkspaceVisibilityMode, - WorkspacesModuleConfig, + AppearanceColor, InvertScrollDirection, ModuleName, WorkspaceIndicatorFormat, + WorkspaceVisibilityMode, WorkspacesModuleConfig, }, outputs::Outputs, services::{ @@ -513,7 +513,11 @@ impl Workspaces { let monitor_name = outputs.get_monitor_name(id); let row = use_theme(|theme| { - let (theme_space, space_sizing) = (theme.space, theme.workspaces.spacing); + let module_appearance = theme + .modules + .get(&ModuleName::Workspaces) + .unwrap_or(&theme.module); + let (theme_space, space_sizing) = (theme.space, module_appearance.spacing); let space = theme_space.resolve(space_sizing); Row::with_children( diff --git a/src/theme.rs b/src/theme.rs index b90247247..892a1a0f4 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,10 +1,10 @@ -use std::cell::RefCell; +use std::{cell::RefCell, collections::HashMap}; use crate::{ components::button::{ButtonHierarchy, ButtonKind}, config::{ Appearance, AppearanceColor, BackgroundLevel, BarAppearance, BarMargin, BarSurface, - MenuAppearance, ModuleAppearance, Position, RadiusSize, SpaceSize, + MenuAppearance, ModuleAppearance, ModuleName, Position, RadiusSize, SpaceSize, }, }; use iced::{ @@ -161,9 +161,8 @@ pub struct AshellTheme { pub iced_theme: Theme, pub space: Space, - pub settings: ModuleAppearance, - pub system_info: ModuleAppearance, - pub workspaces: ModuleAppearance, + pub modules: HashMap, + pub module: ModuleAppearance, pub bar: BarAppearance, @@ -306,9 +305,8 @@ fn base_theme_from_appearance( bar: appearance.bar, menu: appearance.menu, - settings: appearance.settings, - system_info: appearance.system_info, - workspaces: appearance.workspaces, + modules: appearance.modules.clone(), + module: ModuleAppearance::default(), workspace_colors: appearance.workspace_colors.clone(), special_workspace_colors: appearance.special_workspace_colors.clone(), From 371649d6c4903c934b62937319bd68d9220e0054 Mon Sep 17 00:00:00 2001 From: Sulieman <168682205+sulabi@users.noreply.github.com> Date: Sun, 26 Jul 2026 21:07:11 +0100 Subject: [PATCH 05/11] fix hover background for individual members --- src/components/module_group.rs | 20 ++++++++++++++++---- src/components/module_item.rs | 10 +++++++++- src/config.rs | 32 ++++++++++++++++++++++++++++++-- src/modules/mod.rs | 25 +++++++++++++++---------- src/modules/system_info.rs | 21 ++++++++++----------- src/theme.rs | 14 ++++++++++---- 6 files changed, 90 insertions(+), 32 deletions(-) diff --git a/src/components/module_group.rs b/src/components/module_group.rs index b7a99c0b1..7b541a483 100644 --- a/src/components/module_group.rs +++ b/src/components/module_group.rs @@ -1,4 +1,7 @@ -use crate::{config::BarSurface, theme::use_theme}; +use crate::{ + config::{BarSurface, ModuleAppearance}, + theme::use_theme, +}; use iced::{Border, Color, Element, widget::container}; /// Wraps content with the appropriate bar surface container. @@ -6,11 +9,15 @@ use iced::{Border, Color, Element, widget::container}; /// - `Solid` → pass through as-is (the bar itself carries the background) /// - `Transparent` → wrap in a container with background color + rounded border /// - `Panel` → wrap in a container with customisation from config -pub fn module_group<'a, Msg: 'static>(content: Element<'a, Msg>) -> Element<'a, Msg> { - let (bar_surface, theme_radius, module_opacity, module_border, module_padding) = +pub fn module_group<'a, Msg: 'static>( + content: Element<'a, Msg>, + module_apperance: Option<&ModuleAppearance>, +) -> Element<'a, Msg> { + let (bar_surface, theme_space, theme_radius, module_opacity, module_border, _module_padding) = use_theme(|theme| { ( theme.bar.surface, + theme.space, theme.radius, theme.bar.opacity.module, theme.bar.module_border, @@ -23,6 +30,9 @@ pub fn module_group<'a, Msg: 'static>(content: Element<'a, Msg>) -> Element<'a, match bar_surface { BarSurface::Solid => content, BarSurface::Transparent => container(content) + .padding( + module_apperance.map_or(0., |appearance| theme_space.resolve(appearance.padding)), + ) .style(move |iced_theme: &iced::Theme| container::Style { background: Some( iced_theme @@ -40,7 +50,9 @@ pub fn module_group<'a, Msg: 'static>(content: Element<'a, Msg>) -> Element<'a, }) .into(), BarSurface::Panel => container(content) - .padding(module_padding) + .padding( + module_apperance.map_or(0., |appearance| theme_space.resolve(appearance.padding)), + ) .style(move |iced_theme: &iced::Theme| container::Style { background: Some( iced_theme diff --git a/src/components/module_item.rs b/src/components/module_item.rs index 05e6f69c8..22cf38241 100644 --- a/src/components/module_item.rs +++ b/src/components/module_item.rs @@ -9,6 +9,7 @@ use super::ButtonUIRef; /// When no press handler is set, renders as a plain container. pub struct ModuleItem<'a, Msg> { content: Element<'a, Msg>, + no_hover: bool, // enum it maybe? on_press: Option, on_press_with_position: Option Msg + 'a>>, on_right_press: Option, @@ -20,6 +21,7 @@ pub struct ModuleItem<'a, Msg> { pub fn module_item<'a, Msg: 'static + Clone>(content: Element<'a, Msg>) -> ModuleItem<'a, Msg> { ModuleItem { content, + no_hover: false, on_press: None, on_press_with_position: None, on_right_press: None, @@ -30,6 +32,11 @@ pub fn module_item<'a, Msg: 'static + Clone>(content: Element<'a, Msg>) -> Modul } impl<'a, Msg: 'static + Clone> ModuleItem<'a, Msg> { + pub fn no_hover(mut self, value: bool) -> Self { + self.no_hover = value; + self + } + pub fn on_press(mut self, msg: Msg) -> Self { self.on_press = Some(msg); self @@ -63,8 +70,9 @@ impl<'a, Msg: 'static + Clone> ModuleItem<'a, Msg> { impl<'a, Msg: 'static + Clone> From> for Element<'a, Msg> { fn from(item: ModuleItem<'a, Msg>) -> Self { + let no_hover = item.no_hover; let (space, module_button_style) = - use_theme(|theme| (theme.space, theme.module_button_style())); + use_theme(|theme| (theme.space, theme.module_button_style(no_hover))); let has_action = item.on_press.is_some() || item.on_press_with_position.is_some(); diff --git a/src/config.rs b/src/config.rs index 178849771..7e35e032a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -983,6 +983,14 @@ impl BarRadius { bottom_right: scale.resolve(self.bottom_right), } } + pub fn new(size: RadiusSize) -> Self { + Self { + top_left: size, + top_right: size, + bottom_left: size, + bottom_right: size, + } + } } /// Per-edge margin selection, deserialized with CSS `margin` shorthand: @@ -1019,7 +1027,7 @@ pub struct BorderAppearance { impl Default for BorderAppearance { fn default() -> Self { Self { - radius: BarRadius::default(), + radius: BarRadius::new(RadiusSize::Lg), width: 0f32, color: AppearanceColor::Simple(HexColor::default()), } @@ -1046,7 +1054,7 @@ impl Default for OpacityAppearance { } } -#[derive(Default, Deserialize, Clone, Copy, Debug)] +#[derive(Deserialize, Clone, Copy, Debug)] #[serde(default)] pub struct BarAppearance { pub surface: BarSurface, @@ -1056,6 +1064,24 @@ pub struct BarAppearance { pub margin: BarMargin, } +impl Default for BarAppearance { + fn default() -> Self { + Self { + border: BorderAppearance { + radius: BarRadius::new(RadiusSize::None), + ..Default::default() + }, + surface: BarSurface::default(), + opacity: OpacityAppearance { + background: 0.5, + ..Default::default() + }, + module_border: BorderAppearance::default(), + margin: BarMargin::default(), + } + } +} + #[derive(Deserialize, Clone, Copy, Debug)] #[serde(default)] pub struct MenuAppearance { @@ -1087,12 +1113,14 @@ pub enum ModuleGroup { pub struct ModuleAppearance { pub spacing: SpaceSize, pub grouping: ModuleGroup, + pub padding: SpaceSize, } impl Default for ModuleAppearance { fn default() -> Self { Self { spacing: SpaceSize::Xs, + padding: SpaceSize::Xxs, grouping: ModuleGroup::default(), } } diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 77c0e490c..2cabd1ffd 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -84,9 +84,14 @@ impl App { fn build_module_item<'a>( &'a self, id: SurfaceId, + module_name: &'a ModuleName, content: Element<'a, Message>, action: Option, ) -> Element<'a, Message> { + let no_hover = use_theme(|t| { + t.modules.get(module_name).unwrap_or(&t.module).grouping != ModuleGroup::Combined + }); + let content = if use_theme(|t| t.animations_enabled) { animated_size(content).into() } else { @@ -94,7 +99,7 @@ impl App { }; match action { Some(action) => { - let mut item = module_item(content); + let mut item = module_item(content).no_hover(no_hover); match action { OnModulePress::Action(msg) => { item = item.on_press(*msg); @@ -147,7 +152,7 @@ impl App { } item.into() } - None => module_item(content).into(), + None => module_item(content).no_hover(no_hover).into(), } } @@ -157,18 +162,18 @@ impl App { module_name: &'a ModuleName, ) -> Option> { let module_appearances = use_theme(|t| t.modules.clone()); - let grouping = module_appearances - .get(module_name) + let module_appearance = module_appearances.get(module_name); + let grouping = module_appearance .unwrap_or(&ModuleAppearance::default()) .grouping; self.get_module_view(id, module_name) .map(|(content, action)| { - let item = self.build_module_item(id, content, action); + let item = self.build_module_item(id, module_name, content, action); match grouping { ModuleGroup::None | ModuleGroup::Individual => item, - ModuleGroup::Combined => module_group(item), + ModuleGroup::Combined => module_group(item, module_appearance), } }) } @@ -192,15 +197,15 @@ impl App { let items = module_items .into_iter() .map(|(module_name, (content, action))| { - let item = self.build_module_item(id, content, action); - let grouping = module_appearances - .get(module_name) + let item = self.build_module_item(id, module_name, content, action); + let module_appearance = module_appearances.get(module_name); + let grouping = module_appearance .unwrap_or(&ModuleAppearance::default()) .grouping; match grouping { ModuleGroup::None | ModuleGroup::Individual => item, - ModuleGroup::Combined => module_group(item), + ModuleGroup::Combined => module_group(item, module_appearance), } }) .collect::>(); diff --git a/src/modules/system_info.rs b/src/modules/system_info.rs index c53d199de..fb61263e3 100644 --- a/src/modules/system_info.rs +++ b/src/modules/system_info.rs @@ -5,8 +5,9 @@ use crate::{ module_group, }, config::{ - CpuFormat, DiskFormat, MemoryFormat, ModuleGroup, ModuleName, SystemInfoIndicator, - SystemInfoModuleConfig, SystemInfoTemperature, TemperatureSensor, TemperatureSensorType, + CpuFormat, DiskFormat, MemoryFormat, ModuleAppearance, ModuleGroup, ModuleName, + SystemInfoIndicator, SystemInfoModuleConfig, SystemInfoTemperature, TemperatureSensor, + TemperatureSensorType, }, i18n::{UnitSystem, unit_system}, t, @@ -639,14 +640,12 @@ impl SystemInfo { } pub fn view(&'_ self) -> Element<'_, Message> { - let (theme_space, space_sizing, grouping) = use_theme(|t| { - let module_appearance = t.modules.get(&ModuleName::SystemInfo).unwrap_or(&t.module); - ( - t.space, - module_appearance.spacing, - module_appearance.grouping, - ) - }); + let (theme_space, module_appearances) = use_theme(|t| (t.space, t.modules.clone())); + let binding = &ModuleAppearance::default(); + let module_appearance = module_appearances + .get(&ModuleName::SystemInfo) + .unwrap_or(binding); + let (space_sizing, grouping) = (module_appearance.spacing, module_appearance.grouping); let space = theme_space.resolve(space_sizing); @@ -788,7 +787,7 @@ impl SystemInfo { element.map(|el| { if grouping == ModuleGroup::Individual { - module_group(el) + module_group(el, Some(module_appearance)) } else { el } diff --git a/src/theme.rs b/src/theme.rs index 892a1a0f4..c64b17d9a 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -723,15 +723,21 @@ impl AshellTheme { /// Module button style: transparent base with hover highlight. /// The module-group background is handled by `module_group`, not the button. - pub fn module_button_style(&self) -> impl Fn(&Theme, Status) -> button::Style + use<> { - let radius_lg = self.radius.lg; + pub fn module_button_style( + &self, + no_hover: bool, + ) -> impl Fn(&Theme, Status) -> button::Style + use<> { + let (theme_radius, border, _module_padding) = + (self.radius, self.bar.module_border, self.space.xxs); + let radius = border.radius.resolve(theme_radius); + let btn_opacity = self.bar.opacity.button; move |theme, status| { let mut base = button::Style { background: None, border: Border { width: 0.0, - radius: radius_lg.into(), + radius, color: Color::TRANSPARENT, }, text_color: theme.palette().text, @@ -739,7 +745,7 @@ impl AshellTheme { }; match status { Status::Active => base, - Status::Hovered => { + Status::Hovered if !no_hover => { base.background = Some( theme .extended_palette() From 85a3e35af9f973584ea1ff2d4420b4277a4cf5c7 Mon Sep 17 00:00:00 2001 From: Sulieman <168682205+sulabi@users.noreply.github.com> Date: Sat, 8 Aug 2026 16:01:51 +0100 Subject: [PATCH 06/11] change module view to return an Element wrapper to fix individual grouping add components/module_view.rs so that modules can return structured content which can store the module's members instead of a single transformed Element. This fixes hover and click issues onto individual elements. --- src/components/mod.rs | 2 + src/components/module_group.rs | 44 ++--- src/components/module_item.rs | 10 +- src/components/module_view.rs | 174 ++++++++++++++++++++ src/config.rs | 5 +- src/modules/custom_module.rs | 43 +++-- src/modules/keyboard_layout.rs | 7 +- src/modules/keyboard_submap.rs | 11 +- src/modules/media_player.rs | 85 ++++++---- src/modules/mod.rs | 291 ++++++++++++++++++--------------- src/modules/notifications.rs | 18 +- src/modules/privacy.rs | 13 +- src/modules/settings/mod.rs | 24 +-- src/modules/system_info.rs | 290 ++++++++++++++++---------------- src/modules/tempo/mod.rs | 6 +- src/modules/tray.rs | 16 +- src/modules/updates.rs | 14 +- src/modules/window_title.rs | 11 +- src/modules/workspaces.rs | 84 +++++----- src/theme.rs | 23 ++- 20 files changed, 706 insertions(+), 465 deletions(-) create mode 100644 src/components/module_view.rs diff --git a/src/components/mod.rs b/src/components/mod.rs index 82c98abdd..b9324084f 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -8,6 +8,7 @@ pub mod menu; mod menu_wrapper; mod module_group; mod module_item; +mod module_view; pub mod password_dialog; mod position_button; mod quick_setting_button; @@ -25,6 +26,7 @@ pub use menu::MenuSize; pub use menu_wrapper::*; pub use module_group::*; pub use module_item::*; +pub use module_view::*; pub use position_button::*; pub use quick_setting_button::*; pub use slider_control::*; diff --git a/src/components/module_group.rs b/src/components/module_group.rs index 7b541a483..b47ac261e 100644 --- a/src/components/module_group.rs +++ b/src/components/module_group.rs @@ -2,7 +2,7 @@ use crate::{ config::{BarSurface, ModuleAppearance}, theme::use_theme, }; -use iced::{Border, Color, Element, widget::container}; +use iced::{Border, Element, widget::container}; /// Wraps content with the appropriate bar surface container. /// @@ -29,27 +29,27 @@ pub fn module_group<'a, Msg: 'static>( match bar_surface { BarSurface::Solid => content, - BarSurface::Transparent => container(content) - .padding( - module_apperance.map_or(0., |appearance| theme_space.resolve(appearance.padding)), - ) - .style(move |iced_theme: &iced::Theme| container::Style { - background: Some( - iced_theme - .palette() - .background - .scale_alpha(module_opacity) - .into(), - ), - border: Border { - width: 0.0, - radius, - color: Color::TRANSPARENT, - }, - ..container::Style::default() - }) - .into(), - BarSurface::Panel => container(content) + // BarSurface::Transparent => container(content) + // .padding( + // module_apperance.map_or(0., |appearance| theme_space.resolve(appearance.padding)), + // ) + // .style(move |iced_theme: &iced::Theme| container::Style { + // background: Some( + // iced_theme + // .palette() + // .background + // .scale_alpha(module_opacity) + // .into(), + // ), + // border: Border { + // width: 0.0, + // radius, + // color: Color::TRANSPARENT, + // }, + // ..container::Style::default() + // }) + // .into(), + BarSurface::Panel | BarSurface::Transparent => container(content) .padding( module_apperance.map_or(0., |appearance| theme_space.resolve(appearance.padding)), ) diff --git a/src/components/module_item.rs b/src/components/module_item.rs index 22cf38241..05e6f69c8 100644 --- a/src/components/module_item.rs +++ b/src/components/module_item.rs @@ -9,7 +9,6 @@ use super::ButtonUIRef; /// When no press handler is set, renders as a plain container. pub struct ModuleItem<'a, Msg> { content: Element<'a, Msg>, - no_hover: bool, // enum it maybe? on_press: Option, on_press_with_position: Option Msg + 'a>>, on_right_press: Option, @@ -21,7 +20,6 @@ pub struct ModuleItem<'a, Msg> { pub fn module_item<'a, Msg: 'static + Clone>(content: Element<'a, Msg>) -> ModuleItem<'a, Msg> { ModuleItem { content, - no_hover: false, on_press: None, on_press_with_position: None, on_right_press: None, @@ -32,11 +30,6 @@ pub fn module_item<'a, Msg: 'static + Clone>(content: Element<'a, Msg>) -> Modul } impl<'a, Msg: 'static + Clone> ModuleItem<'a, Msg> { - pub fn no_hover(mut self, value: bool) -> Self { - self.no_hover = value; - self - } - pub fn on_press(mut self, msg: Msg) -> Self { self.on_press = Some(msg); self @@ -70,9 +63,8 @@ impl<'a, Msg: 'static + Clone> ModuleItem<'a, Msg> { impl<'a, Msg: 'static + Clone> From> for Element<'a, Msg> { fn from(item: ModuleItem<'a, Msg>) -> Self { - let no_hover = item.no_hover; let (space, module_button_style) = - use_theme(|theme| (theme.space, theme.module_button_style(no_hover))); + use_theme(|theme| (theme.space, theme.module_button_style())); let has_action = item.on_press.is_some() || item.on_press_with_position.is_some(); diff --git a/src/components/module_view.rs b/src/components/module_view.rs new file mode 100644 index 000000000..d601e1edf --- /dev/null +++ b/src/components/module_view.rs @@ -0,0 +1,174 @@ +use iced::Row; +use iced::alignment::Vertical; +use iced::{Element, Length}; + +use crate::modules::OnModulePress; + +pub struct ModuleView<'a, Msg> { + pub content: ModuleContent<'a, Msg>, +} + +pub enum ModuleContent<'a, Msg> { + Row(ModuleRow<'a, Msg>), + Element(Element<'a, Msg>), +} + +impl<'a, Msg> std::fmt::Debug for ModuleContent<'a, Msg> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ModuleContent::Element(_) => f.write_str("Element(..)"), + ModuleContent::Row(row) => f + .debug_struct("Row") + .field("children", &row.children.len()) + .field("spacing", &row.spacing) + .field("align_y", &row.align_y) + .finish(), + } + } +} + +impl<'a, Msg: 'a> ModuleView<'a, Msg> { + pub fn new(content: impl Into>) -> Self { + Self { + content: content.into(), + } + } + + pub fn into_element(self) -> Element<'a, Msg> { + match self.content { + ModuleContent::Element(element) => element, + ModuleContent::Row(row) => row.into_element(), + } + } + + pub fn map(self, f: impl Fn(Msg) -> NewMsg + Clone + 'a) -> ModuleView<'a, NewMsg> + where + NewMsg: 'a, + { + ModuleView::new(match self.content { + ModuleContent::Element(element) => ModuleContent::Element(element.map(f)), + ModuleContent::Row(row) => ModuleContent::Row(row.map(f)), + }) + } + + pub fn map_elements(self, f: impl Fn(Element<'a, Msg>) -> Element<'a, Msg> + Clone) -> Self { + Self { + content: match self.content { + ModuleContent::Element(element) => ModuleContent::Element(f(element)), + ModuleContent::Row(mut row) => { + row.children = row + .children + .into_iter() + .map(|child| f.clone()(child)) + .collect(); + ModuleContent::Row(row) + } + }, + } + } +} + +impl<'a, Msg> From> for ModuleView<'a, Msg> { + fn from(element: Element<'a, Msg>) -> Self { + Self { + content: ModuleContent::Element(element), + } + } +} + +impl<'a, Msg> From> for ModuleView<'a, Msg> { + fn from(row: ModuleRow<'a, Msg>) -> Self { + Self { + content: ModuleContent::Row(row), + } + } +} + +pub struct ModuleRow<'a, Msg> { + pub children: Vec>, + pub spacing: f32, + pub align_y: Vertical, + pub height: Length, +} + +impl<'a, Msg: 'a> ModuleRow<'a, Msg> { + pub fn new() -> Self { + Self { + children: Vec::new(), + spacing: 0.0, + align_y: Vertical::Top, + height: Length::Shrink, + } + } + + pub fn with_children(children: I) -> Self + where + I: IntoIterator, + I::Item: Into>, + { + let children = children.into_iter(); + let mut row = Self::with_capacity(children.size_hint().0); + + row.children.extend(children.map(Into::into)); + + row + } + + pub fn with_capacity(capacity: usize) -> Self { + Self { + children: Vec::with_capacity(capacity), + spacing: 0.0, + align_y: Vertical::Top, + height: Length::Shrink, + } + } + + pub fn push(mut self, child: impl Into>) -> Self { + self.children.push(child.into()); + self + } + + pub fn spacing(mut self, spacing: f32) -> Self { + self.spacing = spacing; + self + } + + pub fn align_y(mut self, align: impl Into) -> Self { + self.align_y = align.into(); + self + } + + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + pub fn into_element(self) -> Element<'a, Msg> { + Row::with_children(self.children) + .height(self.height) + .align_y(self.align_y) + .spacing(self.spacing) + .into() + } + + pub fn map(self, f: impl Fn(Msg) -> NewMsg + Clone + 'a) -> ModuleRow<'a, NewMsg> + where + NewMsg: 'a, + { + ModuleRow { + children: self + .children + .into_iter() + .map(|child| child.map(f.clone())) + .collect(), + spacing: self.spacing, + align_y: self.align_y, + height: self.height, + } + } +} + +pub struct ModuleResult<'a, Msg: 'static> { + pub view: ModuleView<'a, Msg>, + pub action: Option, +} diff --git a/src/config.rs b/src/config.rs index 7e35e032a..f9f1c7a6d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1072,10 +1072,7 @@ impl Default for BarAppearance { ..Default::default() }, surface: BarSurface::default(), - opacity: OpacityAppearance { - background: 0.5, - ..Default::default() - }, + opacity: OpacityAppearance::default(), module_border: BorderAppearance::default(), margin: BarMargin::default(), } diff --git a/src/modules/custom_module.rs b/src/modules/custom_module.rs index 12c97f125..598b5b7a6 100644 --- a/src/modules/custom_module.rs +++ b/src/modules/custom_module.rs @@ -1,5 +1,8 @@ use crate::{ - components::icons::{DynamicIcon, StaticIcon, icon}, + components::{ + ModuleContent, ModuleRow, ModuleView, + icons::{DynamicIcon, StaticIcon, icon}, + }, config::CustomModuleDef, theme::use_theme, utils::launcher::execute_command, @@ -8,7 +11,7 @@ use iced::widget::canvas; use iced::{ Element, Length, Subscription, Theme, stream::channel, - widget::{Space, Stack, row, text}, + widget::{Space, Stack, text}, }; use iced::{ mouse::Cursor, @@ -119,7 +122,7 @@ impl Custom { } } - pub fn view(&'_ self) -> Element<'_, Message> { + pub fn view<'a>(&'a self) -> ModuleView<'a, Message> { let space = use_theme(|theme| theme.space); match self.config.r#type { crate::config::CustomModuleType::Text => self @@ -128,12 +131,18 @@ impl Custom { .as_ref() .and_then(|text_content| { if !text_content.is_empty() { - Some(text(text_content.clone()).into()) + Some(ModuleView::new(ModuleContent::Element( + text(text_content.clone()).into(), + ))) } else { None } }) - .unwrap_or_else(|| Space::new().width(Length::Shrink).into()), + .unwrap_or_else(|| { + ModuleView::new(ModuleContent::Element( + Space::new().width(Length::Shrink).into(), + )) + }), crate::config::CustomModuleType::Button => { let mut icon_element = self.config.icon.as_ref().map_or_else( || icon(StaticIcon::None), @@ -160,7 +169,7 @@ impl Custom { false }; - let icon_with_alert = if show_alert { + let icon_with_alert: Element<'a, Message> = if show_alert { let alert_canvas = canvas(AlertIndicator) .width(Length::Fixed(space.xs)) // Size of the dot .height(Length::Fixed(space.xs)); @@ -180,18 +189,22 @@ impl Custom { padded_icon_container.into() // No alert, just the padded icon }; - let maybe_text_element = self.data.text.as_ref().and_then(|text_content| { - if !text_content.is_empty() { - Some(text(text_content.clone())) - } else { - None - } - }); + let maybe_text_element: Option> = + self.data.text.as_ref().and_then(|text_content| { + if !text_content.is_empty() { + Some(text(text_content.clone()).into()) + } else { + None + } + }); if let Some(text_element) = maybe_text_element { - row![icon_with_alert, text_element].spacing(space.xs).into() + ModuleView::new(ModuleContent::Row( + ModuleRow::with_children(vec![icon_with_alert, text_element]) + .spacing(space.xs), + )) } else { - icon_with_alert + ModuleView::new(ModuleContent::Element(icon_with_alert)) } } } diff --git a/src/modules/keyboard_layout.rs b/src/modules/keyboard_layout.rs index d5cedbb44..94ea06e9a 100644 --- a/src/modules/keyboard_layout.rs +++ b/src/modules/keyboard_layout.rs @@ -1,11 +1,12 @@ use crate::{ + components::{ModuleContent, ModuleView}, config::KeyboardLayoutModuleConfig, services::{ ReadOnlyService, Service, ServiceEvent, compositor::{CompositorCommand, CompositorService}, }, }; -use iced::{Element, Subscription, Task, widget::text}; +use iced::{Subscription, Task, widget::text}; #[derive(Debug, Clone)] pub enum Message { @@ -56,7 +57,7 @@ impl KeyboardLayout { } } - pub fn view(&self) -> Option> { + pub fn view(&self) -> Option> { let service = self.service.as_ref()?; let active_layout = &service.keyboard_layout; @@ -73,7 +74,7 @@ impl KeyboardLayout { // Returns plain text matching original implementation style. // (Assuming parent container or mouse area handles interactions if any) - Some(text(label).into()) + Some(ModuleView::new(ModuleContent::Element(text(label).into()))) } pub fn subscription(&self) -> Subscription { diff --git a/src/modules/keyboard_submap.rs b/src/modules/keyboard_submap.rs index 0c3c82aa3..a9b8dd507 100644 --- a/src/modules/keyboard_submap.rs +++ b/src/modules/keyboard_submap.rs @@ -1,5 +1,8 @@ -use crate::services::{ReadOnlyService, ServiceEvent, compositor::CompositorService}; -use iced::{Element, Subscription, widget::text}; +use crate::{ + components::{ModuleContent, ModuleView}, + services::{ReadOnlyService, ServiceEvent, compositor::CompositorService}, +}; +use iced::{Subscription, widget::text}; #[derive(Debug, Clone)] pub enum Message { @@ -30,11 +33,11 @@ impl KeyboardSubmap { } } - pub fn view(&self) -> Option> { + pub fn view(&self) -> Option> { let submap = self.service.as_ref()?.submap.as_ref()?; if !submap.is_empty() { - Some(text(submap).into()) + Some(ModuleView::new(ModuleContent::Element(text(submap).into()))) } else { None } diff --git a/src/modules/media_player.rs b/src/modules/media_player.rs index 03d574ab4..d67eac352 100644 --- a/src/modules/media_player.rs +++ b/src/modules/media_player.rs @@ -1,7 +1,8 @@ use crate::{ - components::divider, - components::icons::{StaticIcon, icon, icon_button}, - components::{ButtonSize, MenuSize}, + components::{ + ButtonSize, MenuSize, ModuleContent, ModuleRow, ModuleView, divider, + icons::{StaticIcon, icon, icon_button}, + }, config::{ MediaPlayerFormat, MediaPlayerModuleConfig, MediaPlayerTextField, MediaPlayerVisualizer, }, @@ -463,7 +464,7 @@ impl MediaPlayer { } } - pub fn view(&'_ self) -> Option> { + pub fn view(&'_ self) -> Option> { let (space, font_size, palette) = use_theme(|theme| (theme.space, theme.font_size, theme.iced_theme.palette())); self.active_player().map(|player| { @@ -490,7 +491,7 @@ impl MediaPlayer { let beside_visualizer = || { container( - Canvas::new(VisualizerCanvas { + Canvas::::new(VisualizerCanvas { bars: self.bars.clone(), low: palette.primary, mid: palette.warning, @@ -519,37 +520,51 @@ impl MediaPlayer { .center_y(Length::Fill) .into() }; - Stack::new() - .push(base) - .push_under( - Canvas::new(VisualizerCanvas { - bars: self.bars.clone(), - low: palette.primary, - mid: palette.warning, - high: palette.danger, - opacity: 0.1, - radius: 0.0, - min_bar_width: VISUALIZER_BAR_MIN_WIDTH, - max_bar_width: VISUALIZER_BG_BAR_MAX_WIDTH, - gap: VISUALIZER_BAR_GAP, - inset: space.xxs, - }) - .width(Length::Fill) - .height(Length::Fill), - ) - .into() + ModuleView::new(ModuleContent::Element( + Stack::new() + .push(base) + .push_under( + Canvas::new(VisualizerCanvas { + bars: self.bars.clone(), + low: palette.primary, + mid: palette.warning, + high: palette.danger, + opacity: 0.1, + radius: 0.0, + min_bar_width: VISUALIZER_BAR_MIN_WIDTH, + max_bar_width: VISUALIZER_BG_BAR_MAX_WIDTH, + gap: VISUALIZER_BAR_GAP, + inset: space.xxs, + }) + .width(Length::Fill) + .height(Length::Fill), + ) + .into(), + )) + } + Some(MediaPlayerVisualizer::Before) if active => { + ModuleView::new(ModuleContent::Row( + ModuleRow::with_children(vec![ + Element::from(beside_visualizer()), + Element::from(content), + ]) + .align_y(Vertical::Center) + .spacing(space.xs) + .height(Length::Fill), + )) + } + Some(MediaPlayerVisualizer::After) if active => { + ModuleView::new(ModuleContent::Row( + ModuleRow::with_children(vec![ + Element::from(content), + Element::from(beside_visualizer()), + ]) + .align_y(Vertical::Center) + .spacing(space.xs) + .height(Length::Fill), + )) } - Some(MediaPlayerVisualizer::Before) if active => row![beside_visualizer(), content] - .align_y(Vertical::Center) - .spacing(space.xs) - .height(Length::Fill) - .into(), - Some(MediaPlayerVisualizer::After) if active => row![content, beside_visualizer()] - .align_y(Vertical::Center) - .spacing(space.xs) - .height(Length::Fill) - .into(), - _ => content.into(), + _ => ModuleView::new(ModuleContent::Element(content.into())), } }) } diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 2cabd1ffd..1b2c253bb 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -1,6 +1,8 @@ use crate::{ app::{App, Message}, - components::{animated_size, menu::MenuType, module_group, module_item}, + components::{ + ModuleItem, ModuleResult, animated_size, menu::MenuType, module_group, module_item, + }, config::{ModuleAppearance, ModuleDef, ModuleGroup, ModuleName}, theme::use_theme, }; @@ -81,6 +83,68 @@ impl App { .collect() } + fn apply_module_action<'a>( + &self, + mut item: ModuleItem<'a, Message>, + action: Option, + id: SurfaceId, + ) -> ModuleItem<'a, Message> { + if let Some(action) = action { + match action { + OnModulePress::Action(msg) => { + item = item.on_press(*msg); + } + OnModulePress::ToggleMenu(menu_type) => { + item = item.on_press_with_position(move |button_ui_ref| { + Message::ToggleMenu(menu_type.clone(), id, button_ui_ref) + }); + } + OnModulePress::ToggleMenuWithExtra { + menu_type, + on_right_press, + on_scroll_up, + on_scroll_down, + } => { + item = item.on_press_with_position(move |button_ui_ref| { + Message::ToggleMenu(menu_type.clone(), id, button_ui_ref) + }); + if let Some(msg) = on_right_press { + item = item.on_right_press(*msg); + } + if let Some(msg) = on_scroll_up { + item = item.on_scroll_up(*msg); + } + if let Some(msg) = on_scroll_down { + item = item.on_scroll_down(*msg); + } + } + OnModulePress::CustomAction { + on_press, + on_right_press, + on_middle_press, + on_scroll_up, + on_scroll_down, + } => { + item = item.on_press(*on_press); + if let Some(msg) = on_right_press { + item = item.on_right_press(*msg); + } + if let Some(msg) = on_middle_press { + item = item.on_middle_press(*msg); + } + if let Some(msg) = on_scroll_up { + item = item.on_scroll_up(*msg); + } + if let Some(msg) = on_scroll_down { + item = item.on_scroll_down(*msg); + } + } + } + }; + + item + } + fn build_module_item<'a>( &'a self, id: SurfaceId, @@ -88,72 +152,17 @@ impl App { content: Element<'a, Message>, action: Option, ) -> Element<'a, Message> { - let no_hover = use_theme(|t| { - t.modules.get(module_name).unwrap_or(&t.module).grouping != ModuleGroup::Combined - }); + let animated = use_theme(|t| t.animations_enabled); - let content = if use_theme(|t| t.animations_enabled) { + let content = if animated { animated_size(content).into() } else { content }; - match action { - Some(action) => { - let mut item = module_item(content).no_hover(no_hover); - match action { - OnModulePress::Action(msg) => { - item = item.on_press(*msg); - } - OnModulePress::ToggleMenu(menu_type) => { - item = item.on_press_with_position(move |button_ui_ref| { - Message::ToggleMenu(menu_type.clone(), id, button_ui_ref) - }); - } - OnModulePress::ToggleMenuWithExtra { - menu_type, - on_right_press, - on_scroll_up, - on_scroll_down, - } => { - item = item.on_press_with_position(move |button_ui_ref| { - Message::ToggleMenu(menu_type.clone(), id, button_ui_ref) - }); - if let Some(msg) = on_right_press { - item = item.on_right_press(*msg); - } - if let Some(msg) = on_scroll_up { - item = item.on_scroll_up(*msg); - } - if let Some(msg) = on_scroll_down { - item = item.on_scroll_down(*msg); - } - } - OnModulePress::CustomAction { - on_press, - on_right_press, - on_middle_press, - on_scroll_up, - on_scroll_down, - } => { - item = item.on_press(*on_press); - if let Some(msg) = on_right_press { - item = item.on_right_press(*msg); - } - if let Some(msg) = on_middle_press { - item = item.on_middle_press(*msg); - } - if let Some(msg) = on_scroll_up { - item = item.on_scroll_up(*msg); - } - if let Some(msg) = on_scroll_down { - item = item.on_scroll_down(*msg); - } - } - } - item.into() - } - None => module_item(content).no_hover(no_hover).into(), - } + + let item = module_item(content); + + self.apply_module_action(item, action, id).into() } fn single_module_wrapper<'a>( @@ -167,15 +176,28 @@ impl App { .unwrap_or(&ModuleAppearance::default()) .grouping; - self.get_module_view(id, module_name) - .map(|(content, action)| { - let item = self.build_module_item(id, module_name, content, action); + self.get_module_view(id, module_name).map(|module_result| { + let ModuleResult { + action, + view: content, + } = module_result; - match grouping { - ModuleGroup::None | ModuleGroup::Individual => item, - ModuleGroup::Combined => module_group(item, module_appearance), + match grouping { + ModuleGroup::Individual => { + let content = content.map_elements(|child| { + // maybe we could further edit individuals + self.build_module_item(id, module_name, child, action.clone()) + }); + + content.into_element() } - }) + ModuleGroup::None | ModuleGroup::Combined => { + let item = + self.build_module_item(id, module_name, content.into_element(), action); + module_group(item, module_appearance) + } + } + }) } fn group_module_wrapper<'a>( @@ -196,16 +218,29 @@ impl App { let items = module_items .into_iter() - .map(|(module_name, (content, action))| { - let item = self.build_module_item(id, module_name, content, action); + .map(|(module_name, module_result)| { + let ModuleResult { + action, + view: content, + } = module_result; let module_appearance = module_appearances.get(module_name); let grouping = module_appearance .unwrap_or(&ModuleAppearance::default()) .grouping; match grouping { - ModuleGroup::None | ModuleGroup::Individual => item, - ModuleGroup::Combined => module_group(item, module_appearance), + ModuleGroup::Individual => { + let content = content.map_elements(|child| { + self.build_module_item(id, module_name, child, action.clone()) + }); + + content.into_element() + } + ModuleGroup::Combined | ModuleGroup::None => { + let item = + self.build_module_item(id, module_name, content.into_element(), action); + module_group(item, module_appearance) + } } }) .collect::>(); @@ -219,7 +254,7 @@ impl App { &'a self, id: SurfaceId, module_name: &'a ModuleName, - ) -> Option<(Element<'a, Message>, Option)> { + ) -> Option> { match module_name { ModuleName::Custom(name) => self.custom.get(name).map(|custom| { let action = match custom.module_type() { @@ -278,52 +313,48 @@ impl App { }) } }; - ( - custom.view().map(|msg| Message::Custom(name.clone(), msg)), + + ModuleResult { + view: custom.view().map(|msg| Message::Custom(name.clone(), msg)), action, - ) + } }), - ModuleName::Updates => self.updates.as_ref().map(|updates| { - ( - updates.view().map(Message::Updates), - Some(OnModulePress::ToggleMenu(MenuType::Updates)), - ) + ModuleName::Updates => self.updates.as_ref().map(|updates| ModuleResult { + view: updates.view().map(Message::Updates), + action: Some(OnModulePress::ToggleMenu(MenuType::Updates)), }), - ModuleName::Workspaces => Some(( - self.workspaces + ModuleName::Workspaces => Some(ModuleResult { + view: self + .workspaces .view(id, &self.outputs) .map(Message::Workspaces), - None, - )), - ModuleName::WindowTitle => self.window_title.get_value().map(|title| { - ( - self.window_title.view(title).map(Message::WindowTitle), - None, - ) + action: None, }), - ModuleName::SystemInfo => Some(( - self.system_info.view().map(Message::SystemInfo), - Some(OnModulePress::ToggleMenu(MenuType::SystemInfo)), - )), - ModuleName::KeyboardLayout => self.keyboard_layout.view().map(|view| { - ( - view.map(Message::KeyboardLayout), - Some(OnModulePress::Action(Box::new(Message::KeyboardLayout( - keyboard_layout::Message::ChangeLayout, - )))), - ) + ModuleName::WindowTitle => self.window_title.get_value().map(|title| ModuleResult { + view: self.window_title.view(title).map(Message::WindowTitle), + action: None, + }), + ModuleName::SystemInfo => Some(ModuleResult { + view: self.system_info.view().map(Message::SystemInfo), + action: Some(OnModulePress::ToggleMenu(MenuType::SystemInfo)), + }), + ModuleName::KeyboardLayout => self.keyboard_layout.view().map(|view| ModuleResult { + view: view.map(Message::KeyboardLayout), + action: Some(OnModulePress::Action(Box::new(Message::KeyboardLayout( + keyboard_layout::Message::ChangeLayout, + )))), }), - ModuleName::KeyboardSubmap => self - .keyboard_submap - .view() - .map(|view| (view.map(Message::KeyboardSubmap), None)), - ModuleName::Tray => self - .tray - .view(id) - .map(|view| (view.map(Message::Tray), None)), - ModuleName::Tempo => Some(( - self.tempo.view().map(Message::Tempo), - Some(OnModulePress::ToggleMenuWithExtra { + ModuleName::KeyboardSubmap => self.keyboard_submap.view().map(|view| ModuleResult { + view: view.map(Message::KeyboardSubmap), + action: None, + }), + ModuleName::Tray => self.tray.view(id).map(|view| ModuleResult { + view: view.map(Message::Tray), + action: None, + }), + ModuleName::Tempo => Some(ModuleResult { + view: self.tempo.view().map(Message::Tempo), + action: Some(OnModulePress::ToggleMenuWithExtra { menu_type: MenuType::Tempo, on_right_press: Some(Box::new(Message::Tempo(tempo::Message::CycleFormat))), on_scroll_up: Some(Box::new(Message::Tempo(tempo::Message::CycleTimezone( @@ -333,25 +364,23 @@ impl App { tempo::TimezoneDirection::Backward, )))), }), - )), - ModuleName::Privacy => self - .privacy - .view() - .map(|view| (view.map(Message::Privacy), None)), - ModuleName::MediaPlayer => self.media_player.view().map(|view| { - ( - view.map(Message::MediaPlayer), - Some(OnModulePress::ToggleMenu(MenuType::MediaPlayer)), - ) }), - ModuleName::Settings => Some(( - self.settings.view(id).map(Message::Settings), - Some(OnModulePress::ToggleMenu(MenuType::Settings)), - )), - ModuleName::Notifications => Some(( - self.notifications.view().map(Message::Notifications), - Some(OnModulePress::ToggleMenu(MenuType::Notifications)), - )), + ModuleName::Privacy => self.privacy.view().map(|view| ModuleResult { + view: view.map(Message::Privacy), + action: None, + }), + ModuleName::MediaPlayer => self.media_player.view().map(|view| ModuleResult { + view: view.map(Message::MediaPlayer), + action: Some(OnModulePress::ToggleMenu(MenuType::MediaPlayer)), + }), + ModuleName::Settings => Some(ModuleResult { + view: self.settings.view(id).map(Message::Settings), + action: Some(OnModulePress::ToggleMenu(MenuType::Settings)), + }), + ModuleName::Notifications => Some(ModuleResult { + view: self.notifications.view().map(Message::Notifications), + action: Some(OnModulePress::ToggleMenu(MenuType::Notifications)), + }), } } diff --git a/src/modules/notifications.rs b/src/modules/notifications.rs index 8f8c76c79..d5fa9f84e 100644 --- a/src/modules/notifications.rs +++ b/src/modules/notifications.rs @@ -1,8 +1,10 @@ use crate::{ - components::collapsible::{self, collapsible}, - components::icons::{StaticIcon, icon, icon_button}, - components::slide::{self, SlideDirection, slide}, - components::{ButtonHierarchy, ButtonKind, ButtonSize, MenuSize}, + components::{ + ButtonHierarchy, ButtonKind, ButtonSize, MenuSize, ModuleContent, ModuleView, + collapsible::{self, collapsible}, + icons::{StaticIcon, icon, icon_button}, + slide::{self, SlideDirection, slide}, + }, config::{NotificationsModuleConfig, ToastPosition}, services::{ ReadOnlyService, ServiceEvent, @@ -645,12 +647,14 @@ impl Notifications { .into() } - pub fn view(&'_ self) -> Element<'_, Message> { - if !self.notifications.is_empty() { + pub fn view(&'_ self) -> ModuleView<'_, Message> { + let element = if !self.notifications.is_empty() { icon(StaticIcon::BellBadge).into() } else { icon(StaticIcon::Bell).into() - } + }; + + ModuleView::new(ModuleContent::Element(element)) } pub fn menu_view<'a>(&'a self) -> Element<'a, Message> { diff --git a/src/modules/privacy.rs b/src/modules/privacy.rs index bcc3e6341..0cbb85d37 100644 --- a/src/modules/privacy.rs +++ b/src/modules/privacy.rs @@ -1,10 +1,13 @@ use crate::{ - components::icons::{StaticIcon, icon}, + components::{ + ModuleContent, ModuleView, + icons::{StaticIcon, icon}, + }, services::{ReadOnlyService, ServiceEvent, privacy::PrivacyService}, theme::use_theme, }; use iced::{ - Alignment, Element, Subscription, + Alignment, Subscription, widget::{Row, container}, }; @@ -35,12 +38,12 @@ impl Privacy { } } - pub fn view(&'_ self) -> Option> { + pub fn view(&'_ self) -> Option> { let space = use_theme(|theme| theme.space); if let Some(service) = self.service.as_ref() && !service.no_access() { - Some( + Some(ModuleView::new(ModuleContent::Element( container( Row::with_capacity(3) .push( @@ -58,7 +61,7 @@ impl Privacy { ..Default::default() }) .into(), - ) + ))) } else { None } diff --git a/src/modules/settings/mod.rs b/src/modules/settings/mod.rs index bbe4f6e9d..5b92fb470 100644 --- a/src/modules/settings/mod.rs +++ b/src/modules/settings/mod.rs @@ -8,7 +8,7 @@ use tokio::time::timeout; use crate::{ components::{ - ButtonUIRef, MenuSize, collapsible, + ButtonUIRef, MenuSize, ModuleContent, ModuleRow, ModuleView, collapsible, icons::{DynamicIcon, Icon, StaticIcon, icon, icon_button}, menu::MenuType, password_dialog, position_button, quick_setting_button, sub_menu_wrapper, @@ -753,14 +753,14 @@ impl Settings { .into() } - pub fn view<'a>(&'a self, id: SurfaceId) -> Element<'a, Message> { + pub fn view<'a>(&'a self, id: SurfaceId) -> ModuleView<'a, Message> { let (theme_space, spacing_size) = use_theme(|t| { let module_appearance = t.modules.get(&ModuleName::Settings).unwrap_or(&t.module); (t.space, module_appearance.spacing) }); let space = theme_space.resolve(spacing_size); - let mut row = Row::with_capacity(self.indicators.len()); + let mut row: Vec> = Vec::with_capacity(self.indicators.len()); for indicator in &self.indicators { let element: Option> = match indicator { @@ -807,7 +807,7 @@ impl Settings { for (index, element) in peripherals.into_iter().enumerate() { let element = element.map(Message::Power); if self.enable_tooltips { - row = row.push( + row.push( position_button(element) .width(Length::Shrink) .height(Length::Shrink) @@ -819,10 +819,11 @@ impl Settings { .on_unhover(Message::TooltipUnhover( id, MenuType::PeripheralBatteryTooltip(index), - )), + )) + .into(), ); } else { - row = row.push(element); + row.push(element); } } None @@ -858,22 +859,25 @@ impl Settings { }; if let Some((hover_msg, menu_type)) = tooltip_config { - row = row.push( + row.push( position_button(element) .width(Length::Shrink) .height(Length::Shrink) .padding(0) .style(transparent_button_style) .on_hover_with_position(move |ui_ref| hover_msg(ui_ref, id)) - .on_unhover(Message::TooltipUnhover(id, menu_type)), + .on_unhover(Message::TooltipUnhover(id, menu_type)) + .into(), ); } else { - row = row.push(element); + row.push(element); } } } - row.spacing(space).into() + ModuleView::new(ModuleContent::Row( + ModuleRow::with_children(row).spacing(space), + )) } pub fn subscription(&self) -> Subscription { diff --git a/src/modules/system_info.rs b/src/modules/system_info.rs index fb61263e3..1a240cae2 100644 --- a/src/modules/system_info.rs +++ b/src/modules/system_info.rs @@ -1,13 +1,11 @@ use crate::{ components::{ - MenuSize, divider, + MenuSize, ModuleContent, ModuleRow, ModuleView, divider, icons::{StaticIcon, icon}, - module_group, }, config::{ - CpuFormat, DiskFormat, MemoryFormat, ModuleAppearance, ModuleGroup, ModuleName, - SystemInfoIndicator, SystemInfoModuleConfig, SystemInfoTemperature, TemperatureSensor, - TemperatureSensorType, + CpuFormat, DiskFormat, MemoryFormat, ModuleAppearance, ModuleName, SystemInfoIndicator, + SystemInfoModuleConfig, SystemInfoTemperature, TemperatureSensor, TemperatureSensorType, }, i18n::{UnitSystem, unit_system}, t, @@ -17,7 +15,7 @@ use crate::{ use iced::{ Alignment, Element, Length, Subscription, Theme, time::every, - widget::{Column, Row, column, container, row, text}, + widget::{Column, column, container, row, text}, }; use iced_anim::{AnimationBuilder, transition::Easing}; use itertools::Itertools; @@ -639,165 +637,157 @@ impl SystemInfo { .into() } - pub fn view(&'_ self) -> Element<'_, Message> { + pub fn view<'a>(&'a self) -> ModuleView<'a, Message> { let (theme_space, module_appearances) = use_theme(|t| (t.space, t.modules.clone())); let binding = &ModuleAppearance::default(); let module_appearance = module_appearances .get(&ModuleName::SystemInfo) .unwrap_or(binding); - let (space_sizing, grouping) = (module_appearance.spacing, module_appearance.grouping); + let space_sizing = module_appearance.spacing; let space = theme_space.resolve(space_sizing); - let indicators = self.config.indicators.iter().filter_map(|i| { - let element = match i { - SystemInfoIndicator::Cpu => Some(Self::indicator_info_element( - StaticIcon::Cpu, - match self.config.cpu.format { - CpuFormat::Percentage => (self.data.cpu_usage.percentage.to_string(), "%"), - CpuFormat::Frequency => (self.data.cpu_usage.frequency.to_string(), " GHz"), - }, - Some(( - self.data.cpu_usage.percentage, - self.config.cpu.warn_threshold, - self.config.cpu.alert_threshold, - )), - None, + let indicators = self.config.indicators.iter().filter_map(|i| match i { + SystemInfoIndicator::Cpu => Some(Self::indicator_info_element( + StaticIcon::Cpu, + match self.config.cpu.format { + CpuFormat::Percentage => (self.data.cpu_usage.percentage.to_string(), "%"), + CpuFormat::Frequency => (self.data.cpu_usage.frequency.to_string(), " GHz"), + }, + Some(( + self.data.cpu_usage.percentage, + self.config.cpu.warn_threshold, + self.config.cpu.alert_threshold, )), - - SystemInfoIndicator::Memory => Some(Self::indicator_info_element( - StaticIcon::Mem, - match self.config.memory.format { - MemoryFormat::Percentage => { - (self.data.memory_usage.percentage.to_string(), "%") - } - MemoryFormat::Fraction => (self.data.memory_usage.fraction.clone(), " GiB"), - }, - Some(( - self.data.memory_usage.percentage, - self.config.memory.warn_threshold, - self.config.memory.alert_threshold, - )), - None, + None, + )), + + SystemInfoIndicator::Memory => Some(Self::indicator_info_element( + StaticIcon::Mem, + match self.config.memory.format { + MemoryFormat::Percentage => { + (self.data.memory_usage.percentage.to_string(), "%") + } + MemoryFormat::Fraction => (self.data.memory_usage.fraction.clone(), " GiB"), + }, + Some(( + self.data.memory_usage.percentage, + self.config.memory.warn_threshold, + self.config.memory.alert_threshold, )), - - SystemInfoIndicator::MemorySwap => Some(Self::indicator_info_element( - StaticIcon::Mem, - match self.config.memory.format { - MemoryFormat::Percentage => { - (self.data.memory_swap_usage.percentage.to_string(), "%") - } - MemoryFormat::Fraction => { - (self.data.memory_swap_usage.fraction.clone(), " GiB") - } - }, + None, + )), + + SystemInfoIndicator::MemorySwap => Some(Self::indicator_info_element( + StaticIcon::Mem, + match self.config.memory.format { + MemoryFormat::Percentage => { + (self.data.memory_swap_usage.percentage.to_string(), "%") + } + MemoryFormat::Fraction => { + (self.data.memory_swap_usage.fraction.clone(), " GiB") + } + }, + Some(( + self.data.memory_swap_usage.percentage, + self.config.memory.warn_threshold, + self.config.memory.alert_threshold, + )), + Some(t!("system-info-swap-indicator-prefix")), + )), + + SystemInfoIndicator::Temperature => self.data.temperature.celsius.map(|cel| { + let units = unit_system(); + let temp_value = match units { + UnitSystem::Metric => cel, + UnitSystem::Imperial => utils::celsius_to_fahrenheit(cel), + }; + Self::indicator_info_element( + StaticIcon::Temp, + (temp_value, units.temperature_symbol()), Some(( - self.data.memory_swap_usage.percentage, - self.config.memory.warn_threshold, - self.config.memory.alert_threshold, + temp_value, + self.config.temperature.warn_threshold(), + self.config.temperature.alert_threshold(), )), - Some(t!("system-info-swap-indicator-prefix")), - )), - - SystemInfoIndicator::Temperature => self.data.temperature.celsius.map(|cel| { - let units = unit_system(); - let temp_value = match units { - UnitSystem::Metric => cel, - UnitSystem::Imperial => utils::celsius_to_fahrenheit(cel), - }; - Self::indicator_info_element( - StaticIcon::Temp, - (temp_value, units.temperature_symbol()), - Some(( - temp_value, - self.config.temperature.warn_threshold(), - self.config.temperature.alert_threshold(), - )), - None, - ) - }), - SystemInfoIndicator::Disk(config) => { - self.data.disks.iter().find_map(|(disk_mount, disk)| { - if disk_mount == &config.path { - Some(Self::indicator_info_element( - StaticIcon::Drive, - match self.config.disk.format { - DiskFormat::Percentage => (disk.percentage.to_string(), "%"), - DiskFormat::Fraction => (disk.fraction.clone(), " GB"), - }, - Some(( - disk.percentage, - self.config.disk.warn_threshold, - self.config.disk.alert_threshold, - )), - Some(config.name.as_deref().unwrap_or(disk_mount).to_string()), - )) - } else { - None - } - }) - } - SystemInfoIndicator::IpAddress => self.data.network.as_ref().map(|network| { - Self::indicator_info_element( - StaticIcon::IpAddress, - (network.ip.to_string(), ""), - None::<(u32, u32, u32)>, - None, - ) - }), - SystemInfoIndicator::DownloadSpeed => self.data.network.as_ref().map(|network| { - Self::indicator_info_element( - StaticIcon::DownloadSpeed, - ( - if network.download_speed > 1000 { - network.download_speed / 1000 - } else { - network.download_speed - }, - if network.download_speed > 1000 { - "MB/s" - } else { - "KB/s" - }, - ), - None::<(u32, u32, u32)>, - None, - ) - }), - SystemInfoIndicator::UploadSpeed => self.data.network.as_ref().map(|network| { - Self::indicator_info_element( - StaticIcon::UploadSpeed, - ( - if network.upload_speed > 1000 { - network.upload_speed / 1000 - } else { - network.upload_speed - }, - if network.upload_speed > 1000 { - "MB/s" - } else { - "KB/s" + None, + ) + }), + SystemInfoIndicator::Disk(config) => { + self.data.disks.iter().find_map(|(disk_mount, disk)| { + if disk_mount == &config.path { + Some(Self::indicator_info_element( + StaticIcon::Drive, + match self.config.disk.format { + DiskFormat::Percentage => (disk.percentage.to_string(), "%"), + DiskFormat::Fraction => (disk.fraction.clone(), " GB"), }, - ), - None::<(u32, u32, u32)>, - None, - ) - }), - }; - - element.map(|el| { - if grouping == ModuleGroup::Individual { - module_group(el, Some(module_appearance)) - } else { - el - } - }) + Some(( + disk.percentage, + self.config.disk.warn_threshold, + self.config.disk.alert_threshold, + )), + Some(config.name.as_deref().unwrap_or(disk_mount).to_string()), + )) + } else { + None + } + }) + } + SystemInfoIndicator::IpAddress => self.data.network.as_ref().map(|network| { + Self::indicator_info_element( + StaticIcon::IpAddress, + (network.ip.to_string(), ""), + None::<(u32, u32, u32)>, + None, + ) + }), + SystemInfoIndicator::DownloadSpeed => self.data.network.as_ref().map(|network| { + Self::indicator_info_element( + StaticIcon::DownloadSpeed, + ( + if network.download_speed > 1000 { + network.download_speed / 1000 + } else { + network.download_speed + }, + if network.download_speed > 1000 { + "MB/s" + } else { + "KB/s" + }, + ), + None::<(u32, u32, u32)>, + None, + ) + }), + SystemInfoIndicator::UploadSpeed => self.data.network.as_ref().map(|network| { + Self::indicator_info_element( + StaticIcon::UploadSpeed, + ( + if network.upload_speed > 1000 { + network.upload_speed / 1000 + } else { + network.upload_speed + }, + if network.upload_speed > 1000 { + "MB/s" + } else { + "KB/s" + }, + ), + None::<(u32, u32, u32)>, + None, + ) + }), }); - Row::with_children(indicators) - .align_y(Alignment::Center) - .spacing(space) - .into() + ModuleView::new(ModuleContent::Row( + ModuleRow::with_children(indicators) + .spacing(space) + .align_y(Alignment::Center) + .height(Length::Fill), + )) } pub fn subscription(&self) -> Subscription { diff --git a/src/modules/tempo/mod.rs b/src/modules/tempo/mod.rs index d5703a9b5..88fe8344e 100644 --- a/src/modules/tempo/mod.rs +++ b/src/modules/tempo/mod.rs @@ -15,7 +15,7 @@ use log::{debug, warn}; use self::weather::{Location, WeatherData, fetch_location, fetch_weather_data}; use crate::{ - components::MenuSize, + components::{MenuSize, ModuleRow, ModuleView}, config::{TempoModuleConfig, WeatherIndicator}, i18n::{language_subtag, unit_system}, theme::use_theme, @@ -164,11 +164,11 @@ impl Tempo { } } - pub fn view(&'_ self) -> Element<'_, Message> { + pub fn view(&'_ self) -> ModuleView<'_, Message> { let space = use_theme(|t| t.space); let display_text = self.time_str(self.current_format(), self.current_timezone_index, None); - Row::with_capacity(2) + ModuleRow::with_capacity(2) .push(self.weather_indicator()) .push(text(display_text)) .align_y(Vertical::Center) diff --git a/src/modules/tray.rs b/src/modules/tray.rs index 5372e8c07..6d03b283c 100644 --- a/src/modules/tray.rs +++ b/src/modules/tray.rs @@ -1,9 +1,9 @@ use crate::{ - components::divider, - components::icons::{StaticIcon, icon}, components::{ - ButtonHierarchy, ButtonKind, ButtonUIRef, IconPosition, MenuSize, position_button, - styled_button, + ButtonHierarchy, ButtonKind, ButtonUIRef, IconPosition, MenuSize, ModuleContent, + ModuleView, divider, + icons::{StaticIcon, icon}, + position_button, styled_button, }, config::{TrayClickAction, TrayModuleConfig}, services::{ @@ -235,7 +235,7 @@ impl TrayModule { } } - pub fn view<'a>(&'a self, id: SurfaceId) -> Option> { + pub fn view<'a>(&'a self, id: SurfaceId) -> Option> { let (space, font_size, button_style) = use_theme(|theme| { ( theme.space, @@ -249,7 +249,7 @@ impl TrayModule { .as_ref() .filter(|s| !s.data.is_empty()) .map(|service| { - Into::>::into( + let element = Into::>::into( Row::with_children( service .data @@ -290,7 +290,9 @@ impl TrayModule { ) .padding([2.0, 0.]) .align_y(Alignment::Center), - ) + ); + + ModuleView::new(ModuleContent::Element(element)) }) } diff --git a/src/modules/updates.rs b/src/modules/updates.rs index 19f411f83..1e9619927 100644 --- a/src/modules/updates.rs +++ b/src/modules/updates.rs @@ -1,8 +1,10 @@ use crate::{ - components::divider, - components::icons::{StaticIcon, icon}, - components::spinning_icon::spinning_icon, - components::{IconPosition, MenuSize, styled_button}, + components::{ + IconPosition, MenuSize, ModuleContent, ModuleView, divider, + icons::{StaticIcon, icon}, + spinning_icon::spinning_icon, + styled_button, + }, config::UpdatesModuleConfig, t, theme::use_theme, @@ -169,7 +171,7 @@ impl Updates { } } - pub fn view(&'_ self) -> Element<'_, Message> { + pub fn view(&'_ self) -> ModuleView<'_, Message> { let (space, font_size, animated) = use_theme(|theme| (theme.space, theme.font_size, theme.animations_enabled)); let is_checking = matches!(self.state, State::Checking); @@ -192,7 +194,7 @@ impl Updates { content = content.push(text(self.updates.len())); } - content.into() + ModuleView::new(ModuleContent::Element(content.into())) } pub fn menu_view<'a>(&'a self, id: SurfaceId) -> Element<'a, Message> { diff --git a/src/modules/window_title.rs b/src/modules/window_title.rs index 147bd529a..ce5218db5 100644 --- a/src/modules/window_title.rs +++ b/src/modules/window_title.rs @@ -1,11 +1,12 @@ use crate::{ + components::{ModuleContent, ModuleView}, config::{WindowTitleConfig, WindowTitleMode}, services::{ReadOnlyService, ServiceEvent, compositor::CompositorService}, theme::use_theme, utils::truncate_text, }; use iced::{ - Element, Subscription, + Subscription, widget::{container, text}, }; @@ -90,8 +91,8 @@ impl WindowTitle { self.value.clone() } - pub fn view(&'_ self, title: String) -> Element<'_, Message> { - use_theme(|theme| { + pub fn view(&'_ self, title: String) -> ModuleView<'_, Message> { + let element = use_theme(|theme| { container( text(title) .size(theme.font_size.sm) @@ -99,7 +100,9 @@ impl WindowTitle { ) .clip(true) .into() - }) + }); + + ModuleView::new(ModuleContent::Element(element)) } pub fn subscription(&self) -> Subscription { diff --git a/src/modules/workspaces.rs b/src/modules/workspaces.rs index 811c5963c..eef93d447 100644 --- a/src/modules/workspaces.rs +++ b/src/modules/workspaces.rs @@ -1,5 +1,5 @@ use crate::{ - components::icons::icon, + components::{ModuleContent, ModuleView, icons::icon}, config::{ AppearanceColor, InvertScrollDirection, ModuleName, WorkspaceIndicatorFormat, WorkspaceVisibilityMode, WorkspacesModuleConfig, @@ -509,7 +509,7 @@ impl Workspaces { } } - pub fn view<'a>(&'a self, id: SurfaceId, outputs: &Outputs) -> Element<'a, Message> { + pub fn view<'a>(&'a self, id: SurfaceId, outputs: &Outputs) -> ModuleView<'a, Message> { let monitor_name = outputs.get_monitor_name(id); let row = use_theme(|theme| { @@ -697,55 +697,55 @@ impl Workspaces { let scroll_monitor = monitor_name.map(str::to_owned); - MouseArea::new(row) - .on_scroll(move |direction| { - let scroll = |dir: i32| Message::Scroll(dir, scroll_monitor.clone()); - match direction { - iced::mouse::ScrollDelta::Lines { y, .. } => { - if y.is_sign_positive() { - match self.config.invert_scroll_direction { - Some(InvertScrollDirection::All | InvertScrollDirection::Mouse) => { - scroll(-1) - } - Some(InvertScrollDirection::Trackpad) => scroll(1), - None => scroll(1), + let element = MouseArea::new(row).on_scroll(move |direction| { + let scroll = |dir: i32| Message::Scroll(dir, scroll_monitor.clone()); + match direction { + iced::mouse::ScrollDelta::Lines { y, .. } => { + if y.is_sign_positive() { + match self.config.invert_scroll_direction { + Some(InvertScrollDirection::All | InvertScrollDirection::Mouse) => { + scroll(-1) } - } else { - match self.config.invert_scroll_direction { - Some(InvertScrollDirection::All | InvertScrollDirection::Mouse) => { - scroll(1) - } - Some(InvertScrollDirection::Trackpad) => scroll(-1), - None => scroll(-1), + Some(InvertScrollDirection::Trackpad) => scroll(1), + None => scroll(1), + } + } else { + match self.config.invert_scroll_direction { + Some(InvertScrollDirection::All | InvertScrollDirection::Mouse) => { + scroll(1) } + Some(InvertScrollDirection::Trackpad) => scroll(-1), + None => scroll(-1), } } - iced::mouse::ScrollDelta::Pixels { y, .. } => { - let sensibility = 3.; - - if self.scroll_accumulator.abs() < sensibility { - Message::ScrollAccumulator(y) - } else if self.scroll_accumulator.is_sign_positive() { - match self.config.invert_scroll_direction { - Some( - InvertScrollDirection::All | InvertScrollDirection::Trackpad, - ) => scroll(-1), - Some(InvertScrollDirection::Mouse) => scroll(1), - None => scroll(1), + } + iced::mouse::ScrollDelta::Pixels { y, .. } => { + let sensibility = 3.; + + if self.scroll_accumulator.abs() < sensibility { + Message::ScrollAccumulator(y) + } else if self.scroll_accumulator.is_sign_positive() { + match self.config.invert_scroll_direction { + Some(InvertScrollDirection::All | InvertScrollDirection::Trackpad) => { + scroll(-1) } - } else { - match self.config.invert_scroll_direction { - Some( - InvertScrollDirection::All | InvertScrollDirection::Trackpad, - ) => scroll(1), - Some(InvertScrollDirection::Mouse) => scroll(-1), - None => scroll(-1), + Some(InvertScrollDirection::Mouse) => scroll(1), + None => scroll(1), + } + } else { + match self.config.invert_scroll_direction { + Some(InvertScrollDirection::All | InvertScrollDirection::Trackpad) => { + scroll(1) } + Some(InvertScrollDirection::Mouse) => scroll(-1), + None => scroll(-1), } } } - }) - .into() + } + }); + + ModuleView::new(ModuleContent::Element(element.into())) } pub fn subscription(&self) -> Subscription { diff --git a/src/theme.rs b/src/theme.rs index c64b17d9a..d55a07885 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -723,18 +723,25 @@ impl AshellTheme { /// Module button style: transparent base with hover highlight. /// The module-group background is handled by `module_group`, not the button. - pub fn module_button_style( - &self, - no_hover: bool, - ) -> impl Fn(&Theme, Status) -> button::Style + use<> { - let (theme_radius, border, _module_padding) = - (self.radius, self.bar.module_border, self.space.xxs); + pub fn module_button_style(&self) -> impl Fn(&Theme, Status) -> button::Style + use<> { + let (theme_radius, border, _module_padding, module_opacity) = ( + self.radius, + self.bar.module_border, + self.space.xxs, + self.bar.opacity.module, + ); let radius = border.radius.resolve(theme_radius); let btn_opacity = self.bar.opacity.button; move |theme, status| { let mut base = button::Style { - background: None, + background: Some( + theme + .palette() + .background + .scale_alpha(module_opacity) + .into(), + ), border: Border { width: 0.0, radius, @@ -745,7 +752,7 @@ impl AshellTheme { }; match status { Status::Active => base, - Status::Hovered if !no_hover => { + Status::Hovered => { base.background = Some( theme .extended_palette() From fd27aae3f7e0346c45feaf923e5418d854dd24ca Mon Sep 17 00:00:00 2001 From: Sulieman <168682205+sulabi@users.noreply.github.com> Date: Sun, 9 Aug 2026 15:25:46 +0100 Subject: [PATCH 07/11] config: deprecate BarSurface for more in depth customisation --- src/app.rs | 54 +++++++++-------------- src/components/menu.rs | 12 ++---- src/components/module_group.rs | 78 ++++++++++------------------------ src/components/module_view.rs | 1 + src/config.rs | 52 +++++++++++++++++++---- src/main.rs | 4 +- src/modules/mod.rs | 35 +++++++++------ src/outputs.rs | 20 ++++----- src/theme.rs | 17 +++----- 9 files changed, 131 insertions(+), 142 deletions(-) diff --git a/src/app.rs b/src/app.rs index f08100358..c6adc9ad7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,7 @@ use crate::{ HEIGHT, components::{Centerbox, menu::MenuType}, - config::{self, BarSurface, Config, ModuleName, Modules, WorkspaceIndicatorFormat}, + config::{self, Config, ModuleName, Modules, WorkspaceIndicatorFormat}, get_log_spec, i18n::{Localizer, init_localizer}, ipc::IpcCommand, @@ -24,7 +24,7 @@ use crate::{ osd::{self, Osd}, outputs::{HasOutput, Outputs}, services::{ReadOnlyService, xdg_icons}, - theme::{AshellTheme, BarLayout, backdrop_color, darken_color, init_theme, use_theme}, + theme::{AshellTheme, BarLayout, darken_color, init_theme, use_theme}, }; use flexi_logger::LoggerHandle; use iced::futures::StreamExt; @@ -84,7 +84,7 @@ impl App { ) -> impl FnOnce() -> (Self, Task) { move || { let mut outputs = Outputs::new( - BarLayout::from_appearance(&config.appearance.bar), + BarLayout::new(config.appearance.bar), config.position, config.layer, config.appearance.scale_factor, @@ -238,7 +238,7 @@ impl App { ); let (bar_position, bar_layout, scale_factor) = use_theme(|t| (t.bar_position, t.bar_layout(), t.scale_factor)); - let new_layout = BarLayout::from_appearance(&config.appearance.bar); + let new_layout = BarLayout::new(config.appearance.bar); if self.general_config.outputs != config.outputs || bar_position != config.position || bar_layout != new_layout @@ -579,52 +579,40 @@ impl App { let (space, bar, menu, animations_enabled, theme_radius) = use_theme(|t| (t.space, t.bar, t.menu, t.animations_enabled, t.radius)); - let (bar_surface, bar_bg_opacity, bar_border) = - (bar.surface, bar.opacity.background, bar.border); + let (bar_bg_opacity, bar_border, bar_inset) = + (bar.opacity.background, bar.border, bar.inset); let radius = bar_border.radius.resolve(theme_radius); + let has_inset = bar_inset > 0.; + let centerbox = Centerbox::new([left, center, right]) .animated(animations_enabled) .spacing(space.xxs) .width(Length::Fill) .align_items(Alignment::Center) - .height(match bar_surface { - BarSurface::Transparent | BarSurface::Panel => HEIGHT, - _ => HEIGHT - space.xs as f64, + .height(if has_inset { + HEIGHT - space.xs as f64 + } else { + HEIGHT } as f32) - .padding(match bar_surface { - BarSurface::Transparent | BarSurface::Panel => [space.xxs, space.xxs], - _ => [0.0, 0.0], + .padding(if has_inset { + [0.0, 0.0] + } else { + [space.xxs, space.xxs] }); let menu_is_open = self.outputs.menu_is_open(); let status_bar = container(centerbox).style(move |t: &Theme| container::Style { - background: match bar_surface { - BarSurface::Solid => Some({ - let bg = t.palette().background.scale_alpha(bar_bg_opacity); - if menu_is_open { - darken_color(bg, menu.backdrop) - } else { - bg - } - .into() - }), - BarSurface::Transparent => { - if menu_is_open { - Some(backdrop_color(menu.backdrop).into()) - } else { - None - } - } - BarSurface::Panel => Some({ - let bg = t.palette().background.scale_alpha(bar_bg_opacity); + background: { + let bg = t.palette().background.scale_alpha(bar_bg_opacity); + Some( if menu_is_open { darken_color(bg, menu.backdrop) } else { bg } - .into() - }), + .into(), + ) }, border: iced::Border { radius, diff --git a/src/components/menu.rs b/src/components/menu.rs index a61651c0b..445900f1a 100644 --- a/src/components/menu.rs +++ b/src/components/menu.rs @@ -1,6 +1,6 @@ use crate::app::{self, App}; use crate::components::{self, ButtonUIRef}; -use crate::config::{BarSurface, Position}; +use crate::config::Position; use crate::theme::{backdrop_color, use_theme}; use iced::alignment::Vertical; use iced::widget::container::Style; @@ -298,13 +298,13 @@ impl App { content: Element<'a, app::Message>, button_ui_ref: ButtonUIRef, ) -> Element<'a, app::Message> { - let (space, menu_opacity, radius, bar_surface, bar_position, menu_backdrop) = + let (space, menu_opacity, radius, bar_inset, bar_position, menu_backdrop) = use_theme(|t| { ( t.space, t.menu.opacity, t.radius, - t.bar.surface, + t.bar.inset, t.bar_position, t.menu.backdrop, ) @@ -332,11 +332,7 @@ impl App { .into(), ) .padding({ - let v_padding = match bar_surface { - BarSurface::Solid => 2, - BarSurface::Transparent | BarSurface::Panel => 0, - }; - + let v_padding = if bar_inset > 0. { 2 } else { 0 }; Padding::new(0.) .top(if bar_position == Position::Top { v_padding diff --git a/src/components/module_group.rs b/src/components/module_group.rs index b47ac261e..78b51764d 100644 --- a/src/components/module_group.rs +++ b/src/components/module_group.rs @@ -1,22 +1,14 @@ -use crate::{ - config::{BarSurface, ModuleAppearance}, - theme::use_theme, -}; +use crate::{config::ModuleAppearance, theme::use_theme}; use iced::{Border, Element, widget::container}; -/// Wraps content with the appropriate bar surface container. -/// -/// - `Solid` → pass through as-is (the bar itself carries the background) -/// - `Transparent` → wrap in a container with background color + rounded border -/// - `Panel` → wrap in a container with customisation from config +/// Wraps content in a container styled from theme pub fn module_group<'a, Msg: 'static>( content: Element<'a, Msg>, - module_apperance: Option<&ModuleAppearance>, + module_apperance: &ModuleAppearance, ) -> Element<'a, Msg> { - let (bar_surface, theme_space, theme_radius, module_opacity, module_border, _module_padding) = + let (theme_space, theme_radius, module_opacity, module_border, _module_padding) = use_theme(|theme| { ( - theme.bar.surface, theme.space, theme.radius, theme.bar.opacity.module, @@ -26,48 +18,24 @@ pub fn module_group<'a, Msg: 'static>( }); let radius = module_border.radius.resolve(theme_radius); + let padding = theme_space.resolve(module_apperance.padding); - match bar_surface { - BarSurface::Solid => content, - // BarSurface::Transparent => container(content) - // .padding( - // module_apperance.map_or(0., |appearance| theme_space.resolve(appearance.padding)), - // ) - // .style(move |iced_theme: &iced::Theme| container::Style { - // background: Some( - // iced_theme - // .palette() - // .background - // .scale_alpha(module_opacity) - // .into(), - // ), - // border: Border { - // width: 0.0, - // radius, - // color: Color::TRANSPARENT, - // }, - // ..container::Style::default() - // }) - // .into(), - BarSurface::Panel | BarSurface::Transparent => container(content) - .padding( - module_apperance.map_or(0., |appearance| theme_space.resolve(appearance.padding)), - ) - .style(move |iced_theme: &iced::Theme| container::Style { - background: Some( - iced_theme - .palette() - .background - .scale_alpha(module_opacity) - .into(), - ), - border: Border { - width: module_border.width, - radius, - color: module_border.color.get_base(), - }, - ..container::Style::default() - }) - .into(), - } + container(content) + .padding(padding) + .style(move |iced_theme: &iced::Theme| container::Style { + background: Some( + iced_theme + .palette() + .background + .scale_alpha(module_opacity) + .into(), + ), + border: Border { + width: module_border.width, + radius, + color: module_border.color.get_base(), + }, + ..container::Style::default() + }) + .into() } diff --git a/src/components/module_view.rs b/src/components/module_view.rs index d601e1edf..0715fc46c 100644 --- a/src/components/module_view.rs +++ b/src/components/module_view.rs @@ -92,6 +92,7 @@ pub struct ModuleRow<'a, Msg> { } impl<'a, Msg: 'a> ModuleRow<'a, Msg> { + #[allow(unused)] pub fn new() -> Self { Self { children: Vec::new(), diff --git a/src/config.rs b/src/config.rs index f9f1c7a6d..9840d6722 100644 --- a/src/config.rs +++ b/src/config.rs @@ -92,6 +92,31 @@ impl Config { self.system_info.validate(); self.settings.validate(); } + + fn translate_deprecated(&mut self) { + if let Some(surface) = self.appearance.bar.surface { + let bar_appearance = &mut self.appearance.bar; + + log::warn!( + "`appearance.bar.surface` is deprecated. \ + configure `appearance.bar` directly instead — see the docs for equivalents." + ); + + match surface { + BarSurface::Solid => { + bar_appearance.opacity.module = 0.; + bar_appearance.module_border.width = 0.; + bar_appearance.inset = 8.; + } + BarSurface::Transparent => { + bar_appearance.opacity.background = 0.; + bar_appearance.inset = 0.; + } + } + } + + // other deprecated stuffs + } } #[derive(Deserialize, Clone, Debug)] @@ -776,7 +801,7 @@ fn hex_to_pair(hex: HexColor, text: Option, text_fallback: Color) -> p ) } -#[derive(Deserialize, Clone, Copy, Debug)] +#[derive(Deserialize, Clone, Copy, Debug, PartialEq)] #[serde(untagged)] pub enum AppearanceColor { Simple(HexColor), @@ -900,7 +925,6 @@ pub enum BarSurface { #[default] Transparent, Solid, - Panel, } #[derive(Deserialize, Default, Copy, Clone, Eq, PartialEq, Debug)] @@ -1016,7 +1040,7 @@ impl<'de> Deserialize<'de> for BarMargin { } } -#[derive(Deserialize, Clone, Copy, Debug)] +#[derive(Deserialize, Clone, Copy, Debug, PartialEq)] #[serde(default)] pub struct BorderAppearance { pub radius: BarRadius, @@ -1034,7 +1058,7 @@ impl Default for BorderAppearance { } } -#[derive(Deserialize, Clone, Copy, Debug)] +#[derive(Deserialize, Clone, Copy, Debug, PartialEq)] #[serde(default)] pub struct OpacityAppearance { #[serde(deserialize_with = "opacity_deserializer")] @@ -1054,16 +1078,22 @@ impl Default for OpacityAppearance { } } -#[derive(Deserialize, Clone, Copy, Debug)] +#[derive(Deserialize, Clone, Copy, Debug, PartialEq)] #[serde(default)] pub struct BarAppearance { - pub surface: BarSurface, pub opacity: OpacityAppearance, pub module_border: BorderAppearance, pub border: BorderAppearance, pub margin: BarMargin, + + pub inset: f32, + + /// Deprecated - set bar appearance directly instead. + pub surface: Option, } +impl BarAppearance {} + impl Default for BarAppearance { fn default() -> Self { Self { @@ -1071,10 +1101,11 @@ impl Default for BarAppearance { radius: BarRadius::new(RadiusSize::None), ..Default::default() }, - surface: BarSurface::default(), opacity: OpacityAppearance::default(), module_border: BorderAppearance::default(), margin: BarMargin::default(), + inset: 0.0, + surface: None, } } } @@ -1117,7 +1148,7 @@ impl Default for ModuleAppearance { fn default() -> Self { Self { spacing: SpaceSize::Xs, - padding: SpaceSize::Xxs, + padding: SpaceSize::None, grouping: ModuleGroup::default(), } } @@ -1141,6 +1172,7 @@ pub struct Appearance { pub special_workspace_colors: Option>, pub modules: HashMap, + pub module: ModuleAppearance, } static PRIMARY: HexColor = HexColor::rgb(122, 162, 247); @@ -1196,6 +1228,7 @@ impl Default for Appearance { scale_factor: 1.0, bar: BarAppearance::default(), menu: MenuAppearance::default(), + module: ModuleAppearance::default(), modules: HashMap::from([ (ModuleName::Settings, ModuleAppearance::default()), ( @@ -1525,6 +1558,9 @@ fn read_config(path: &Path) -> Result> { info!("Config file loaded successfully"); let mut config: Config = config; config.validate(); + + config.translate_deprecated(); + Ok(config) } Err(e) => { diff --git a/src/main.rs b/src/main.rs index 723742a13..d76beb0db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -128,8 +128,8 @@ fn main() -> iced::Result { Font::DEFAULT }; - let bar_layout = BarLayout::from_appearance(&config.appearance.bar); - let height = Outputs::get_height(bar_layout.surface, config.appearance.scale_factor); + let bar_layout = BarLayout::new(config.appearance.bar); + let height = Outputs::get_height(bar_layout.appearance.inset, config.appearance.scale_factor); let iced_layer = match config.layer { config::Layer::Top => Layer::Top, diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 1b2c253bb..2c9325c84 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -3,7 +3,7 @@ use crate::{ components::{ ModuleItem, ModuleResult, animated_size, menu::MenuType, module_group, module_item, }, - config::{ModuleAppearance, ModuleDef, ModuleGroup, ModuleName}, + config::{ModuleDef, ModuleGroup, ModuleName}, theme::use_theme, }; use iced::{Alignment, Element, Length, Subscription, SurfaceId, widget::Row}; @@ -148,7 +148,7 @@ impl App { fn build_module_item<'a>( &'a self, id: SurfaceId, - module_name: &'a ModuleName, + _module_name: &'a ModuleName, // we can use to customise more in the future content: Element<'a, Message>, action: Option, ) -> Element<'a, Message> { @@ -170,11 +170,12 @@ impl App { id: SurfaceId, module_name: &'a ModuleName, ) -> Option> { - let module_appearances = use_theme(|t| t.modules.clone()); - let module_appearance = module_appearances.get(module_name); - let grouping = module_appearance - .unwrap_or(&ModuleAppearance::default()) - .grouping; + let (module_appearances, module_appearance) = + use_theme(|t| (t.modules.clone(), t.module.clone())); + let module_appearance = module_appearances + .get(module_name) + .unwrap_or(&module_appearance); + let grouping = module_appearance.grouping; self.get_module_view(id, module_name).map(|module_result| { let ModuleResult { @@ -205,7 +206,8 @@ impl App { id: SurfaceId, group: &'a [ModuleName], ) -> Option> { - let module_appearances = use_theme(|t| t.modules.clone()); + let (module_appearances, module_appearance) = + use_theme(|t| (t.modules.clone(), t.module.clone())); let module_items: Vec<_> = group .iter() @@ -223,10 +225,12 @@ impl App { action, view: content, } = module_result; - let module_appearance = module_appearances.get(module_name); - let grouping = module_appearance - .unwrap_or(&ModuleAppearance::default()) - .grouping; + + let module_appearance = module_appearances + .get(module_name) + .unwrap_or(&module_appearance); + + let grouping = module_appearance.grouping; match grouping { ModuleGroup::Individual => { @@ -239,15 +243,18 @@ impl App { ModuleGroup::Combined | ModuleGroup::None => { let item = self.build_module_item(id, module_name, content.into_element(), action); - module_group(item, module_appearance) + // module_group(item, module_appearance) + // we should allow more customisation but for now leave it + item } } }) .collect::>(); let row = Row::with_children(items); + // println!("group for {:?}", group); - Some(row.into()) + Some(module_group(row.into(), &module_appearance)) } fn get_module_view<'a>( diff --git a/src/outputs.rs b/src/outputs.rs index 637ca00a5..521bc89ce 100644 --- a/src/outputs.rs +++ b/src/outputs.rs @@ -9,7 +9,7 @@ use crate::{ HEIGHT, components::ButtonUIRef, components::menu::{Menu, MenuType, OpenMenu}, - config::{self, BarSurface, Position}, + config::{self, Position}, theme::BarLayout, }; @@ -141,13 +141,8 @@ impl Outputs { Menu::with_animations(self.animations_enabled) } - pub fn get_height(surface: BarSurface, scale_factor: f64) -> f64 { - (HEIGHT - - match surface { - BarSurface::Solid => 8., - BarSurface::Transparent | BarSurface::Panel => 0., - }) - * scale_factor + pub fn get_height(inset: f32, scale_factor: f64) -> f64 { + (HEIGHT - inset as f64) * scale_factor } /// Layer-shell outer margin scaled to physical pixels, ordered @@ -161,7 +156,7 @@ impl Outputs { /// Space reserved on the anchored edge: the bar height plus the margin that /// pushes the bar away from that edge. pub fn exclusive_zone(layout: BarLayout, position: Position, scale_factor: f64) -> i32 { - let height = Self::get_height(layout.surface, scale_factor); + let height = Self::get_height(layout.appearance.inset, scale_factor); let (top, _, bottom, _) = Self::margin(layout, scale_factor); height as i32 + match position { @@ -177,7 +172,7 @@ impl Outputs { layer: config::Layer, scale_factor: f64, ) -> (SurfaceId, Task) { - let height = Self::get_height(layout.surface, scale_factor); + let height = Self::get_height(layout.appearance.inset, scale_factor); let iced_layer = match layer { config::Layer::Top => Layer::Top, @@ -552,7 +547,7 @@ impl Outputs { ); shell_info.layout = layout; shell_info.scale_factor = scale_factor; - let height = Self::get_height(layout.surface, scale_factor); + let height = Self::get_height(layout.appearance.inset, scale_factor); tasks.push(Task::batch(vec![ set_size(shell_info.id, (0, height as u32)), set_exclusive_zone( @@ -845,7 +840,8 @@ impl Outputs { if *oid == Some(target) { info.as_ref().and_then(|i| { i.output_logical_height.map(|h| { - let bar = Self::get_height(i.layout.surface, i.scale_factor) as u32; + let bar = + Self::get_height(i.layout.appearance.inset, i.scale_factor) as u32; h.saturating_sub(bar) }) }) diff --git a/src/theme.rs b/src/theme.rs index d55a07885..493ec6d5e 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -3,8 +3,8 @@ use std::{cell::RefCell, collections::HashMap}; use crate::{ components::button::{ButtonHierarchy, ButtonKind}, config::{ - Appearance, AppearanceColor, BackgroundLevel, BarAppearance, BarMargin, BarSurface, - MenuAppearance, ModuleAppearance, ModuleName, Position, RadiusSize, SpaceSize, + Appearance, AppearanceColor, BackgroundLevel, BarAppearance, MenuAppearance, + ModuleAppearance, ModuleName, Position, RadiusSize, SpaceSize, }, }; use iced::{ @@ -106,20 +106,17 @@ impl Radius { /// ordered `(top, right, bottom, left)`. #[derive(Debug, Clone, Copy, PartialEq)] pub struct BarLayout { - pub surface: BarSurface, pub margin: (f32, f32, f32, f32), + pub appearance: BarAppearance, } impl BarLayout { - pub fn from_appearance(bar: &BarAppearance) -> Self { - Self::new(bar.surface, bar.margin) - } - - fn new(surface: BarSurface, margin: BarMargin) -> Self { + pub fn new(appearance: BarAppearance) -> Self { + let margin = appearance.margin; let space = Space::default(); Self { - surface, + appearance, margin: ( space.resolve(margin.top), space.resolve(margin.right), @@ -326,7 +323,7 @@ impl AshellTheme { } pub fn bar_layout(&self) -> BarLayout { - BarLayout::new(self.bar.surface, self.bar.margin) + BarLayout::new(self.bar) } pub fn button_style( From 0eea3919088ed4c12eb8b5d6df5b1e087aa2a494 Mon Sep 17 00:00:00 2001 From: Sulieman <168682205+sulabi@users.noreply.github.com> Date: Sun, 9 Aug 2026 16:27:28 +0100 Subject: [PATCH 08/11] change default to transparent as it should --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index e2a8b1bae..8e9f15840 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1072,7 +1072,7 @@ impl Default for OpacityAppearance { fn default() -> Self { Self { button: default_opacity(), - background: default_opacity(), + background: 0., module: default_opacity(), } } From 9d4cef0e877e39df5646538c1dc2a87b77537466 Mon Sep 17 00:00:00 2001 From: Sulieman <168682205+sulabi@users.noreply.github.com> Date: Tue, 11 Aug 2026 12:01:04 +0100 Subject: [PATCH 09/11] fix some bugs and clean up --- src/components/module_view.rs | 8 ++--- src/config.rs | 55 +++++++++++++++-------------------- src/modules/mod.rs | 21 +++++-------- src/modules/settings/mod.rs | 8 ++--- src/modules/system_info.rs | 14 ++++----- src/modules/tempo/mod.rs | 9 ++++-- src/modules/workspaces.rs | 7 ++--- src/theme.rs | 14 +++++++-- 8 files changed, 63 insertions(+), 73 deletions(-) diff --git a/src/components/module_view.rs b/src/components/module_view.rs index 0715fc46c..b809b56f2 100644 --- a/src/components/module_view.rs +++ b/src/components/module_view.rs @@ -97,8 +97,8 @@ impl<'a, Msg: 'a> ModuleRow<'a, Msg> { Self { children: Vec::new(), spacing: 0.0, - align_y: Vertical::Top, - height: Length::Shrink, + align_y: Vertical::Center, + height: Length::Fill, } } @@ -119,8 +119,8 @@ impl<'a, Msg: 'a> ModuleRow<'a, Msg> { Self { children: Vec::with_capacity(capacity), spacing: 0.0, - align_y: Vertical::Top, - height: Length::Shrink, + align_y: Vertical::Center, + height: Length::Fill, } } diff --git a/src/config.rs b/src/config.rs index 8e9f15840..79f541de5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -107,6 +107,7 @@ impl Config { bar_appearance.opacity.module = 0.; bar_appearance.module_border.width = 0.; bar_appearance.inset = 8.; + bar_appearance.opacity.background = 1.; } BarSurface::Transparent => { bar_appearance.opacity.background = 0.; @@ -1136,20 +1137,22 @@ pub enum ModuleGroup { None, // no containers } -#[derive(Deserialize, Clone, Debug)] +#[derive(Deserialize, Copy, Clone, Debug)] #[serde(default)] pub struct ModuleAppearance { pub spacing: SpaceSize, pub grouping: ModuleGroup, pub padding: SpaceSize, + pub border: BorderAppearance } impl Default for ModuleAppearance { fn default() -> Self { Self { - spacing: SpaceSize::Xs, + spacing: SpaceSize::Xxs, padding: SpaceSize::None, grouping: ModuleGroup::default(), + border: BorderAppearance::default() } } } @@ -1255,23 +1258,13 @@ impl Default for Appearance { bar: BarAppearance::default(), menu: MenuAppearance::default(), module: ModuleAppearance::default(), - modules: HashMap::from([ - (ModuleName::Settings, ModuleAppearance::default()), - ( - ModuleName::SystemInfo, - ModuleAppearance { - spacing: SpaceSize::Xxs, - ..Default::default() - }, - ), - ( - ModuleName::Workspaces, - ModuleAppearance { - spacing: SpaceSize::Xxs, - ..Default::default() - }, - ), - ]), + modules: HashMap::from([( + ModuleName::Settings, + ModuleAppearance { + spacing: SpaceSize::Xs, + ..Default::default() + }, + )]), background_color: BackgroundAppearanceColor::Complete { base: HexColor::rgb(26, 27, 38), weakest: None, @@ -1346,18 +1339,18 @@ impl<'de> Deserialize<'de> for ModuleName { E: serde::de::Error, { Ok(match value { - "Updates" => ModuleName::Updates, - "Workspaces" => ModuleName::Workspaces, - "WindowTitle" => ModuleName::WindowTitle, - "SystemInfo" => ModuleName::SystemInfo, - "KeyboardLayout" => ModuleName::KeyboardLayout, - "KeyboardSubmap" => ModuleName::KeyboardSubmap, - "Tray" => ModuleName::Tray, - "Notifications" => ModuleName::Notifications, - "Tempo" => ModuleName::Tempo, - "Privacy" => ModuleName::Privacy, - "Settings" => ModuleName::Settings, - "MediaPlayer" => ModuleName::MediaPlayer, + "updates" | "Updates" => ModuleName::Updates, + "workspaces" | "Workspaces" => ModuleName::Workspaces, + "window_title" | "WindowTitle" => ModuleName::WindowTitle, + "system_info" | "SystemInfo" => ModuleName::SystemInfo, + "keyboard_layout" | "KeyboardLayout" => ModuleName::KeyboardLayout, + "keyboard_submap" | "KeyboardSubmap" => ModuleName::KeyboardSubmap, + "tray" | "Tray" => ModuleName::Tray, + "notifications" | "Notifications" => ModuleName::Notifications, + "tempo" | "Tempo" => ModuleName::Tempo, + "privacy" | "Privacy" => ModuleName::Privacy, + "settings" | "Settings" => ModuleName::Settings, + "media_player" | "MediaPlayer" => ModuleName::MediaPlayer, other => ModuleName::Custom(other.to_string()), }) } diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 2c9325c84..62ee27607 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -170,11 +170,7 @@ impl App { id: SurfaceId, module_name: &'a ModuleName, ) -> Option> { - let (module_appearances, module_appearance) = - use_theme(|t| (t.modules.clone(), t.module.clone())); - let module_appearance = module_appearances - .get(module_name) - .unwrap_or(&module_appearance); + let module_appearance = use_theme(|t| t.module_appearance()(module_name)); let grouping = module_appearance.grouping; self.get_module_view(id, module_name).map(|module_result| { @@ -195,7 +191,7 @@ impl App { ModuleGroup::None | ModuleGroup::Combined => { let item = self.build_module_item(id, module_name, content.into_element(), action); - module_group(item, module_appearance) + module_group(item, &module_appearance) } } }) @@ -206,8 +202,8 @@ impl App { id: SurfaceId, group: &'a [ModuleName], ) -> Option> { - let (module_appearances, module_appearance) = - use_theme(|t| (t.modules.clone(), t.module.clone())); + let (module_apperance, default_appearance) = + use_theme(|t| (t.module_appearance(), t.module)); let module_items: Vec<_> = group .iter() @@ -226,11 +222,9 @@ impl App { view: content, } = module_result; - let module_appearance = module_appearances - .get(module_name) - .unwrap_or(&module_appearance); + let appearance = module_apperance(module_name); - let grouping = module_appearance.grouping; + let grouping = appearance.grouping; match grouping { ModuleGroup::Individual => { @@ -252,9 +246,8 @@ impl App { .collect::>(); let row = Row::with_children(items); - // println!("group for {:?}", group); - Some(module_group(row.into(), &module_appearance)) + Some(module_group(row.into(), &default_appearance)) } fn get_module_view<'a>( diff --git a/src/modules/settings/mod.rs b/src/modules/settings/mod.rs index 5b92fb470..0d533eb56 100644 --- a/src/modules/settings/mod.rs +++ b/src/modules/settings/mod.rs @@ -754,11 +754,9 @@ impl Settings { } pub fn view<'a>(&'a self, id: SurfaceId) -> ModuleView<'a, Message> { - let (theme_space, spacing_size) = use_theme(|t| { - let module_appearance = t.modules.get(&ModuleName::Settings).unwrap_or(&t.module); - (t.space, module_appearance.spacing) - }); - let space = theme_space.resolve(spacing_size); + let (theme_space, appearance) = + use_theme(|t| (t.space, t.module_appearance()(&ModuleName::Settings))); + let space = theme_space.resolve(appearance.spacing); let mut row: Vec> = Vec::with_capacity(self.indicators.len()); diff --git a/src/modules/system_info.rs b/src/modules/system_info.rs index 1a240cae2..e5b581d94 100644 --- a/src/modules/system_info.rs +++ b/src/modules/system_info.rs @@ -4,7 +4,7 @@ use crate::{ icons::{StaticIcon, icon}, }, config::{ - CpuFormat, DiskFormat, MemoryFormat, ModuleAppearance, ModuleName, SystemInfoIndicator, + CpuFormat, DiskFormat, MemoryFormat, ModuleName, SystemInfoIndicator, SystemInfoModuleConfig, SystemInfoTemperature, TemperatureSensor, TemperatureSensorType, }, i18n::{UnitSystem, unit_system}, @@ -638,12 +638,9 @@ impl SystemInfo { } pub fn view<'a>(&'a self) -> ModuleView<'a, Message> { - let (theme_space, module_appearances) = use_theme(|t| (t.space, t.modules.clone())); - let binding = &ModuleAppearance::default(); - let module_appearance = module_appearances - .get(&ModuleName::SystemInfo) - .unwrap_or(binding); - let space_sizing = module_appearance.spacing; + let (theme_space, appearance) = + use_theme(|t| (t.space, t.module_appearance()(&ModuleName::SystemInfo))); + let space_sizing = appearance.spacing; let space = theme_space.resolve(space_sizing); @@ -785,8 +782,7 @@ impl SystemInfo { ModuleView::new(ModuleContent::Row( ModuleRow::with_children(indicators) .spacing(space) - .align_y(Alignment::Center) - .height(Length::Fill), + .align_y(Alignment::Center), )) } diff --git a/src/modules/tempo/mod.rs b/src/modules/tempo/mod.rs index 88fe8344e..0c3791baa 100644 --- a/src/modules/tempo/mod.rs +++ b/src/modules/tempo/mod.rs @@ -16,7 +16,7 @@ use log::{debug, warn}; use self::weather::{Location, WeatherData, fetch_location, fetch_weather_data}; use crate::{ components::{MenuSize, ModuleRow, ModuleView}, - config::{TempoModuleConfig, WeatherIndicator}, + config::{ModuleName, TempoModuleConfig, WeatherIndicator}, i18n::{language_subtag, unit_system}, theme::use_theme, }; @@ -165,14 +165,17 @@ impl Tempo { } pub fn view(&'_ self) -> ModuleView<'_, Message> { - let space = use_theme(|t| t.space); + let (theme_space, appearance) = + use_theme(|t| (t.space, t.module_appearance()(&ModuleName::Tempo))); let display_text = self.time_str(self.current_format(), self.current_timezone_index, None); + let spacing = theme_space.resolve(appearance.spacing); + ModuleRow::with_capacity(2) .push(self.weather_indicator()) .push(text(display_text)) .align_y(Vertical::Center) - .spacing(space.sm) + .spacing(spacing) .into() } diff --git a/src/modules/workspaces.rs b/src/modules/workspaces.rs index eef93d447..732357959 100644 --- a/src/modules/workspaces.rs +++ b/src/modules/workspaces.rs @@ -513,11 +513,8 @@ impl Workspaces { let monitor_name = outputs.get_monitor_name(id); let row = use_theme(|theme| { - let module_appearance = theme - .modules - .get(&ModuleName::Workspaces) - .unwrap_or(&theme.module); - let (theme_space, space_sizing) = (theme.space, module_appearance.spacing); + let appearance = theme.module_appearance()(&ModuleName::Workspaces); + let (theme_space, space_sizing) = (theme.space, appearance.spacing); let space = theme_space.resolve(space_sizing); Row::with_children( diff --git a/src/theme.rs b/src/theme.rs index 2c29f321d..292809198 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,3 +1,4 @@ +use std::sync::Arc; use std::{cell::RefCell, collections::HashMap}; use crate::{ @@ -158,7 +159,7 @@ pub struct AshellTheme { pub iced_theme: Theme, pub space: Space, - pub modules: HashMap, + pub modules: Arc>, pub module: ModuleAppearance, pub bar: BarAppearance, @@ -294,6 +295,7 @@ fn base_theme_from_appearance( bar_position: Position, animations_enabled: bool, ) -> AshellTheme { + println!("called"); AshellTheme { space: Space::default(), radius: Radius::default(), @@ -303,7 +305,7 @@ fn base_theme_from_appearance( bar: appearance.bar, menu: appearance.menu, - modules: appearance.modules.clone(), + modules: Arc::new(appearance.modules.clone()), module: ModuleAppearance::default(), workspace_colors: appearance.workspace_colors.clone(), @@ -775,6 +777,14 @@ impl AshellTheme { } } } + + pub fn module_appearance(&self) -> impl Fn(&ModuleName) -> ModuleAppearance + use<> { + let modules = Arc::clone(&self.modules); + move |module_name| { + let module = modules.get(module_name); + module.copied().unwrap_or_default() + } + } } pub fn backdrop_color(backdrop: f32) -> Color { From 475599353456f7bb09a18890276a05fa3cb13239 Mon Sep 17 00:00:00 2001 From: Sulieman <168682205+sulabi@users.noreply.github.com> Date: Tue, 11 Aug 2026 14:53:05 +0100 Subject: [PATCH 10/11] more appearance config --- src/components/module_group.rs | 50 +++++++++++++------- src/components/module_item.rs | 11 +++-- src/config.rs | 10 +++- src/modules/mod.rs | 65 ++++++++++++++------------ src/theme.rs | 84 +++++++++++++++++++++++++--------- 5 files changed, 150 insertions(+), 70 deletions(-) diff --git a/src/components/module_group.rs b/src/components/module_group.rs index 7500e2870..1fb6e5962 100644 --- a/src/components/module_group.rs +++ b/src/components/module_group.rs @@ -1,4 +1,7 @@ -use crate::{config::ModuleAppearance, theme::use_theme}; +use crate::{ + config::{BorderAppearance, ModuleAppearance}, + theme::use_theme, +}; use iced::{ Border, Element, widget::{blur_container, container}, @@ -7,7 +10,7 @@ use iced::{ /// Wraps content in a container styled from theme pub fn module_group<'a, Msg: 'static>( content: Element<'a, Msg>, - module_apperance: &ModuleAppearance, + module_apperance: ModuleAppearance, ) -> Element<'a, Msg> { let (theme_space, theme_radius, module_opacity, module_border, blur) = use_theme(|theme| { ( @@ -19,23 +22,38 @@ pub fn module_group<'a, Msg: 'static>( ) }); - let radius = module_border.radius.resolve(theme_radius); - let padding = theme_space.resolve(module_apperance.padding); - - let style = move |iced_theme: &iced::Theme| container::Style { - background: Some( - iced_theme - .palette() - .background - .scale_alpha(module_opacity) - .into(), - ), - border: Border { + let border = module_apperance.border.map_or_else( + || Border { width: module_border.width, - radius, color: module_border.color.get_base(), + radius: module_border.radius.resolve(theme_radius), + }, + |BorderAppearance { + width, + radius, + color, + }| { + Border { + width, + radius: radius.resolve(theme_radius), + color: color.get_base(), + } }, - ..container::Style::default() + ); + let opacity = module_apperance.opacity.unwrap_or(module_opacity); + + let padding = theme_space.resolve(module_apperance.padding); + let style = move |iced_theme: &iced::Theme| { + let background = module_apperance.background.map_or_else( + || iced_theme.palette().background.scale_alpha(opacity), + |b| b.get_base().scale_alpha(opacity), + ); + + container::Style { + background: Some(background.into()), + border, + ..container::Style::default() + } }; if blur { diff --git a/src/components/module_item.rs b/src/components/module_item.rs index 05e6f69c8..163872ca2 100644 --- a/src/components/module_item.rs +++ b/src/components/module_item.rs @@ -1,4 +1,4 @@ -use crate::{components::position_button, theme::use_theme}; +use crate::{components::position_button, config::ModuleAppearance, theme::use_theme}; use iced::{Alignment, Element, Length, widget::container}; use super::ButtonUIRef; @@ -9,6 +9,7 @@ use super::ButtonUIRef; /// When no press handler is set, renders as a plain container. pub struct ModuleItem<'a, Msg> { content: Element<'a, Msg>, + appearance: Option, on_press: Option, on_press_with_position: Option Msg + 'a>>, on_right_press: Option, @@ -17,9 +18,13 @@ pub struct ModuleItem<'a, Msg> { on_scroll_down: Option, } -pub fn module_item<'a, Msg: 'static + Clone>(content: Element<'a, Msg>) -> ModuleItem<'a, Msg> { +pub fn module_item<'a, Msg: 'static + Clone>( + content: Element<'a, Msg>, + appearance: Option, +) -> ModuleItem<'a, Msg> { ModuleItem { content, + appearance, on_press: None, on_press_with_position: None, on_right_press: None, @@ -64,7 +69,7 @@ impl<'a, Msg: 'static + Clone> ModuleItem<'a, Msg> { impl<'a, Msg: 'static + Clone> From> for Element<'a, Msg> { fn from(item: ModuleItem<'a, Msg>) -> Self { let (space, module_button_style) = - use_theme(|theme| (theme.space, theme.module_button_style())); + use_theme(|theme| (theme.space, theme.module_button_style(item.appearance))); let has_action = item.on_press.is_some() || item.on_press_with_position.is_some(); diff --git a/src/config.rs b/src/config.rs index 79f541de5..3c9cba4b8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1143,7 +1143,10 @@ pub struct ModuleAppearance { pub spacing: SpaceSize, pub grouping: ModuleGroup, pub padding: SpaceSize, - pub border: BorderAppearance + pub border: Option, + pub background: Option, + pub opacity: Option, + pub text_color: AppearanceColor, } impl Default for ModuleAppearance { @@ -1152,7 +1155,10 @@ impl Default for ModuleAppearance { spacing: SpaceSize::Xxs, padding: SpaceSize::None, grouping: ModuleGroup::default(), - border: BorderAppearance::default() + border: None, + background: None, + opacity: None, + text_color: AppearanceColor::Simple(HexColor::rgb(169, 177, 214)), } } } diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 62ee27607..c9432daac 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -3,7 +3,7 @@ use crate::{ components::{ ModuleItem, ModuleResult, animated_size, menu::MenuType, module_group, module_item, }, - config::{ModuleDef, ModuleGroup, ModuleName}, + config::{ModuleAppearance, ModuleDef, ModuleGroup, ModuleName}, theme::use_theme, }; use iced::{Alignment, Element, Length, Subscription, SurfaceId, widget::Row}; @@ -148,7 +148,7 @@ impl App { fn build_module_item<'a>( &'a self, id: SurfaceId, - _module_name: &'a ModuleName, // we can use to customise more in the future + module_appearance: Option, content: Element<'a, Message>, action: Option, ) -> Element<'a, Message> { @@ -160,7 +160,7 @@ impl App { content }; - let item = module_item(content); + let item = module_item(content, module_appearance); self.apply_module_action(item, action, id).into() } @@ -173,28 +173,33 @@ impl App { let module_appearance = use_theme(|t| t.module_appearance()(module_name)); let grouping = module_appearance.grouping; - self.get_module_view(id, module_name).map(|module_result| { - let ModuleResult { - action, - view: content, - } = module_result; + self.get_module_view(id, module_name) + .map(move |module_result| { + let ModuleResult { + action, + view: content, + } = module_result; - match grouping { - ModuleGroup::Individual => { - let content = content.map_elements(|child| { - // maybe we could further edit individuals - self.build_module_item(id, module_name, child, action.clone()) - }); + match grouping { + ModuleGroup::Individual => { + let content = content.map_elements(|child| { + // maybe we could further edit individuals + self.build_module_item( + id, + Some(module_appearance), + child, + action.clone(), + ) + }); - content.into_element() - } - ModuleGroup::None | ModuleGroup::Combined => { - let item = - self.build_module_item(id, module_name, content.into_element(), action); - module_group(item, &module_appearance) + content.into_element() + } + ModuleGroup::None | ModuleGroup::Combined => { + let item = self.build_module_item(id, None, content.into_element(), action); + module_group(item, module_appearance) + } } - } - }) + }) } fn group_module_wrapper<'a>( @@ -202,7 +207,7 @@ impl App { id: SurfaceId, group: &'a [ModuleName], ) -> Option> { - let (module_apperance, default_appearance) = + let (module_appearance, default_appearance) = use_theme(|t| (t.module_appearance(), t.module)); let module_items: Vec<_> = group @@ -222,21 +227,25 @@ impl App { view: content, } = module_result; - let appearance = module_apperance(module_name); + let appearance = module_appearance(module_name); let grouping = appearance.grouping; match grouping { ModuleGroup::Individual => { let content = content.map_elements(|child| { - self.build_module_item(id, module_name, child, action.clone()) + self.build_module_item(id, Some(appearance), child, action.clone()) }); content.into_element() } ModuleGroup::Combined | ModuleGroup::None => { - let item = - self.build_module_item(id, module_name, content.into_element(), action); + let item = self.build_module_item( + id, + Some(appearance), + content.into_element(), + action, + ); // module_group(item, module_appearance) // we should allow more customisation but for now leave it item @@ -247,7 +256,7 @@ impl App { let row = Row::with_children(items); - Some(module_group(row.into(), &default_appearance)) + Some(module_group(row.into(), default_appearance)) } fn get_module_view<'a>( diff --git a/src/theme.rs b/src/theme.rs index 292809198..64392d333 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use std::{cell::RefCell, collections::HashMap}; +use crate::config::BorderAppearance; use crate::{ components::button::{ButtonHierarchy, ButtonKind}, config::{ @@ -732,45 +733,86 @@ impl AshellTheme { /// Module button style: transparent base with hover highlight. /// The module-group background is handled by `module_group`, not the button. - pub fn module_button_style(&self) -> impl Fn(&Theme, Status) -> button::Style + use<> { + pub fn module_button_style( + &self, + appearance: Option, + ) -> impl Fn(&Theme, Status) -> button::Style + use<> { let (theme_radius, border, _module_padding, module_opacity) = ( self.radius, self.bar.module_border, self.space.xxs, self.bar.opacity.module, ); - let radius = border.radius.resolve(theme_radius); + + let border = appearance.and_then(|a| a.border).map_or_else( + || Border { + width: 0.0, + radius: border.radius.resolve(theme_radius), + color: Color::TRANSPARENT, + }, + |BorderAppearance { + radius, + width, + color, + }| { + Border { + width, + color: color.get_base(), + radius: radius.resolve(theme_radius), + } + }, + ); let btn_opacity = self.bar.opacity.button; move |theme, status| { + let opacity = appearance.and_then(|a| a.opacity).unwrap_or(module_opacity); + + let background = Some(appearance.and_then(|a| a.background).map_or_else( + || theme.palette().background.scale_alpha(opacity).into(), + |background| background.get_base().scale_alpha(opacity).into(), + )); + + let text_color = appearance.map_or_else( + || theme.palette().text, + |appearance| appearance.text_color.get_base(), + ); + let mut base = button::Style { - background: Some( - theme - .palette() - .background - .scale_alpha(module_opacity) - .into(), - ), - border: Border { - width: 0.0, - radius, - color: Color::TRANSPARENT, - }, - text_color: theme.palette().text, + background, + border, + text_color, + ..button::Style::default() }; + match status { Status::Active => base, Status::Hovered => { base.background = Some( - theme - .extended_palette() - .background - .weak - .color - .scale_alpha(btn_opacity) + appearance + .and_then(|a| a.background) + .map_or_else( + || { + theme + .extended_palette() + .background + .weak + .color + .scale_alpha(btn_opacity) + }, + |background| { + background + .get_pair(BackgroundLevel::Weak, background.get_base()) + .map_or_else( + || theme.extended_palette().background.weak.color, + |c| c.color, + ) + .scale_alpha(btn_opacity) + }, + ) .into(), ); + base } _ => base, From 0b52a2b1872a9d713ad6faefd259aa10261fb668 Mon Sep 17 00:00:00 2001 From: Sulieman <168682205+sulabi@users.noreply.github.com> Date: Wed, 12 Aug 2026 16:17:23 +0100 Subject: [PATCH 11/11] fix some appearance bugs --- src/config.rs | 8 ++++---- src/modules/mod.rs | 30 +++++++++++++++++++++++------- src/theme.rs | 11 +++++------ 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/config.rs b/src/config.rs index 3c9cba4b8..3864d9f35 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1146,7 +1146,7 @@ pub struct ModuleAppearance { pub border: Option, pub background: Option, pub opacity: Option, - pub text_color: AppearanceColor, + pub text_color: Option, } impl Default for ModuleAppearance { @@ -1158,7 +1158,7 @@ impl Default for ModuleAppearance { border: None, background: None, opacity: None, - text_color: AppearanceColor::Simple(HexColor::rgb(169, 177, 214)), + text_color: None, } } } @@ -1181,7 +1181,7 @@ pub struct Appearance { pub special_workspace_colors: Option>, pub modules: HashMap, - pub module: ModuleAppearance, + pub grouped: ModuleAppearance, /// Blur the wallpaper behind ashell's translucent surfaces via /// `ext-background-effect-v1`. No-op where the protocol is unsupported. pub blur: BlurMode, @@ -1263,7 +1263,7 @@ impl Default for Appearance { scale_factor: 1.0, bar: BarAppearance::default(), menu: MenuAppearance::default(), - module: ModuleAppearance::default(), + grouped: ModuleAppearance::default(), modules: HashMap::from([( ModuleName::Settings, ModuleAppearance { diff --git a/src/modules/mod.rs b/src/modules/mod.rs index c9432daac..77399caed 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -194,10 +194,22 @@ impl App { content.into_element() } - ModuleGroup::None | ModuleGroup::Combined => { - let item = self.build_module_item(id, None, content.into_element(), action); + ModuleGroup::Combined => { + let item_appearance = ModuleAppearance { + opacity: Some(0.), + ..module_appearance + }; + let item = self.build_module_item( + id, + Some(item_appearance), + content.into_element(), + action, + ); module_group(item, module_appearance) } + ModuleGroup::None => { + self.build_module_item(id, None, content.into_element(), action) + } } }) } @@ -207,8 +219,8 @@ impl App { id: SurfaceId, group: &'a [ModuleName], ) -> Option> { - let (module_appearance, default_appearance) = - use_theme(|t| (t.module_appearance(), t.module)); + let (theme_space, module_appearance, group_appearance) = + use_theme(|t| (t.space, t.module_appearance(), t.grouped)); let module_items: Vec<_> = group .iter() @@ -239,7 +251,7 @@ impl App { content.into_element() } - ModuleGroup::Combined | ModuleGroup::None => { + ModuleGroup::Combined => { let item = self.build_module_item( id, Some(appearance), @@ -250,13 +262,17 @@ impl App { // we should allow more customisation but for now leave it item } + ModuleGroup::None => { + self.build_module_item(id, Some(appearance), content.into_element(), action) + } } }) .collect::>(); - let row = Row::with_children(items); + let spacing = theme_space.resolve(group_appearance.spacing); + let row = Row::with_children(items).spacing(spacing); - Some(module_group(row.into(), default_appearance)) + Some(module_group(row.into(), group_appearance)) } fn get_module_view<'a>( diff --git a/src/theme.rs b/src/theme.rs index 64392d333..9c3121464 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -161,7 +161,7 @@ pub struct AshellTheme { pub space: Space, pub modules: Arc>, - pub module: ModuleAppearance, + pub grouped: ModuleAppearance, pub bar: BarAppearance, @@ -307,7 +307,7 @@ fn base_theme_from_appearance( menu: appearance.menu, modules: Arc::new(appearance.modules.clone()), - module: ModuleAppearance::default(), + grouped: appearance.grouped, workspace_colors: appearance.workspace_colors.clone(), special_workspace_colors: appearance.special_workspace_colors.clone(), @@ -772,10 +772,9 @@ impl AshellTheme { |background| background.get_base().scale_alpha(opacity).into(), )); - let text_color = appearance.map_or_else( - || theme.palette().text, - |appearance| appearance.text_color.get_base(), - ); + let text_color = appearance + .and_then(|a| a.text_color) + .map_or_else(|| theme.palette().text, |text_color| text_color.get_base()); let mut base = button::Style { background,