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")] 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)] pub enum Favorites { ByMonitor(HashMap>), Global(Vec), } impl Default for Favorites { fn default() -> Self { Self::Global(vec![]) } } #[derive(Debug, Deserialize, Clone)] pub struct WorkspacesModule { /// Map of actual workspace names to custom names. name_map: Option>, /// Array of always shown workspaces, and what monitor to show on #[serde(default)] favorites: Favorites, /// List of workspace names to never show #[serde(default)] hidden: Vec, /// Whether to display buttons for all monitors. #[serde(default = "crate::config::default_false")] all_monitors: bool, #[serde(default)] sort: SortOrder, #[serde(default = "default_icon_size")] icon_size: i32, #[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