From 2cd2d255d4d067eb125ab07b8e11b93eeedf1450 Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Sat, 19 Oct 2024 00:07:49 +0100 Subject: [PATCH] fix(workspaces): rewrite module to fix several small issues Rewrites the module code to be better structured, in a similar pattern to the launcher. The code is now more robust and more maintainable, yay! Fixes #705 Fixes an issue with moving favourite workspaces. Fixes an issue with workspace visible state being incorrect. Fixes an issue where the `inactive` class looked at hidden instead of closed favourites. --- src/clients/compositor/mod.rs | 12 +- src/gtk_helpers.rs | 7 + src/modules/workspaces.rs | 428 --------------------------- src/modules/workspaces/button.rs | 78 +++++ src/modules/workspaces/button_map.rs | 64 ++++ src/modules/workspaces/mod.rs | 378 +++++++++++++++++++++++ src/modules/workspaces/open_state.rs | 30 ++ 7 files changed, 561 insertions(+), 436 deletions(-) delete mode 100644 src/modules/workspaces.rs create mode 100644 src/modules/workspaces/button.rs create mode 100644 src/modules/workspaces/button_map.rs create mode 100644 src/modules/workspaces/mod.rs create mode 100644 src/modules/workspaces/open_state.rs diff --git a/src/clients/compositor/mod.rs b/src/clients/compositor/mod.rs index e03dbe4c..11b8653d 100644 --- a/src/clients/compositor/mod.rs +++ b/src/clients/compositor/mod.rs @@ -90,25 +90,21 @@ pub struct Workspace { /// Yes, this is the same signature as Option, but it's impl is a lot more suited for our case. #[derive(Debug, Copy, Clone)] pub enum Visibility { - Visible(bool), + Visible { focused: bool }, Hidden, } impl Visibility { pub fn visible() -> Self { - Self::Visible(false) + Self::Visible { focused: false } } pub fn focused() -> Self { - Self::Visible(true) - } - - pub fn is_visible(self) -> bool { - matches!(self, Self::Visible(_)) + Self::Visible { focused: true } } pub fn is_focused(self) -> bool { - if let Self::Visible(focused) = self { + if let Self::Visible { focused } = self { focused } else { false diff --git a/src/gtk_helpers.rs b/src/gtk_helpers.rs index 628ac3a6..ef39fb62 100644 --- a/src/gtk_helpers.rs +++ b/src/gtk_helpers.rs @@ -18,6 +18,9 @@ pub struct WidgetGeometry { pub trait IronbarGtkExt { /// Adds a new CSS class to the widget. fn add_class(&self, class: &str); + + /// Removes a CSS class from the widget + fn remove_class(&self, class: &str); /// Gets the geometry for the widget fn geometry(&self, orientation: Orientation) -> WidgetGeometry; @@ -32,6 +35,10 @@ impl> IronbarGtkExt for W { self.style_context().add_class(class); } + fn remove_class(&self, class: &str) { + self.style_context().remove_class(class); + } + fn geometry(&self, orientation: Orientation) -> WidgetGeometry { let allocation = self.allocation(); diff --git a/src/modules/workspaces.rs b/src/modules/workspaces.rs deleted file mode 100644 index 7fb01be2..00000000 --- a/src/modules/workspaces.rs +++ /dev/null @@ -1,428 +0,0 @@ -use crate::clients::compositor::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate}; -use crate::config::CommonConfig; -use crate::gtk_helpers::IronbarGtkExt; -use crate::image::new_icon_button; -use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; -use crate::{glib_recv, module_impl, send_async, spawn, try_send, Ironbar}; -use color_eyre::{Report, Result}; -use gtk::prelude::*; -use gtk::{Button, IconTheme}; -use serde::Deserialize; -use std::cmp::Ordering; -use std::collections::{HashMap, HashSet}; -use tokio::sync::mpsc::{Receiver, Sender}; -use tracing::{debug, trace, warn}; - -#[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -pub enum SortOrder { - /// Shows workspaces in the order they're added - Added, - /// Shows workspaces in numeric order. - /// Named workspaces are added to the end in alphabetical order. - Alphanumeric, -} - -impl Default for SortOrder { - fn default() -> Self { - Self::Alphanumeric - } -} - -#[derive(Debug, Deserialize, Clone)] -#[serde(untagged)] -#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -pub enum Favorites { - ByMonitor(HashMap>), - Global(Vec), -} - -impl Default for Favorites { - fn default() -> Self { - Self::Global(vec![]) - } -} - -#[derive(Debug, Deserialize, Clone)] -#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -pub struct WorkspacesModule { - /// Map of actual workspace names to custom names. - /// - /// Custom names can be [images](images). - /// - /// If a workspace is not present in the map, - /// it will fall back to using its actual name. - name_map: Option>, - - /// Workspaces which should always be shown. - /// This can either be an array of workspace names, - /// or a map of monitor names to arrays of workspace names. - /// - /// **Default**: `{}` - /// - /// # Example - /// - /// ```corn - /// // array format - /// { - /// type = "workspaces" - /// favorites = ["1", "2", "3"] - /// } - /// - /// // map format - /// { - /// type = "workspaces" - /// favorites.DP-1 = ["1", "2", "3"] - /// favorites.DP-2 = ["4", "5", "6"] - /// } - /// ``` - #[serde(default)] - favorites: Favorites, - - /// A list of workspace names to never show. - /// - /// This may be useful for scratchpad/special workspaces, for example. - /// - /// **Default**: `[]` - #[serde(default)] - hidden: Vec, - - /// Whether to display workspaces from all monitors. - /// When false, only shows workspaces on the current monitor. - /// - /// **Default**: `false` - #[serde(default = "crate::config::default_false")] - all_monitors: bool, - - /// The method used for sorting workspaces. - /// `added` always appends to the end, `alphanumeric` sorts by number/name. - /// - /// **Valid options**: `added`, `alphanumeric` - ///
- /// **Default**: `alphanumeric` - #[serde(default)] - sort: SortOrder, - - /// The size to render icons at (image icons only). - /// - /// **Default**: `32` - #[serde(default = "default_icon_size")] - icon_size: i32, - - /// See [common options](module-level-options#common-options). - #[serde(flatten)] - pub common: Option, -} - -const fn default_icon_size() -> i32 { - 32 -} - -/// Creates a button from a workspace -fn create_button( - name: &str, - visibility: Visibility, - name_map: &HashMap, - icon_theme: &IconTheme, - icon_size: i32, - tx: &Sender, -) -> Button { - let label = name_map.get(name).map_or(name, String::as_str); - - let button = new_icon_button(label, icon_theme, icon_size); - button.set_widget_name(name); - - let style_context = button.style_context(); - style_context.add_class("item"); - - if visibility.is_visible() { - style_context.add_class("visible"); - } - - if visibility.is_focused() { - style_context.add_class("focused"); - } - - if !visibility.is_visible() { - style_context.add_class("inactive"); - } - - { - let tx = tx.clone(); - let name = name.to_string(); - button.connect_clicked(move |_item| { - try_send!(tx, name.clone()); - }); - } - - button -} - -fn reorder_workspaces(container: >k::Box) { - let mut buttons = container - .children() - .into_iter() - .map(|child| (child.widget_name().to_string(), child)) - .collect::>(); - - buttons.sort_by(|(label_a, _), (label_b, _a)| { - match (label_a.parse::(), label_b.parse::()) { - (Ok(a), Ok(b)) => a.cmp(&b), - (Ok(_), Err(_)) => Ordering::Less, - (Err(_), Ok(_)) => Ordering::Greater, - (Err(_), Err(_)) => label_a.cmp(label_b), - } - }); - - for (i, (_, button)) in buttons.into_iter().enumerate() { - container.reorder_child(&button, i as i32); - } -} - -fn find_btn(map: &HashMap, workspace: &Workspace) -> Option