Implement custom icons per workspace. (#261)
* Implement custom icons per workspace. * Finish custom workspace icon implementation * Remove unsupported color definition.
This commit is contained in:
46
README.md
46
README.md
@@ -11,21 +11,26 @@
|
||||
<br/>
|
||||
|
||||
# HyprPanel 🚀
|
||||
|
||||
A panel built for Hyprland with [AGS](https://github.com/Aylur/ags)
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
||||
142
lib/constants/colors.ts
Normal file
142
lib/constants/colors.ts
Normal file
@@ -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',
|
||||
]);
|
||||
13
lib/types/workspace.d.ts
vendored
13
lib/types/workspace.d.ts
vendored
@@ -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;
|
||||
|
||||
23
lib/utils.ts
23
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<T> = import('types/service').Binding<any, any, T>;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}`;
|
||||
};
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<ActiveWsIndicator>('underline'),
|
||||
icons: {
|
||||
available: opt(''),
|
||||
active: opt(''),
|
||||
occupied: opt(''),
|
||||
},
|
||||
workspaceIconMap: opt<WorkspaceIcons | WorkspaceIconsColored>({}),
|
||||
workspaces: opt(10),
|
||||
spacing: opt(1),
|
||||
monitorSpecific: opt(true),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -136,6 +136,14 @@ export const BarSettings = (): Scrollable<Gtk.Widget, Gtk.Widget> => {
|
||||
******************************
|
||||
*/
|
||||
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<Gtk.Widget, Gtk.Widget> => {
|
||||
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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user