diff options
Diffstat (limited to '.config/ags/widget/dock')
| -rw-r--r-- | .config/ags/widget/dock/Dock.ts | 150 | ||||
| -rw-r--r-- | .config/ags/widget/dock/FloatingDock.ts | 70 | ||||
| -rw-r--r-- | .config/ags/widget/dock/ToolBox.ts | 122 | ||||
| -rw-r--r-- | .config/ags/widget/dock/ToolBoxDock.ts | 57 | ||||
| -rw-r--r-- | .config/ags/widget/dock/dock.scss | 73 |
5 files changed, 472 insertions, 0 deletions
diff --git a/.config/ags/widget/dock/Dock.ts b/.config/ags/widget/dock/Dock.ts new file mode 100644 index 0000000..c55f89f --- /dev/null +++ b/.config/ags/widget/dock/Dock.ts @@ -0,0 +1,150 @@ +import { launchApp } from "lib/utils"; +import options from "options"; +import * as Gtk from "gi://Gtk?version=3.0"; +import { type ButtonProps } from "types/widgets/button"; +import { type BoxProps } from "types/widgets/box"; + +const hyprland = await Service.import("hyprland"); +const applications = await Service.import("applications"); + +const focus = (address: string) => hyprland.messageAsync(`dispatch focuswindow address:${address}`); + +const AppButton = ({ icon, pinned = false, term, ...rest }: ButtonProps & { term?: string }): Gtk.Button & ButtonProps => { + const { iconSize } = options.dock; + + const buttonBox = Widget.Box({ + class_name: 'box', + child: Widget.Icon({ + icon, + size: iconSize, + }), + }); + + const button = Widget.Button({ + ...rest, + class_name: '', + child: pinned ? buttonBox : Widget.Overlay({ + child: buttonBox, + pass_through: false, + overlays: [], + }), + }); + + return Object.assign(button, {}); +}; + +const createAppButton = ({ app, term, ...params }) => { + return AppButton({ + icon: app.icon_name || '', + term, + ...params, + }); +}; + +const filterValidClients = (clients: any[]) => { + return clients.filter(client => ( + client.mapped && + [client.class, client.title, client.initialClass].every(prop => typeof prop === 'string' && prop !== '') + )); +}; + +const Taskbar = (): Gtk.Box & BoxProps => { + const addedApps = new Set<string>(); + + const updateTaskbar = (clients: any[]) => { + const validClients = filterValidClients(clients); + + const focusedAddress = hyprland?.active.client?.address; + const running = validClients.filter(client => client.mapped); + const focused = running.find(client => client.address === focusedAddress); + + return validClients.map(client => { + if (![client.class, client.title, client.initialClass].every(prop => typeof prop === 'string' && prop !== '')) { + return null; + } + + if (addedApps.has(client.title)) { + return null; + } + + for (const appName of options.dock.pinnedApps.value) { + if (!appName || typeof appName !== 'string') { + continue; + } + + if (client.class.includes(appName) || client.title.includes(appName) + || client.initialClass.includes(appName)) { + return null; + } + } + + const matchingApp = applications?.list.find(app => ( + app.match(client.title) || app.match(client.class) || app.match(client.initialClass) + )); + + if (matchingApp) { + addedApps.add(client.title); + return createAppButton({ + app: matchingApp, + term: matchingApp.title, + on_primary_click: () => { + const clickAddress = client.address || focusedAddress; + clickAddress && focus(clickAddress); + }, + on_secondary_click: () => launchApp(matchingApp), + }); + a + } + return null; + }); + }; + + return Widget.Box({ + vertical: false, + }) + .bind('children', hyprland, 'clients', updateTaskbar); +}; + +const PinnedApps = (): Gtk.Box & BoxProps => { + const updatePinnedApps = (pinnedApps: string[]) => { + return pinnedApps + .map(term => ({ app: applications?.query(term)?.[0], term })) + .filter(({ app }) => app) + .map(({ app, term = true }) => createAppButton({ + app, + term, + pinned: true, + on_primary_click: () => { + const matchingClients = hyprland?.clients.filter(client => ( + typeof client.class === 'string' && + typeof client.title === 'string' && + typeof client.initialClass === 'string' && + (client.class.includes(term) || client.title.includes(term) || client.initialClass.includes(term)) + )); + + if (matchingClients.length > 0) { + const { address } = matchingClients[0]; + address && focus(address); + } else { + launchApp(app); + } + }, + on_secondary_click: () => launchApp(app), + })); + }; + + return Widget.Box({ + class_name: 'pins', + vertical: false, + homogeneous: true, + }) + .bind('children', options.dock.pinnedApps, 'value', updatePinnedApps); +}; + +const Dock = (): Gtk.Box & BoxProps => Widget.Box({ + class_name: 'dock', + vertical: false, + children: [PinnedApps(), Taskbar()], +}); + +export default Dock; diff --git a/.config/ags/widget/dock/FloatingDock.ts b/.config/ags/widget/dock/FloatingDock.ts new file mode 100644 index 0000000..369f56f --- /dev/null +++ b/.config/ags/widget/dock/FloatingDock.ts @@ -0,0 +1,70 @@ +import options from 'options'; +import Dock from './Dock.ts'; +const hyprland = await Service.import('hyprland'); +const apps = await Service.import('applications'); + +const { Gdk, Gtk } = imports.gi; +import type Gtk from 'gi://Gtk?version=3.0'; +import { type WindowProps } from 'types/widgets/window'; +import { type RevealerProps } from 'types/widgets/revealer'; +import { type EventBoxProps } from 'types/widgets/eventbox'; + +/** @param {number} monitor */ +const FloatingDock = (monitor: number): Gtk.Window & WindowProps => { + const update = () => { + const ws = Hyprland.getWorkspace(Hyprland.active.workspace.id); + if (Hyprland.getMonitor(monitor)?.name === ws?.monitor) self.reveal_child = ws?.windows === 0; + }; + const revealer: Gtk.Revealer & RevealerProps = Widget.Revealer({ + transition: 'slide_up', + transitionDuration: 90, + child: Dock(), + setup: self => self.hook(hyprland, update, 'client-added').hook(hyprland, update, 'client-removed').hook(hyprland.active.workspace, update), + }); + + const window = Widget.Window({ + monitor, + //halign: 'fill', + halign: 'end', + //layer: "overlay", + layer: 'dock', + name: `dock${monitor}`, + click_through: false, + class_name: 'floating-dock', + // class_name: 'floating-dock-no-gap', + // class_name: "f-dock-wrap", + + typeHint: Gdk.WindowTypeHint.DOCK, + exclusivity: 'false', + + anchor: ['bottom'], + child: Widget.Box({ + vertical: false, + halign: 'bottom', + hpack: 'start', + children: [ + revealer, + Widget.Box({ + class_name: 'padding', + css: 'padding: 9px; margin: 0;', + vertical: false, + halign: 'bottom', + hpack: 'start', + }), + ], + }), + }); + + window + .on('enter-notify-event', () => { + revealer.reveal_child = true; + }) + .on('leave-notify-event', () => { + revealer.reveal_child = false; + }) + .bind('visible', options.bar.position, 'value', v => v !== 'left'); + + return window; +}; + +export default FloatingDock; diff --git a/.config/ags/widget/dock/ToolBox.ts b/.config/ags/widget/dock/ToolBox.ts new file mode 100644 index 0000000..51fda72 --- /dev/null +++ b/.config/ags/widget/dock/ToolBox.ts @@ -0,0 +1,122 @@ +import options from "options"; +import { sh } from "lib/utils"; +import * as Gtk from "gi://Gtk?version=3.0"; +import { type ButtonProps } from "types/widgets/button"; +import { type BoxProps } from "types/widgets/box"; + +const hyprland = await Service.import("hyprland"); +const { icons } = options.dock.toolbox; +const buttonToggles = {}; + +const dispatch = (action: string, arg: string) => { + //console.log(`Performing action: ${action} with argument: ${arg}`); + sh(`hyprctl dispatch ${action} ${arg}`); +}; + +const keyword = (action: string, arg: string) => { + //console.log(`Performing action: ${action} with argument: ${arg}`); + sh(`hyprctl keyword ${action} ${arg}`); +}; + +const ToggleSwitch = (buttonIndex, actionOn, argOn, actionOff, argOff, actionExec) => { + buttonToggles[buttonIndex] = !buttonToggles[buttonIndex]; + const { action, arg } = buttonToggles[buttonIndex] ? { action: actionOn, arg: argOn } : { action: actionOff, arg: argOff }; + actionExec(action, arg); +}; + +const ToggleOnMulti = (buttonIndex, actionOn, argOn, actionOff, argOff, actionExec) => { + buttonToggles[buttonIndex] = !buttonToggles[buttonIndex]; + if (buttonToggles[buttonIndex]) { + actionOn.forEach(({ action, arg }) => { + actionExec(action, arg); + }); + } else { + actionExec(actionOff, argOff); + } +}; + +const execAction = (trigger, actionIndex, actionOn, argOn, actionOff, argOff, action, arg, actionExec) => { + switch (trigger) { + case 'toggleOn-multi': + ToggleOnMulti(actionIndex, actionOn, argOn, actionOff, argOff, actionExec); + break; + case 'toggle-switch': + ToggleSwitch(actionIndex, actionOn, argOn, actionOff, argOff, actionExec); + break; + case 'oneshot': + actionExec(action, arg); + break; + default: + break; + } +}; + +const buttonConfigs = [ + { actionExec: dispatch, trigger: 'oneshot', actionIndex: 0, action: 'killactive', arg: '' }, + { actionExec: dispatch, trigger: 'oneshot', actionIndex: 1, action: 'exec hyprctl', arg: 'kill' }, + { + actionExec: keyword, + trigger: 'toggle-switch', + actionIndex: 2, + actionOn: 'monitor', argOn: 'eDP-1,2736x1824,0x0,0,transform,1', + actionOff: 'monitor', argOff: 'eDP-1,2736x1824,0x0,0,transform,0' + }, + { actionExec: dispatch, trigger: 'oneshot', actionIndex: 3, action: 'workspace', arg: 'r-1' }, + { actionExec: dispatch, trigger: 'oneshot', actionIndex: 4, action: 'workspace', arg: 'r+1' }, + { actionExec: dispatch, trigger: 'oneshot', actionIndex: 5, action: 'movewindow', arg: 'l' }, + { actionExec: dispatch, trigger: 'oneshot', actionIndex: 6, action: 'movewindow', arg: 'r' }, + { actionExec: dispatch, trigger: 'oneshot', actionIndex: 7, action: 'movewindow', arg: 'u' }, + { actionExec: dispatch, trigger: 'oneshot', actionIndex: 8, action: 'movewindow', arg: 'd' }, + { actionExec: dispatch, trigger: 'oneshot', actionIndex: 9, action: 'swapnext', arg: 'next' }, + { actionExec: dispatch, trigger: 'oneshot', actionIndex: 10, action: 'togglesplit', arg: '' }, + { + actionExec: dispatch, + trigger: 'toggleOn-multi', + actionIndex: 11, + actionOn: [ + { action: 'setfloating', arg: 'active' }, + { action: 'resizeactive', arg: 'exact 90% 90%' }, + { action: 'centerwindow', arg: '' }, + ], + actionOff: 'settiled', argOff: 'active' + }, + { actionExec: dispatch, trigger: 'oneshot', actionIndex: 12, action: 'pin', arg: '' }, + { actionExec: dispatch, trigger: 'oneshot', actionIndex: 13, action: 'fullscreen', arg: '0' }, + { + actionExec: dispatch, + trigger: 'toggle-switch', + actionIndex: 14, + actionOn: 'exec', argOn: 'wvctl 1', + actionOff: 'exec', argOff: 'wvctl 0' + }, +]; + +const ToolBox = () => { + const ToolBoxButtons = () => { + const buttons = buttonConfigs.map(({ actionIndex, actionOn, argOn, actionOff, argOff, actionExec, trigger, action, arg }) => { + const execActionWrapper = () => execAction(trigger, actionIndex, actionOn, argOn, actionOff, argOff, action, arg, actionExec); + + return Widget.Button({ + child: Widget.Icon({ + icon: icons[actionIndex].bind(), + }), + on_clicked: execActionWrapper, + }); + }); + + return Widget.Box({ + vertical: true, + homogeneous: true, + children: buttons, + }); + }; + + return Widget.Box({ + class_name: "toolbox", + vertical: true, + homogeneous: true, + children: [ToolBoxButtons()], + }); +}; + +export default ToolBox; diff --git a/.config/ags/widget/dock/ToolBoxDock.ts b/.config/ags/widget/dock/ToolBoxDock.ts new file mode 100644 index 0000000..21beaeb --- /dev/null +++ b/.config/ags/widget/dock/ToolBoxDock.ts @@ -0,0 +1,57 @@ +import options from "options"; +import ToolBox from "./ToolBox.ts"; +const hyprland = await Service.import("hyprland"); +const apps = await Service.import("applications"); + +import type Gtk from "gi://Gtk?version=3.0"; +import { type WindowProps } from "types/widgets/window"; +import { type RevealerProps } from "types/widgets/revealer"; +import { type EventBoxProps } from "types/widgets/eventbox"; + +/** @param {number} monitor */ +const ToolBoxDock = (monitor: number): Gtk.Window & WindowProps => { + + const revealer: Gtk.Revealer & RevealerProps = Widget.Revealer({ + transition: 'slide_left', + transitionDuration: 50, + child: ToolBox(), + }); + + const window = Widget.Window({ + monitor, + halign: 'fill', + layer: "overlay", + name: `toolbox${monitor}`, + click_through: false, + class_name: 'floating-toolbox', + anchor: ['right'], + child: Widget.Box({ + vertical: true, + halign: 'top', + hpack: 'fill', + children: [ + revealer, + Widget.Box({ + class_name: 'padding', + css: 'padding: 14px;', + vertical: true, + halign: 'top', + hpack: 'fill', + }), + ], + }), + }); + + window + .on('enter-notify-event', () => { + revealer.reveal_child = true; + }) + .on('leave-notify-event', () => { + revealer.reveal_child = false; + }) + .bind('visible', options.bar.position, 'value', v => v !== 'left'); + + return window; +}; + +export default ToolBoxDock; diff --git a/.config/ags/widget/dock/dock.scss b/.config/ags/widget/dock/dock.scss new file mode 100644 index 0000000..9dc6256 --- /dev/null +++ b/.config/ags/widget/dock/dock.scss @@ -0,0 +1,73 @@ +@use 'sass:color'; + +.floating-dock { + padding-left: 0.2rem; + padding-right: 0.2rem; + padding-top: 0.3rem; + padding-bottom: 0.3rem; + border-radius: 1rem; +} + +.dock { + // @include floating-widget; + border-radius: $radius; + background-color: transparentize($bg, 0.07); + min-width: 0; + padding: 6; + + // Common styles for both PinnedApps and Taskbar buttons + button { + @include button($flat: true); + border-radius: 4; + padding: 2; + + .box { + margin: 0em; + min-width: 1em; + min-height: 0em; + padding: 0; + } + + image { + margin: 0px; + } + + .indicator { + background-color: transparentize($primary-bg, 0.3); + border-radius: $radius; + min-height: 1pt; + min-width: 16pt; + margin: 1pt; + } + } +} + +.toolbox { + // @include floating-widget; + border-radius: $radius; + background-color: transparentize($bg, 0.07); + min-width: 0; + padding: 6; + + // Common styles for both PinnedApps and Taskbar buttons + button { + @include button($flat: true); + border-radius: 0; + padding: 0; + + image { + margin: 0px; + font-size: 32px; + } + + &:hover, + &:active, + &:focus { + // Override hover, active, and focus styles for buttons in .toolbox + background-color: transparent; + border: none; + box-shadow: none; + outline: none; + } + } +} |
