//! Workspaces module: renders workspaces 1–10 as cubes, coloured and scaled by //! state — focused largest, existing mid, non-existent smallest. State arrives //! as `HyprlandEvent`s from the hyprland service. //! //! Ported from the Quickshell `Workspaces.qml`: constant 10×15 slot per //! workspace, corner radius 2.5, and the inactive cubes scaled about their //! centre (0.85 existing / 0.75 absent) so the shrink never shifts the row. use std::collections::BTreeSet; use common::{BarModule, HyprlandEvent, HyprlandMsg, ModuleEffect, Service, Wire}; use iced::border; use iced::theme::palette; use iced::widget::{container, row, space}; use iced::{window, Element, Length, Task, Theme}; use serde::Deserialize; use toml::Table; /// How far each state's cube fades from the theme text colour toward the /// theme background. Opaque (mixed, not alpha) so cubes stay solid over the /// transparent bar instead of washing out. const FADE_ACTIVE: f32 = 0.0; const FADE_EXISTS: f32 = 0.55; const FADE_ABSENT: f32 = 0.8; /// Look of the workspace row, from `[modules.workspaces]`; every field /// defaults to the value Quickshell's `Workspaces.qml` hardcodes, so an /// absent table (or absent key) renders identically to before. #[derive(Deserialize)] #[serde(default)] struct WorkspacesConfig { /// Number of slots to render (workspaces `1..=count`). count: i32, /// Horizontal gap between slots in logical px. gap: f32, /// Slot width in logical px — every workspace keeps this slot, so the /// unfocused shrink never moves the row. slot_w: f32, /// Slot height in logical px. slot_h: f32, /// Corner radius. radius: f32, /// Visual scale per state. scale_active: f32, scale_exists: f32, scale_absent: f32, } impl Default for WorkspacesConfig { fn default() -> Self { Self { count: 10, gap: 6.0, slot_w: 10.0, slot_h: 15.0, radius: 2.5, scale_active: 1.0, scale_exists: 0.85, scale_absent: 0.75, } } } fn config_workspaces(table: Option