From f09ffa7699cd3b5211c999f9462da338ead35492 Mon Sep 17 00:00:00 2001 From: Jas Singh Date: Sun, 15 Sep 2024 15:19:32 -0700 Subject: [PATCH] Implement custom icons per workspace. (#261) * Implement custom icons per workspace. * Finish custom workspace icon implementation * Remove unsupported color definition. --- README.md | 46 +++- lib/constants/colors.ts | 142 +++++++++++ lib/types/workspace.d.ts | 13 + lib/utils.ts | 23 ++ modules/bar/workspaces/index.ts | 257 +------------------- modules/bar/workspaces/utils.ts | 54 +++- modules/bar/workspaces/variants/default.ts | 76 ++++-- modules/bar/workspaces/variants/occupied.ts | 28 ++- options.ts | 4 + scss/style/bar/workspace.scss | 125 +++++----- widget/settings/pages/config/bar/index.ts | 18 ++ widget/settings/side_effects/index.ts | 11 +- 12 files changed, 437 insertions(+), 360 deletions(-) create mode 100644 lib/constants/colors.ts diff --git a/README.md b/README.md index 024bace..13eb8e0 100644 --- a/README.md +++ b/README.md @@ -11,21 +11,26 @@
# HyprPanel 🚀 + A panel built for Hyprland with [AGS](https://github.com/Aylur/ags) ![HyprPanel](./assets/HyprPanel.png) ## Installation + The [HyprPanel Wiki](https://hyprpanel.com/getting_started/installation.html) contains in depth instructions for installing the panel and all of its dependencies. The instructions below are general instructions for installing the panel. ## Requirements + Bun ```sh curl -fsSL https://bun.sh/install | bash && \ sudo ln -s $HOME/.bun/bin/bun /usr/local/bin/bun ``` + Additional dependencies: + ```sh pipewire libgtop @@ -45,6 +50,7 @@ gnome-bluetooth-3.0 ``` Optional Dependencies: + ```sh ## Used for Tracking GPU Usage in your Dashboard (NVidia only) python @@ -58,11 +64,13 @@ pacman-contrib ``` Arch (pacman): + ```bash sudo pacman -S pipewire libgtop bluez bluez-utils btop networkmanager dart-sass wl-clipboard brightnessctl swww python gnome-bluetooth-3.0 pacman-contrib ``` Arch (AUR): + ```bash yay -S grimblast-git gpu-screen-recorder hyprpicker matugen-bin python-gpustat aylurs-gtk-shell-git ``` @@ -72,32 +80,43 @@ For NixOS/Home-Manager, see [NixOS & Home-Manager instructions](#nixos--home-man ## Instructions ### AGS + Once everything is installed you need to put the contents of this repo in `~/.config/ags`. If you already have something in `~/.config/ags`, it's recommended that you back it up with: + ```bash mv $HOME/.config/ags $HOME/.config/ags.bkup ``` + Otherwise you can use this command to install the panel: + ```bash git clone https://github.com/Jas-SinghFSU/HyprPanel.git && \ ln -s $(pwd)/HyprPanel $HOME/.config/ags ``` + ### Nerd Fonts + Additionally, you need to ensure that you have a [Nerd Font](https://www.nerdfonts.com/font-downloads) installed for your icons to render properly. ### Launch the panel + Afterwards you can run the panel with the following command in your terminal: + ```bash ags ``` Or you can add it to your Hyprland config (hyprland.conf) to auto-start with: + ```bash exec-once = ags ``` ### NixOS & Home-Manager + Alternatively, if you're using NixOS and/or Home-Manager, you can setup AGS using the provided Nix Flake. First, add the repository to your Flake's inputs, and enable the overlay. + ```nix # flake.nix @@ -105,7 +124,7 @@ Alternatively, if you're using NixOS and/or Home-Manager, you can setup AGS usin inputs.hyprpanel.url = "github:Jas-SinghFSU/HyprPanel"; # ... - outputs = { self, nixpkgs, ... }@inputs: + outputs = { self, nixpkgs, ... }@inputs: let # ... system = "x86_64-linux"; # change to whatever your system should be. @@ -175,6 +194,7 @@ The panel is automatically scaled based on your font size in `Configuration > Ge ### Specifying bar layouts per monitor To specify layouts for each monitor you can create a JSON object such as: + ```JSON { "0": { @@ -229,19 +249,21 @@ To specify layouts for each monitor you can create a JSON object such as: ``` Where each monitor is defined by its index (0, 1, 2 in this case) and each section (left, middle, right) contains one or more of the following modules: + ```js -"battery" -"dashboard" -"workspaces" -"windowtitle" -"media" -"notifications" -"volume" -"network" -"bluetooth" -"clock" -"systray" +'battery'; +'dashboard'; +'workspaces'; +'windowtitle'; +'media'; +'notifications'; +'volume'; +'network'; +'bluetooth'; +'clock'; +'systray'; ``` + Since the text-box in the options dialog isn't sufficient, it is recommended that you create this JSON configuration in a text editor elsewhere and paste it into the layout text-box under Configuration > Bar > "Bar Layouts for Monitors". ### Additional Configuration diff --git a/lib/constants/colors.ts b/lib/constants/colors.ts new file mode 100644 index 0000000..95ac5cf --- /dev/null +++ b/lib/constants/colors.ts @@ -0,0 +1,142 @@ +export const namedColors = new Set([ + 'alice blue', + 'antique white', + 'aqua', + 'aquamarine', + 'azure', + 'beige', + 'bisque', + 'black', + 'blanched almond', + 'blue', + 'blue violet', + 'brown', + 'burlywood', + 'cadet blue', + 'chartreuse', + 'chocolate', + 'coral', + 'cornflower blue', + 'cornsilk', + 'crimson', + 'cyan', + 'dark blue', + 'dark cyan', + 'dark goldenrod', + 'dark gray', + 'dark green', + 'dark khaki', + 'dark magenta', + 'dark olive green', + 'dark orange', + 'dark orchid', + 'dark red', + 'dark salmon', + 'dark sea green', + 'dark slate blue', + 'dark slate gray', + 'dark turquoise', + 'dark violet', + 'deep pink', + 'deep sky blue', + 'dim gray', + 'dodger blue', + 'firebrick', + 'floral white', + 'forest green', + 'fuchsia', + 'gainsboro', + 'ghost white', + 'gold', + 'goldenrod', + 'gray', + 'green', + 'green yellow', + 'honeydew', + 'hot pink', + 'indian red', + 'indigo', + 'ivory', + 'khaki', + 'lavender', + 'lavender blush', + 'lawn green', + 'lemon chiffon', + 'light blue', + 'light coral', + 'light cyan', + 'light goldenrod yellow', + 'light green', + 'light grey', + 'light pink', + 'light salmon', + 'light sea green', + 'light sky blue', + 'light slate gray', + 'light steel blue', + 'light yellow', + 'lime', + 'lime green', + 'linen', + 'magenta', + 'maroon', + 'medium aquamarine', + 'medium blue', + 'medium orchid', + 'medium purple', + 'medium sea green', + 'medium slate blue', + 'medium spring green', + 'medium turquoise', + 'medium violet red', + 'midnight blue', + 'mint cream', + 'misty rose', + 'moccasin', + 'navajo white', + 'navy', + 'old lace', + 'olive', + 'olive drab', + 'orange', + 'orange red', + 'orchid', + 'pale goldenrod', + 'pale green', + 'pale turquoise', + 'pale violet red', + 'papaya whip', + 'peach puff', + 'peru', + 'pink', + 'plum', + 'powder blue', + 'purple', + 'red', + 'rosy brown', + 'royal blue', + 'saddle brown', + 'salmon', + 'sandy brown', + 'sea green', + 'seashell', + 'sienna', + 'silver', + 'sky blue', + 'slate blue', + 'slate gray', + 'snow', + 'spring green', + 'steel blue', + 'tan', + 'teal', + 'thistle', + 'tomato', + 'turquoise', + 'violet', + 'wheat', + 'white', + 'white smoke', + 'yellow', + 'yellow green', +]); diff --git a/lib/types/workspace.d.ts b/lib/types/workspace.d.ts index afc1522..9c46c15 100644 --- a/lib/types/workspace.d.ts +++ b/lib/types/workspace.d.ts @@ -10,3 +10,16 @@ export type WorkspaceMap = { export type MonitorMap = { [key: number]: string; }; + +export type WorkspaceIcons = { + [key: string]: string; +}; + +export type WorkspaceIconsColored = { + [key: string]: { + color: string; + icon: string; + }; +}; + +export type WorkspaceIconMap = WorkspaceIcons | WorkspaceIconsColored; diff --git a/lib/utils.ts b/lib/utils.ts index 925f035..8c50008 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -10,6 +10,7 @@ import GdkPixbuf from 'gi://GdkPixbuf'; import { NotificationArgs } from 'types/utils/notify'; import { SubstituteKeys } from './types/utils'; import { Window } from 'types/@girs/gtk-3.0/gtk-3.0.cjs'; +import { namedColors } from './constants/colors'; export type Binding = import('types/service').Binding; @@ -166,3 +167,25 @@ export function getPosition(pos: NotificationAnchor | OSDAnchor): ('top' | 'bott return positionMap[pos] || ['top']; } +export const isValidGjsColor = (color: string): boolean => { + const colorLower = color.toLowerCase().trim(); + + if (namedColors.has(colorLower)) { + return true; + } + + const hexColorRegex = /^#(?:[a-fA-F0-9]{3,4}|[a-fA-F0-9]{6,8})$/; + + const rgbRegex = /^rgb\(\s*(\d{1,3}%?\s*,\s*){2}\d{1,3}%?\s*\)$/; + const rgbaRegex = /^rgba\(\s*(\d{1,3}%?\s*,\s*){3}(0|1|0?\.\d+)\s*\)$/; + + if (hexColorRegex.test(color)) { + return true; + } + + if (rgbRegex.test(colorLower) || rgbaRegex.test(colorLower)) { + return true; + } + + return false; +}; diff --git a/modules/bar/workspaces/index.ts b/modules/bar/workspaces/index.ts index 586c8b9..161d292 100644 --- a/modules/bar/workspaces/index.ts +++ b/modules/bar/workspaces/index.ts @@ -1,20 +1,10 @@ -const hyprland = await Service.import('hyprland'); import options from 'options'; -import { - createThrottledScrollHandlers, - getCurrentMonitorWorkspaces, - getWorkspaceRules, - getWorkspacesForMonitor, -} from './helpers'; -import { Workspace } from 'types/service/hyprland'; -import { BoxWidget } from 'lib/types/widget'; +import { createThrottledScrollHandlers, getCurrentMonitorWorkspaces } from './helpers'; import { BarBoxChild, SelfButton } from 'lib/types/bar'; +import { occupiedWses } from './variants/occupied'; +import { defaultWses } from './variants/default'; -const { workspaces, monitorSpecific, workspaceMask, scroll_speed, spacing } = options.bar.workspaces; - -function range(length: number, start = 1): number[] { - return Array.from({ length }, (_, i) => i + start); -} +const { workspaces, scroll_speed } = options.bar.workspaces; const Workspaces = (monitor = -1): BarBoxChild => { const currentMonitorWorkspaces = Variable(getCurrentMonitorWorkspaces(monitor)); @@ -23,249 +13,12 @@ const Workspaces = (monitor = -1): BarBoxChild => { currentMonitorWorkspaces.value = getCurrentMonitorWorkspaces(monitor); }); - const renderClassnames = ( - showIcons: boolean, - showNumbered: boolean, - numberedActiveIndicator: string, - i: number, - ): string => { - if (showIcons) { - return `workspace-icon txt-icon bar`; - } - if (showNumbered) { - const numActiveInd = hyprland.active.workspace.id === i ? numberedActiveIndicator : ''; - - return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`; - } - return 'default'; - }; - - const renderLabel = ( - showIcons: boolean, - available: string, - active: string, - occupied: string, - workspaceMask: boolean, - i: number, - index: number, - ): string => { - if (showIcons) { - if (hyprland.active.workspace.id === i) { - return active; - } - if ((hyprland.getWorkspace(i)?.windows || 0) > 0) { - return occupied; - } - if (monitor !== -1) { - return available; - } - } - return workspaceMask ? `${index + 1}` : `${i}`; - }; - const defaultWses = (): BoxWidget => { - return Widget.Box({ - children: Utils.merge( - [workspaces.bind('value'), monitorSpecific.bind()], - (workspaces: number, monitorSpecific: boolean) => { - return range(workspaces || 8) - .filter((i) => { - if (!monitorSpecific) { - return true; - } - const workspaceRules = getWorkspaceRules(); - return getWorkspacesForMonitor(i, workspaceRules, monitor); - }) - .sort((a, b) => { - return a - b; - }) - .map((i, index) => { - return Widget.Button({ - class_name: 'workspace-button', - on_primary_click: () => { - hyprland.messageAsync(`dispatch workspace ${i}`); - }, - child: Widget.Label({ - attribute: i, - vpack: 'center', - css: spacing.bind('value').as((sp) => `margin: 0rem ${0.375 * sp}rem;`), - class_name: Utils.merge( - [ - options.bar.workspaces.show_icons.bind('value'), - options.bar.workspaces.show_numbered.bind('value'), - options.bar.workspaces.numbered_active_indicator.bind('value'), - options.bar.workspaces.icons.available.bind('value'), - options.bar.workspaces.icons.active.bind('value'), - options.bar.workspaces.icons.occupied.bind('value'), - hyprland.active.workspace.bind('id'), - ], - ( - showIcons: boolean, - showNumbered: boolean, - numberedActiveIndicator: string, - ) => { - if (showIcons) { - return `workspace-icon txt-icon bar`; - } - if (showNumbered) { - const numActiveInd = - hyprland.active.workspace.id === i ? numberedActiveIndicator : ''; - - return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`; - } - return 'default'; - }, - ), - label: Utils.merge( - [ - options.bar.workspaces.show_icons.bind('value'), - options.bar.workspaces.icons.available.bind('value'), - options.bar.workspaces.icons.active.bind('value'), - options.bar.workspaces.icons.occupied.bind('value'), - workspaceMask.bind('value'), - hyprland.active.workspace.bind('id'), - ], - ( - showIcons: boolean, - available: string, - active: string, - occupied: string, - workspaceMask: boolean, - ) => { - if (showIcons) { - if (hyprland.active.workspace.id === i) { - return active; - } - if ((hyprland.getWorkspace(i)?.windows || 0) > 0) { - return occupied; - } - if (monitor !== -1) { - return available; - } - } - return workspaceMask ? `${index + 1}` : `${i}`; - }, - ), - setup: (self) => { - self.hook(hyprland, () => { - self.toggleClassName('active', hyprland.active.workspace.id === i); - self.toggleClassName( - 'occupied', - (hyprland.getWorkspace(i)?.windows || 0) > 0, - ); - }); - }, - }), - }); - }); - }, - ), - }); - }; - const occupiedWses = (): BoxWidget => { - return Widget.Box({ - children: Utils.merge( - [ - monitorSpecific.bind('value'), - hyprland.bind('workspaces'), - workspaceMask.bind('value'), - workspaces.bind('value'), - options.bar.workspaces.show_icons.bind('value'), - options.bar.workspaces.icons.available.bind('value'), - options.bar.workspaces.icons.active.bind('value'), - options.bar.workspaces.icons.occupied.bind('value'), - options.bar.workspaces.show_numbered.bind('value'), - options.bar.workspaces.numbered_active_indicator.bind('value'), - spacing.bind('value'), - hyprland.active.workspace.bind('id'), - ], - ( - monitorSpecific: boolean, - wkSpaces: Workspace[], - workspaceMask: boolean, - totalWkspcs: number, - showIcons: boolean, - available: string, - active: string, - occupied: string, - showNumbered: boolean, - numberedActiveIndicator: string, - spacing: number, - activeId: number, - ) => { - let allWkspcs = range(totalWkspcs || 8); - - const activeWorkspaces = wkSpaces.map((w) => w.id); - const workspaceRules = getWorkspaceRules(); - - // Sometimes hyprland doesn't have all the monitors in the list - // so we complement it with monitors from the workspace list - const workspaceMonitorList = hyprland?.workspaces?.map((m) => ({ - id: m.monitorID, - name: m.monitor, - })); - const curMonitor = - hyprland.monitors.find((m) => m.id === monitor) || - workspaceMonitorList.find((m) => m.id === monitor); - - // go through each key in workspaceRules and flatten the array - const workspacesWithRules = Object.keys(workspaceRules).reduce((acc: number[], k: string) => { - return [...acc, ...workspaceRules[k]]; - }, [] as number[]); - - const activesForMonitor = activeWorkspaces.filter((w) => { - if ( - curMonitor && - Object.hasOwnProperty.call(workspaceRules, curMonitor.name) && - workspacesWithRules.includes(w) - ) { - return workspaceRules[curMonitor.name].includes(w); - } - return true; - }); - - if (monitorSpecific) { - const wrkspcsInRange = range(totalWkspcs).filter((w) => { - return getWorkspacesForMonitor(w, workspaceRules, monitor); - }); - allWkspcs = [...new Set([...activesForMonitor, ...wrkspcsInRange])]; - } else { - allWkspcs = [...new Set([...allWkspcs, ...activeWorkspaces])]; - } - - return allWkspcs - .sort((a, b) => { - return a - b; - }) - .map((i, index) => { - return Widget.Button({ - class_name: 'workspace-button', - on_primary_click: () => { - hyprland.messageAsync(`dispatch workspace ${i}`); - }, - child: Widget.Label({ - attribute: i, - vpack: 'center', - css: `margin: 0rem ${0.375 * spacing}rem;`, - class_name: renderClassnames(showIcons, showNumbered, numberedActiveIndicator, i), - label: renderLabel(showIcons, available, active, occupied, workspaceMask, i, index), - setup: (self) => { - self.toggleClassName('active', activeId === i); - self.toggleClassName('occupied', (hyprland.getWorkspace(i)?.windows || 0) > 0); - }, - }), - }); - }); - }, - ), - }); - }; - return { component: Widget.Box({ class_name: 'workspaces', child: options.bar.workspaces.hideUnoccupied .bind('value') - .as((hideUnoccupied) => (hideUnoccupied ? occupiedWses() : defaultWses())), + .as((hideUnoccupied) => (hideUnoccupied ? occupiedWses(monitor) : defaultWses(monitor))), }), isVisible: true, boxClass: 'workspaces', diff --git a/modules/bar/workspaces/utils.ts b/modules/bar/workspaces/utils.ts index 81b3d96..ed29582 100644 --- a/modules/bar/workspaces/utils.ts +++ b/modules/bar/workspaces/utils.ts @@ -1,18 +1,60 @@ +import { WorkspaceIconMap } from 'lib/types/workspace'; +import { isValidGjsColor } from 'lib/utils'; + const hyprland = await Service.import('hyprland'); +const getWsIcon = (wsIconMap: WorkspaceIconMap, i: number): string => { + const iconEntry = wsIconMap[i]; + + if (!iconEntry) { + return `${i}`; + } + + const hasIcon = typeof iconEntry === 'object' && 'icon' in iconEntry && iconEntry.icon !== ''; + + if (typeof iconEntry === 'string' && iconEntry !== '') { + return iconEntry; + } + + if (hasIcon) { + return iconEntry.icon; + } + + return `${i}`; +}; + +export const getWsColor = (wsIconMap: WorkspaceIconMap, i: number): string => { + const iconEntry = wsIconMap[i]; + if (!iconEntry) { + return ''; + } + + const hasColor = typeof iconEntry === 'object' && 'color' in iconEntry && iconEntry.color !== ''; + if (hasColor && isValidGjsColor(iconEntry.color)) { + return `color: ${iconEntry.color}; border-bottom-color: ${iconEntry.color};`; + } + return ''; +}; + export const renderClassnames = ( showIcons: boolean, showNumbered: boolean, numberedActiveIndicator: string, + showWsIcons: boolean, i: number, ): string => { if (showIcons) { return `workspace-icon txt-icon bar`; } - if (showNumbered) { - const numActiveInd = hyprland.active.workspace.id === i ? numberedActiveIndicator : ''; + if (showNumbered || showWsIcons) { + const numActiveInd = hyprland.active.workspace.id === i ? `${numberedActiveIndicator}` : ''; - return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`; + const className = + `workspace-number can_${numberedActiveIndicator} ` + + `${numActiveInd} ` + + `${showWsIcons ? 'txt-icon' : ''}`; + + return className; } return 'default'; }; @@ -23,6 +65,8 @@ export const renderLabel = ( active: string, occupied: string, workspaceMask: boolean, + showWsIcons: boolean, + wsIconMap: WorkspaceIconMap, i: number, index: number, monitor: number, @@ -38,6 +82,8 @@ export const renderLabel = ( return available; } } - + if (showWsIcons) { + return getWsIcon(wsIconMap, i); + } return workspaceMask ? `${index + 1}` : `${i}`; }; diff --git a/modules/bar/workspaces/variants/default.ts b/modules/bar/workspaces/variants/default.ts index 141212d..ccd985b 100644 --- a/modules/bar/workspaces/variants/default.ts +++ b/modules/bar/workspaces/variants/default.ts @@ -3,6 +3,8 @@ import options from 'options'; import { getWorkspaceRules, getWorkspacesForMonitor } from '../helpers'; import { range } from 'lib/utils'; import { BoxWidget } from 'lib/types/widget'; +import { getWsColor, renderClassnames, renderLabel } from '../utils'; +import { WorkspaceIconMap } from 'lib/types/workspace'; const { workspaces, monitorSpecific, workspaceMask, spacing } = options.bar.workspaces; export const defaultWses = (monitor: number): BoxWidget => { @@ -30,28 +32,48 @@ export const defaultWses = (monitor: number): BoxWidget => { child: Widget.Label({ attribute: i, vpack: 'center', - css: spacing.bind('value').as((sp) => `margin: 0rem ${0.375 * sp}rem;`), + css: Utils.merge( + [ + spacing.bind('value'), + options.bar.workspaces.showWsIcons.bind('value'), + options.bar.workspaces.workspaceIconMap.bind('value'), + options.theme.matugen.bind('value'), + ], + ( + sp: number, + showWsIcons: boolean, + workspaceIconMap: WorkspaceIconMap, + matugen: boolean, + ) => { + return ( + `margin: 0rem ${0.375 * sp}rem;` + + `${showWsIcons && !matugen ? getWsColor(workspaceIconMap, i) : ''}` + ); + }, + ), class_name: Utils.merge( [ options.bar.workspaces.show_icons.bind('value'), options.bar.workspaces.show_numbered.bind('value'), options.bar.workspaces.numbered_active_indicator.bind('value'), + options.bar.workspaces.showWsIcons.bind('value'), options.bar.workspaces.icons.available.bind('value'), options.bar.workspaces.icons.active.bind('value'), - options.bar.workspaces.icons.occupied.bind('value'), hyprland.active.workspace.bind('id'), ], - (showIcons: boolean, showNumbered: boolean, numberedActiveIndicator: string) => { - if (showIcons) { - return `workspace-icon txt-icon bar`; - } - if (showNumbered) { - const numActiveInd = - hyprland.active.workspace.id === i ? numberedActiveIndicator : ''; - - return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`; - } - return 'default'; + ( + showIcons: boolean, + showNumbered: boolean, + numberedActiveIndicator: string, + showWsIcons: boolean, + ) => { + return renderClassnames( + showIcons, + showNumbered, + numberedActiveIndicator, + showWsIcons, + i, + ); }, ), label: Utils.merge( @@ -60,6 +82,8 @@ export const defaultWses = (monitor: number): BoxWidget => { options.bar.workspaces.icons.available.bind('value'), options.bar.workspaces.icons.active.bind('value'), options.bar.workspaces.icons.occupied.bind('value'), + options.bar.workspaces.workspaceIconMap.bind('value'), + options.bar.workspaces.showWsIcons.bind('value'), workspaceMask.bind('value'), hyprland.active.workspace.bind('id'), ], @@ -68,20 +92,22 @@ export const defaultWses = (monitor: number): BoxWidget => { available: string, active: string, occupied: string, + wsIconMap: WorkspaceIconMap, + showWsIcons: boolean, workspaceMask: boolean, ) => { - if (showIcons) { - if (hyprland.active.workspace.id === i) { - return active; - } - if ((hyprland.getWorkspace(i)?.windows || 0) > 0) { - return occupied; - } - if (monitor !== -1) { - return available; - } - } - return workspaceMask ? `${index + 1}` : `${i}`; + return renderLabel( + showIcons, + available, + active, + occupied, + workspaceMask, + showWsIcons, + wsIconMap, + i, + index, + monitor, + ); }, ), setup: (self) => { diff --git a/modules/bar/workspaces/variants/occupied.ts b/modules/bar/workspaces/variants/occupied.ts index a7efe95..028ec4a 100644 --- a/modules/bar/workspaces/variants/occupied.ts +++ b/modules/bar/workspaces/variants/occupied.ts @@ -2,9 +2,10 @@ const hyprland = await Service.import('hyprland'); import options from 'options'; import { getWorkspaceRules, getWorkspacesForMonitor } from '../helpers'; import { Workspace } from 'types/service/hyprland'; -import { renderClassnames, renderLabel } from '../utils'; +import { getWsColor, renderClassnames, renderLabel } from '../utils'; import { range } from 'lib/utils'; import { BoxWidget } from 'lib/types/widget'; +import { WorkspaceIconMap } from 'lib/types/workspace'; const { workspaces, monitorSpecific, workspaceMask, spacing } = options.bar.workspaces; @@ -24,6 +25,9 @@ export const occupiedWses = (monitor: number): BoxWidget => { options.bar.workspaces.numbered_active_indicator.bind('value'), spacing.bind('value'), hyprland.active.workspace.bind('id'), + options.bar.workspaces.workspaceIconMap.bind('value'), + options.bar.workspaces.showWsIcons.bind('value'), + options.theme.matugen.bind('value'), ], ( monitorSpecific: boolean, @@ -38,6 +42,9 @@ export const occupiedWses = (monitor: number): BoxWidget => { numberedActiveIndicator: string, spacing: number, activeId: number, + wsIconMap: WorkspaceIconMap, + showWsIcons: boolean, + matugen: boolean, ) => { let allWkspcs = range(totalWkspcs || 8); @@ -46,7 +53,10 @@ export const occupiedWses = (monitor: number): BoxWidget => { // Sometimes hyprland doesn't have all the monitors in the list // so we complement it with monitors from the workspace list - const workspaceMonitorList = hyprland?.workspaces?.map((m) => ({ id: m.monitorID, name: m.monitor })); + const workspaceMonitorList = hyprland?.workspaces?.map((m) => ({ + id: m.monitorID, + name: m.monitor, + })); const curMonitor = hyprland.monitors.find((m) => m.id === monitor) || workspaceMonitorList.find((m) => m.id === monitor); @@ -89,14 +99,24 @@ export const occupiedWses = (monitor: number): BoxWidget => { child: Widget.Label({ attribute: i, vpack: 'center', - css: `margin: 0rem ${0.375 * spacing}rem;`, - class_name: renderClassnames(showIcons, showNumbered, numberedActiveIndicator, i), + css: + `margin: 0rem ${0.375 * spacing}rem;` + + `${showWsIcons && !matugen ? getWsColor(wsIconMap, i) : ''}`, + class_name: renderClassnames( + showIcons, + showNumbered, + numberedActiveIndicator, + showWsIcons, + i, + ), label: renderLabel( showIcons, available, active, occupied, workspaceMask, + showWsIcons, + wsIconMap, i, index, monitor, diff --git a/options.ts b/options.ts index 14e2dfd..104e7c7 100644 --- a/options.ts +++ b/options.ts @@ -20,6 +20,7 @@ import { } from 'lib/types/options'; import { MatugenScheme, MatugenTheme, MatugenVariations } from 'lib/types/options'; import { UnitType } from 'lib/types/weather'; +import { WorkspaceIcons, WorkspaceIconsColored } from 'lib/types/workspace'; // WARN: CHANGING THESE VALUES WILL PREVENT MATUGEN COLOR GENERATION FOR THE CHANGED VALUE export const colors = { @@ -191,6 +192,7 @@ const options = mkOptions(OPTIONS, { numbered_active_highlighted_text_color: opt(colors.mantle), numbered_active_underline_color: opt(colors.pink), spacing: opt('0.5em'), + fontSize: opt('1.2em'), }, windowtitle: { background: opt(colors.base2), @@ -810,12 +812,14 @@ const options = mkOptions(OPTIONS, { workspaces: { show_icons: opt(false), show_numbered: opt(false), + showWsIcons: opt(false), numbered_active_indicator: opt('underline'), icons: { available: opt(''), active: opt(''), occupied: opt(''), }, + workspaceIconMap: opt({}), workspaces: opt(10), spacing: opt(1), monitorSpecific: opt(true), diff --git a/scss/style/bar/workspace.scss b/scss/style/bar/workspace.scss index 776c7f5..3480e87 100644 --- a/scss/style/bar/workspace.scss +++ b/scss/style/bar/workspace.scss @@ -1,81 +1,82 @@ .workspaces { - label { - font-size: 0.2em; - min-width: 4em; - min-height: 4em; - border-radius: 1.9rem * .6; - transition: 300ms * .5; - background-color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-workspaces-available); - color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-workspaces-available); + label { + font-size: 0.2em; + min-width: 4em; + min-height: 4em; + border-radius: 1.9rem * 0.6; + transition: 300ms * 0.5; + background-color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-workspaces-available); + color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-workspaces-available); - &.occupied { - background-color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-workspaces-occupied); - color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-workspaces-occupied); - min-width: 4em; - min-height: 4em; - } + &.occupied { + background-color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-workspaces-occupied); + color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-workspaces-occupied); + min-width: 4em; + min-height: 4em; + } - &.active { - color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-workspaces-active); - background-color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-workspaces-active); - min-width: 12em; - min-height: 4em; - } + &.active { + color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-workspaces-active); + background-color: if($bar-buttons-monochrome, $bar-buttons-icon, $bar-buttons-workspaces-active); + min-width: 12em; + min-height: 4em; + } - &.workspace-icon { - background-color: transparent; - min-width: 0em; - min-height: 0em; - border-radius: 0em; - transition: 300ms * .5; - font-size: 1em; - } + &.workspace-icon { + background-color: transparent; + min-width: 0em; + min-height: 0em; + border-radius: 0em; + transition: 300ms * 0.5; + font-size: 1em; + } - &.workspace-number { - background-color: transparent; - min-width: 0em; - min-height: 0em; - border-radius: 0em; - transition: 0ms; - padding: 0em 0.2em; - font-size: 1.2em; - } + &.workspace-number { + background-color: transparent; + min-width: 0em; + min-height: 0em; + border-radius: 0em; + transition: 0ms; + padding: 0em 0.2em; + font-size: $bar-buttons-workspaces-fontSize; + } - &.underline { - border-top: 0.1em solid transparent; - border-bottom: 0.1em solid $bar-buttons-workspaces-numbered_active_underline_color; - } + &.underline { + border-top: 0.1em solid transparent; + border-bottom: 0.1em solid $bar-buttons-workspaces-numbered_active_underline_color; + } - &.highlight { - color: $bar-buttons-workspaces-numbered_active_highlighted_text_color; - border-radius: $bar-buttons-workspaces-numbered_active_highlight_border; - background-color: $bar-buttons-workspaces-active; - padding: 0em $bar-buttons-workspaces-numbered_active_highlight_padding; + &.highlight { + color: $bar-buttons-workspaces-numbered_active_highlighted_text_color; + border-radius: $bar-buttons-workspaces-numbered_active_highlight_border; + background-color: $bar-buttons-workspaces-active; + padding: 0em $bar-buttons-workspaces-numbered_active_highlight_padding; + } } - } } .workspace-button { - &:hover label { - color: $bar-buttons-workspaces-hover; + &:hover label { + color: $bar-buttons-workspaces-hover; - &.default { - background-color: $bar-buttons-workspaces-hover; + &.default { + background-color: $bar-buttons-workspaces-hover; + } } - } - &:hover .can_underline { - border-top: 0.1em solid transparent; - border-bottom: 0.1em solid if($bar-buttons-monochrome, $bar-buttons-workspaces-hover, $bar-buttons-workspaces-hover); - } + &:hover .can_underline { + border-top: 0.1em solid transparent; + border-bottom: 0.1em solid + if($bar-buttons-monochrome, $bar-buttons-workspaces-hover, $bar-buttons-workspaces-hover); + } - &:hover .can_highlight { - background-color: $bar-buttons-workspaces-hover; - color: $bar-buttons-workspaces-numbered_active_highlighted_text_color; - border-radius: $bar-buttons-workspaces-numbered_active_highlight_border; - } + &:hover .can_highlight { + background-color: $bar-buttons-workspaces-hover; + color: $bar-buttons-workspaces-numbered_active_highlighted_text_color; + border-radius: $bar-buttons-workspaces-numbered_active_highlight_border; + } } .style2.workspaces { - padding: $bar-buttons-padding_y $bar-buttons-padding_x; + padding: $bar-buttons-padding_y $bar-buttons-padding_x; } diff --git a/widget/settings/pages/config/bar/index.ts b/widget/settings/pages/config/bar/index.ts index ef685c7..a213b88 100644 --- a/widget/settings/pages/config/bar/index.ts +++ b/widget/settings/pages/config/bar/index.ts @@ -136,6 +136,14 @@ export const BarSettings = (): Scrollable => { ****************************** */ Header('Workspaces'), + Option({ + opt: options.theme.bar.buttons.workspaces.fontSize, + title: 'Indicator Size', + subtitle: + 'Only applicable to numbered workspaces and mapped icons\n' + + 'Adjust with caution as it may cause the bar to expand', + type: 'string', + }), Option({ opt: options.bar.workspaces.show_icons, title: 'Show Workspace Icons', @@ -180,6 +188,16 @@ export const BarSettings = (): Scrollable => { subtitle: 'Only applicable if Workspace Numbers are enabled', type: 'string', }), + Option({ + opt: options.bar.workspaces.showWsIcons, + title: 'Map Workspaces to Icons', + type: 'boolean', + }), + Option({ + opt: options.bar.workspaces.workspaceIconMap, + title: 'Workspace Icon Mappings', + type: 'object', + }), Option({ opt: options.bar.workspaces.spacing, title: 'Spacing', diff --git a/widget/settings/side_effects/index.ts b/widget/settings/side_effects/index.ts index a93d03b..6e268c1 100644 --- a/widget/settings/side_effects/index.ts +++ b/widget/settings/side_effects/index.ts @@ -1,6 +1,6 @@ import options from 'options'; -const { show_numbered, show_icons } = options.bar.workspaces; +const { show_numbered, show_icons, showWsIcons } = options.bar.workspaces; const { monochrome: monoBar } = options.theme.bar.buttons; const { monochrome: monoMenu } = options.theme.bar.menus; const { matugen } = options.theme; @@ -8,12 +8,21 @@ const { matugen } = options.theme; show_numbered.connect('changed', ({ value }) => { if (value === true) { show_icons.value = false; + showWsIcons.value = false; } }); show_icons.connect('changed', ({ value }) => { if (value === true) { show_numbered.value = false; + showWsIcons.value = false; + } +}); + +showWsIcons.connect('changed', ({ value }) => { + if (value === true) { + show_numbered.value = false; + show_icons.value = false; } });