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