diff --git a/.config/ags/modules/bar/focus/workspaces_hyprland.js b/.config/ags/modules/bar/focus/workspaces_hyprland.js new file mode 100644 index 00000000..b5477c72 --- /dev/null +++ b/.config/ags/modules/bar/focus/workspaces_hyprland.js @@ -0,0 +1,196 @@ +const { GLib, Gdk, Gtk } = imports.gi; +const Lang = imports.lang; +const Cairo = imports.cairo; +const Pango = imports.gi.Pango; +const PangoCairo = imports.gi.PangoCairo; +import App from 'resource:///com/github/Aylur/ags/app.js'; +import * as Utils from 'resource:///com/github/Aylur/ags/utils.js' +import Widget from 'resource:///com/github/Aylur/ags/widget.js'; +const { Box, DrawingArea, EventBox } = Widget; +import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; + +const NUM_OF_WORKSPACES_SHOWN = 10; // Limit = 53 I think +const dummyWs = Box({ className: 'bar-ws-focus' }); // Not shown. Only for getting size props +const dummyActiveWs = Box({ className: 'bar-ws-focus bar-ws-focus-active' }); // Not shown. Only for getting size props +const dummyOccupiedWs = Box({ className: 'bar-ws-focus bar-ws-focus-occupied' }); // Not shown. Only for getting size props + +const WS_TAKEN_WIDTH_MULTIPLIER = 1.4; + +// Font size = workspace id +const WorkspaceContents = (count = 10) => { + return DrawingArea({ + className: 'menu-decel', + // css: `transition: 300ms cubic-bezier(0.1, 1, 0, 1);`, + attribute: { + immediateActiveWs: 0, + initialized: false, + workspaceMask: 0, + workspaceGroup: 0, + updateMask: (self) => { + const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * NUM_OF_WORKSPACES_SHOWN; + // if (self.attribute.initialized) return; // We only need this to run once + const workspaces = Hyprland.workspaces; + let workspaceMask = 0; + for (let i = 0; i < workspaces.length; i++) { + const ws = workspaces[i]; + if (ws.id <= offset || ws.id > offset + count) continue; // Out of range, ignore + if (workspaces[i].windows > 0) + workspaceMask |= (1 << (ws.id - offset)); + } + // console.log('Mask:', workspaceMask.toString(2)); + self.attribute.workspaceMask = workspaceMask; + // self.attribute.initialized = true; + self.queue_draw(); + }, + toggleMask: (self, occupied, name) => { + if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name)); + else self.attribute.workspaceMask &= ~(1 << parseInt(name)); + self.queue_draw(); + }, + }, + setup: (area) => area + .hook(Hyprland.active.workspace, (self) => { + const newActiveWs = (Hyprland.active.workspace.id - 1) % count + 1; + self.setCss(`font-size: ${newActiveWs}px;`); + self.attribute.immediateActiveWs = newActiveWs; + const previousGroup = self.attribute.workspaceGroup; + const currentGroup = Math.floor((Hyprland.active.workspace.id - 1) / count); + if (currentGroup !== previousGroup) { + self.attribute.updateMask(self); + self.attribute.workspaceGroup = currentGroup; + } + }) + .hook(Hyprland, (self) => self.attribute.updateMask(self), 'notify::workspaces') + .on('draw', Lang.bind(area, (area, cr) => { + const offset = Math.floor((Hyprland.active.workspace.id - 1) / count) * NUM_OF_WORKSPACES_SHOWN; + + const allocation = area.get_allocation(); + const { width, height } = allocation; + + const workspaceStyleContext = dummyWs.get_style_context(); + const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL); + const workspaceRadius = workspaceDiameter / 2; + const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL); + + const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context(); + const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL); + + const activeWorkspaceStyleContext = dummyActiveWs.get_style_context(); + const activeWorkspaceWidth = activeWorkspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL); + // const activeWorkspaceWidth = 100; + const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL); + + const widgetStyleContext = area.get_style_context(); + const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL); + const immediateActiveWs = area.attribute.immediateActiveWs; + + // Draw + area.set_size_request(workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * (count - 1) + activeWorkspaceWidth, -1); + for (let i = 1; i <= count; i++) { + if (i == immediateActiveWs) continue; + if (area.attribute.workspaceMask & (1 << i)) + cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha); + else + cr.setSourceRGBA(wsbg.red, wsbg.green, wsbg.blue, wsbg.alpha); + + const centerX = (i <= activeWs) ? + (-workspaceRadius + (workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * i)) + : -workspaceRadius + workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * (count - 1) + activeWorkspaceWidth - ((count - i) * workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER); + cr.arc(centerX, height / 2, workspaceRadius, 0, 2 * Math.PI); + cr.fill(); + // What if shrinking + if (i == immediateActiveWs - 1 && immediateActiveWs > activeWs) { // To right + const widthPercentage = 1 - (immediateActiveWs - activeWs); + const leftX = centerX; + const wsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * (1 - widthPercentage); + cr.rectangle(leftX, height / 2 - workspaceRadius, wsWidth, workspaceDiameter); + cr.fill(); + cr.arc(leftX + wsWidth, height / 2, workspaceRadius, 0, Math.PI * 2); + cr.fill(); + } + else if (i == immediateActiveWs + 1 && immediateActiveWs < activeWs) { // To left + const widthPercentage = activeWs - immediateActiveWs; + const rightX = centerX; + const wsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * widthPercentage; + const leftX = rightX - wsWidth; + cr.rectangle(leftX, height / 2 - workspaceRadius, wsWidth, workspaceDiameter); + cr.fill(); + cr.arc(rightX, height / 2, workspaceRadius, 0, Math.PI * 2); + cr.fill(); + } + } + + let widthPercentage, leftX, rightX, activeWsWidth; + cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha); + if (immediateActiveWs > activeWs) { // To right + widthPercentage = immediateActiveWs - activeWs; + rightX = -workspaceRadius + workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * (count - 1) + activeWorkspaceWidth - ((count - immediateActiveWs) * workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER); + activeWsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * (1 - widthPercentage); + leftX = rightX - activeWsWidth; + + cr.arc(leftX, height / 2, workspaceRadius, 0, Math.PI * 2); // Should be 0.5 * Math.PI, 1.5 * Math.PI in theory but it leaves a weird 1px gap + cr.fill(); + cr.rectangle(leftX, height / 2 - workspaceRadius, activeWsWidth, workspaceDiameter); + cr.fill(); + cr.arc(leftX + activeWsWidth, height / 2, workspaceRadius, 0, Math.PI * 2); + cr.fill(); + } + else { // To left + widthPercentage = 1 - (activeWs - immediateActiveWs); + leftX = -workspaceRadius + (workspaceDiameter * WS_TAKEN_WIDTH_MULTIPLIER * immediateActiveWs); + activeWsWidth = (activeWorkspaceWidth - (workspaceDiameter * 1.5)) * widthPercentage + + cr.arc(leftX, height / 2, workspaceRadius, 0, Math.PI * 2); // Should be 0.5 * Math.PI, 1.5 * Math.PI in theory but it leaves a weird 1px gap + cr.fill(); + cr.rectangle(leftX, height / 2 - workspaceRadius, activeWsWidth, workspaceDiameter); + cr.fill(); + cr.arc(leftX + activeWsWidth, height / 2, workspaceRadius, 0, Math.PI * 2); + cr.fill(); + } + })) + , + }) +} + +export default () => EventBox({ + onScrollUp: () => Hyprland.messageAsync(`dispatch workspace -1`).catch(print), + onScrollDown: () => Hyprland.messageAsync(`dispatch workspace +1`).catch(print), + onMiddleClickRelease: () => App.toggleWindow('overview'), + onSecondaryClickRelease: () => App.toggleWindow('osk'), + attribute: { + clicked: false, + ws_group: 0, + }, + child: Box({ + homogeneous: true, + // className: 'bar-group-margin', + children: [Box({ + // className: 'bar-group bar-group-standalone bar-group-pad', + css: 'min-width: 2px;', + children: [WorkspaceContents(NUM_OF_WORKSPACES_SHOWN)], + })] + }), + setup: (self) => { + self.add_events(Gdk.EventMask.POINTER_MOTION_MASK); + self.on('motion-notify-event', (self, event) => { + if (!self.attribute.clicked) return; + const [_, cursorX, cursorY] = event.get_coords(); + const widgetWidth = self.get_allocation().width; + const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES_SHOWN / widgetWidth); + Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`]) + .catch(print); + }) + self.on('button-press-event', (self, event) => { + if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here + self.attribute.clicked = true; + const [_, cursorX, cursorY] = event.get_coords(); + const widgetWidth = self.get_allocation().width; + // const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES_PER_GROUP / widgetWidth) + self.attribute.ws_group * NUM_OF_WORKSPACES_PER_GROUP; + // Hyprland.messageAsync(`dispatch workspace ${wsId}`).catch(print); + const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES_SHOWN / widgetWidth); + Utils.execAsync([`${App.configDir}/scripts/hyprland/workspace_action.sh`, 'workspace', `${wsId}`]) + .catch(print); + }) + self.on('button-release-event', (self) => self.attribute.clicked = false); + } +}) diff --git a/.config/ags/modules/bar/workspaces_sway.js b/.config/ags/modules/bar/focus/workspaces_sway.js similarity index 99% rename from .config/ags/modules/bar/workspaces_sway.js rename to .config/ags/modules/bar/focus/workspaces_sway.js index 53612eb9..632b4b56 100644 --- a/.config/ags/modules/bar/workspaces_sway.js +++ b/.config/ags/modules/bar/focus/workspaces_sway.js @@ -4,7 +4,7 @@ const Cairo = imports.cairo; const Pango = imports.gi.Pango; const PangoCairo = imports.gi.PangoCairo; import Widget from "resource:///com/github/Aylur/ags/widget.js"; -import Sway from "../../services/sway.js"; +import Sway from "../../../services/sway.js"; import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; const { execAsync, exec } = Utils; const { Box, DrawingArea, EventBox } = Widget; diff --git a/.config/ags/modules/bar/main.js b/.config/ags/modules/bar/main.js index d3d51ff1..7719919b 100644 --- a/.config/ags/modules/bar/main.js +++ b/.config/ags/modules/bar/main.js @@ -1,19 +1,35 @@ const { Gtk } = imports.gi; import Widget from 'resource:///com/github/Aylur/ags/widget.js'; +import Battery from 'resource:///com/github/Aylur/ags/service/battery.js'; -import WindowTitle from "./spaceleft.js"; -import Indicators from "./spaceright.js"; -import Music from "./music.js"; -import System from "./system.js"; +import WindowTitle from "./normal/spaceleft.js"; +import Indicators from "./normal/spaceright.js"; +import Music from "./normal/music.js"; +import System from "./normal/system.js"; import { enableClickthrough } from "../.widgetutils/clickthrough.js"; import { RoundedCorner } from "../.commonwidgets/cairo_roundedcorner.js"; +import { currentShellMode } from '../../variables.js'; -const OptionalWorkspaces = async () => { +const BATTERY_LOW = 20; + +const NormalOptionalWorkspaces = async () => { try { - return (await import('./workspaces_hyprland.js')).default(); + return (await import('./normal/workspaces_hyprland.js')).default(); } catch { try { - return (await import('./workspaces_sway.js')).default(); + return (await import('./normal/workspaces_sway.js')).default(); + } catch { + return null; + } + } +}; + +const FocusOptionalWorkspaces = async () => { + try { + return (await import('./focus/workspaces_hyprland.js')).default(); + } catch { + try { + return (await import('./focus/workspaces_sway.js')).default(); } catch { return null; } @@ -25,7 +41,7 @@ export const Bar = async (monitor = 0) => { className: 'bar-sidemodule', children: children, }); - const barContent = Widget.CenterBox({ + const normalBarContent = Widget.CenterBox({ className: 'bar-bg', setup: (self) => { const styleContext = self.get_style_context(); @@ -39,20 +55,54 @@ export const Bar = async (monitor = 0) => { SideModule([Music()]), Widget.Box({ homogeneous: true, - children: [await OptionalWorkspaces()], + children: [await NormalOptionalWorkspaces()], }), SideModule([System()]), ] }), endWidget: Indicators(), }); + const focusedBarContent = Widget.CenterBox({ + className: 'bar-bg-focus', + startWidget: Widget.Box({}), + centerWidget: Widget.Box({ + className: 'spacing-h-4', + children: [ + SideModule([]), + Widget.Box({ + homogeneous: true, + children: [await FocusOptionalWorkspaces()], + }), + SideModule([]), + ] + }), + endWidget: Widget.Box({}), + setup: (self) => { + self.hook(Battery, (self) => { + if(!Battery.available) return; + print(Battery.percent) + self.toggleClassName('bar-bg-focus-batterylow', Battery.percent <= BATTERY_LOW); + }) + } + }); return Widget.Window({ monitor, name: `bar${monitor}`, anchor: ['top', 'left', 'right'], exclusivity: 'exclusive', visible: true, - child: barContent, + child: Widget.Stack({ + homogeneous: false, + transition: 'slide_up_down', + transitionDuration: 200, + children: { + 'normal': normalBarContent, + 'focus': focusedBarContent, + }, + setup: (self) => self.hook(currentShellMode, (self) => { + self.shown = currentShellMode.value; + }) + }), }); } @@ -73,4 +123,4 @@ export const BarCornerTopright = (id = '') => Widget.Window({ visible: true, child: RoundedCorner('topright', { className: 'corner', }), setup: enableClickthrough, -}); \ No newline at end of file +}); diff --git a/.config/ags/modules/bar/music.js b/.config/ags/modules/bar/normal/music.js similarity index 93% rename from .config/ags/modules/bar/music.js rename to .config/ags/modules/bar/normal/music.js index 9dc4b013..0d96b6b1 100644 --- a/.config/ags/modules/bar/music.js +++ b/.config/ags/modules/bar/normal/music.js @@ -4,9 +4,9 @@ import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js'; const { Box, Button, EventBox, Label, Overlay, Revealer, Scrollable } = Widget; const { execAsync, exec } = Utils; -import { AnimatedCircProg } from "../.commonwidgets/cairo_circularprogress.js"; -import { MaterialIcon } from '../.commonwidgets/materialicon.js'; -import { showMusicControls } from '../../variables.js'; +import { AnimatedCircProg } from "../../.commonwidgets/cairo_circularprogress.js"; +import { MaterialIcon } from '../../.commonwidgets/materialicon.js'; +import { showMusicControls } from '../../../variables.js'; const CUSTOM_MODULE_CONTENT_INTERVAL_FILE = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-interval.txt`; const CUSTOM_MODULE_CONTENT_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-poll.sh`; @@ -42,16 +42,19 @@ const BarResource = (name, icon, command) => { vpack: 'center', hpack: 'center', }); - const resourceProgress = Overlay({ - child: Box({ - vpack: 'center', - className: 'bar-batt', - homogeneous: true, - children: [ - MaterialIcon(icon, 'small'), - ], - }), - overlays: [resourceCircProg] + const resourceProgress = Box({ + homogeneous: true, + children: [Overlay({ + child: Box({ + vpack: 'center', + className: 'bar-batt', + homogeneous: true, + children: [ + MaterialIcon(icon, 'small'), + ], + }), + overlays: [resourceCircProg] + })] }); const resourceLabel = Label({ className: 'txt-smallie txt-onSurfaceVariant', @@ -59,8 +62,8 @@ const BarResource = (name, icon, command) => { const widget = Box({ className: 'spacing-h-4 txt-onSurfaceVariant', children: [ - resourceLabel, resourceProgress, + resourceLabel, ], setup: (self) => self .poll(5000, () => execAsync(['bash', '-c', command]) @@ -210,8 +213,8 @@ export default () => { child: Box({ className: 'spacing-h-5', children: [ - BarGroup({ child: musicStuff }), SystemResourcesOrCustomModule(), + BarGroup({ child: musicStuff }), ] }) }); diff --git a/.config/ags/modules/bar/spaceleft.js b/.config/ags/modules/bar/normal/spaceleft.js similarity index 95% rename from .config/ags/modules/bar/spaceleft.js rename to .config/ags/modules/bar/normal/spaceleft.js index 4f2b8540..4f43189a 100644 --- a/.config/ags/modules/bar/spaceleft.js +++ b/.config/ags/modules/bar/normal/spaceleft.js @@ -1,7 +1,7 @@ import App from 'resource:///com/github/Aylur/ags/app.js'; import Widget from 'resource:///com/github/Aylur/ags/widget.js'; -import Brightness from '../../services/brightness.js'; -import Indicator from '../../services/indicator.js'; +import Brightness from '../../../services/brightness.js'; +import Indicator from '../../../services/indicator.js'; const WindowTitle = async () => { try { diff --git a/.config/ags/modules/bar/spaceright.js b/.config/ags/modules/bar/normal/spaceright.js similarity index 96% rename from .config/ags/modules/bar/spaceright.js rename to .config/ags/modules/bar/normal/spaceright.js index ca0d6d4a..25768082 100644 --- a/.config/ags/modules/bar/spaceright.js +++ b/.config/ags/modules/bar/normal/spaceright.js @@ -5,8 +5,8 @@ import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; import Audio from 'resource:///com/github/Aylur/ags/service/audio.js'; import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js'; const { execAsync } = Utils; -import Indicator from '../../services/indicator.js'; -import { StatusIcons } from '../.commonwidgets/statusicons.js'; +import Indicator from '../../../services/indicator.js'; +import { StatusIcons } from '../../.commonwidgets/statusicons.js'; import { Tray } from "./tray.js"; export default () => { diff --git a/.config/ags/modules/bar/system.js b/.config/ags/modules/bar/normal/system.js similarity index 98% rename from .config/ags/modules/bar/system.js rename to .config/ags/modules/bar/normal/system.js index 5efdc0fd..63e5fdb3 100644 --- a/.config/ags/modules/bar/system.js +++ b/.config/ags/modules/bar/normal/system.js @@ -5,9 +5,9 @@ const { Box, Label, Button, Overlay, Revealer, Scrollable, Stack, EventBox } = W const { exec, execAsync } = Utils; const { GLib } = imports.gi; import Battery from 'resource:///com/github/Aylur/ags/service/battery.js'; -import { MaterialIcon } from '../.commonwidgets/materialicon.js'; -import { AnimatedCircProg } from "../.commonwidgets/cairo_circularprogress.js"; -import { WWO_CODE, WEATHER_SYMBOL, NIGHT_WEATHER_SYMBOL } from '../.commondata/weather.js'; +import { MaterialIcon } from '../../.commonwidgets/materialicon.js'; +import { AnimatedCircProg } from "../../.commonwidgets/cairo_circularprogress.js"; +import { WWO_CODE, WEATHER_SYMBOL, NIGHT_WEATHER_SYMBOL } from '../../.commondata/weather.js'; const BATTERY_LOW = 20; const WEATHER_CACHE_FOLDER = `${GLib.get_user_cache_dir()}/ags/weather`; diff --git a/.config/ags/modules/bar/tray.js b/.config/ags/modules/bar/normal/tray.js similarity index 100% rename from .config/ags/modules/bar/tray.js rename to .config/ags/modules/bar/normal/tray.js diff --git a/.config/ags/modules/bar/workspaces_hyprland.js b/.config/ags/modules/bar/normal/workspaces_hyprland.js similarity index 100% rename from .config/ags/modules/bar/workspaces_hyprland.js rename to .config/ags/modules/bar/normal/workspaces_hyprland.js diff --git a/.config/ags/modules/bar/normal/workspaces_sway.js b/.config/ags/modules/bar/normal/workspaces_sway.js new file mode 100644 index 00000000..632b4b56 --- /dev/null +++ b/.config/ags/modules/bar/normal/workspaces_sway.js @@ -0,0 +1,184 @@ +const { GLib, Gdk, Gtk } = imports.gi; +const Lang = imports.lang; +const Cairo = imports.cairo; +const Pango = imports.gi.Pango; +const PangoCairo = imports.gi.PangoCairo; +import Widget from "resource:///com/github/Aylur/ags/widget.js"; +import Sway from "../../../services/sway.js"; +import * as Utils from "resource:///com/github/Aylur/ags/utils.js"; +const { execAsync, exec } = Utils; +const { Box, DrawingArea, EventBox } = Widget; + +const NUM_OF_WORKSPACES = 10; +const dummyWs = Box({ className: 'bar-ws' }); // Not shown. Only for getting size props +const dummyActiveWs = Box({ className: 'bar-ws bar-ws-active' }); // Not shown. Only for getting size props +const dummyOccupiedWs = Box({ className: 'bar-ws bar-ws-occupied' }); // Not shown. Only for getting size props + +const switchToWorkspace = (arg) => Utils.execAsync(`swaymsg workspace ${arg}`).catch(print); +const switchToRelativeWorkspace = (self, num) => + execAsync([`${App.configDir}/scripts/sway/swayToRelativeWs.sh`, `${num}`]).catch(print); + +const WorkspaceContents = (count = 10) => { + return DrawingArea({ + css: `transition: 90ms cubic-bezier(0.1, 1, 0, 1);`, + attribute: { + initialized: false, + workspaceMask: 0, + updateMask: (self) => { + if (self.attribute.initialized) return; // We only need this to run once + const workspaces = Sway.workspaces; + let workspaceMask = 0; + // console.log('----------------') + for (let i = 0; i < workspaces.length; i++) { + const ws = workspaces[i]; + // console.log(ws.name, ',', ws.num); + if (!Number(ws.name)) return; + const id = Number(ws.name); + if (id <= 0) continue; // Ignore scratchpads + if (id > count) return; // Not rendered + if (workspaces[i].windows > 0) { + workspaceMask |= (1 << id); + } + } + self.attribute.workspaceMask = workspaceMask; + self.attribute.initialized = true; + }, + toggleMask: (self, occupied, name) => { + if (occupied) self.attribute.workspaceMask |= (1 << parseInt(name)); + else self.attribute.workspaceMask &= ~(1 << parseInt(name)); + }, + }, + setup: (area) => area + .hook(Sway.active.workspace, (area) => { + area.setCss(`font-size: ${Sway.active.workspace.name}px;`) + }) + .hook(Sway, (self) => self.attribute.updateMask(self), 'notify::workspaces') + // .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, true, name), 'workspace-added') + // .hook(Hyprland, (self, name) => self.attribute.toggleMask(self, false, name), 'workspace-removed') + .on('draw', Lang.bind(area, (area, cr) => { + const allocation = area.get_allocation(); + const { width, height } = allocation; + + const workspaceStyleContext = dummyWs.get_style_context(); + const workspaceDiameter = workspaceStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL); + const workspaceRadius = workspaceDiameter / 2; + const workspaceFontSize = workspaceStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 4 * 3; + const workspaceFontFamily = workspaceStyleContext.get_property('font-family', Gtk.StateFlags.NORMAL); + const wsbg = workspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL); + const wsfg = workspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL); + + const occupiedWorkspaceStyleContext = dummyOccupiedWs.get_style_context(); + const occupiedbg = occupiedWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL); + const occupiedfg = occupiedWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL); + + const activeWorkspaceStyleContext = dummyActiveWs.get_style_context(); + const activebg = activeWorkspaceStyleContext.get_property('background-color', Gtk.StateFlags.NORMAL); + const activefg = activeWorkspaceStyleContext.get_property('color', Gtk.StateFlags.NORMAL); + area.set_size_request(workspaceDiameter * count, -1); + const widgetStyleContext = area.get_style_context(); + const activeWs = widgetStyleContext.get_property('font-size', Gtk.StateFlags.NORMAL); + + const activeWsCenterX = -(workspaceDiameter / 2) + (workspaceDiameter * activeWs); + const activeWsCenterY = height / 2; + + // Font + const layout = PangoCairo.create_layout(cr); + const fontDesc = Pango.font_description_from_string(`${workspaceFontFamily[0]} ${workspaceFontSize}`); + layout.set_font_description(fontDesc); + cr.setAntialias(Cairo.Antialias.BEST); + // Get kinda min radius for number indicators + layout.set_text("0".repeat(count.toString().length), -1); + const [layoutWidth, layoutHeight] = layout.get_pixel_size(); + const indicatorRadius = Math.max(layoutWidth, layoutHeight) / 2 * 1.2; // a bit smaller than sqrt(2)*radius + const indicatorGap = workspaceRadius - indicatorRadius; + + // Draw workspace numbers + for (let i = 1; i <= count; i++) { + if (area.attribute.workspaceMask & (1 << i)) { + // Draw bg highlight + cr.setSourceRGBA(occupiedbg.red, occupiedbg.green, occupiedbg.blue, occupiedbg.alpha); + const wsCenterX = -(workspaceRadius) + (workspaceDiameter * i); + const wsCenterY = height / 2; + if (!(area.attribute.workspaceMask & (1 << (i - 1)))) { // Left + cr.arc(wsCenterX, wsCenterY, workspaceRadius, 0.5 * Math.PI, 1.5 * Math.PI); + cr.fill(); + } + else { + cr.rectangle(wsCenterX - workspaceRadius, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2) + cr.fill(); + } + if (!(area.attribute.workspaceMask & (1 << (i + 1)))) { // Right + cr.arc(wsCenterX, wsCenterY, workspaceRadius, -0.5 * Math.PI, 0.5 * Math.PI); + cr.fill(); + } + else { + cr.rectangle(wsCenterX, wsCenterY - workspaceRadius, workspaceRadius, workspaceRadius * 2) + cr.fill(); + } + + // Set color for text + cr.setSourceRGBA(occupiedfg.red, occupiedfg.green, occupiedfg.blue, occupiedfg.alpha); + } + else + cr.setSourceRGBA(wsfg.red, wsfg.green, wsfg.blue, wsfg.alpha); + layout.set_text(`${i}`, -1); + const [layoutWidth, layoutHeight] = layout.get_pixel_size(); + const x = -workspaceRadius + (workspaceDiameter * i) - (layoutWidth / 2); + const y = (height - layoutHeight) / 2; + cr.moveTo(x, y); + // cr.showText(text); + PangoCairo.show_layout(cr, layout); + cr.stroke(); + } + + // Draw active ws + // base + cr.setSourceRGBA(activebg.red, activebg.green, activebg.blue, activebg.alpha); + cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius, 0, 2 * Math.PI); + cr.fill(); + // inner decor + cr.setSourceRGBA(activefg.red, activefg.green, activefg.blue, activefg.alpha); + cr.arc(activeWsCenterX, activeWsCenterY, indicatorRadius * 0.2, 0, 2 * Math.PI); + cr.fill(); + })) + , + }) +} + +export default () => EventBox({ + onScrollUp: (self) => switchToRelativeWorkspace(self, -1), + onScrollDown: (self) => switchToRelativeWorkspace(self, +1), + onMiddleClickRelease: () => App.toggleWindow('overview'), + onSecondaryClickRelease: () => App.toggleWindow('osk'), + attribute: { clicked: false }, + child: Box({ + homogeneous: true, + className: 'bar-group-margin', + children: [Box({ + className: 'bar-group bar-group-standalone bar-group-pad', + css: 'min-width: 2px;', + children: [ + WorkspaceContents(10), + ] + })] + }), + setup: (self) => { + self.add_events(Gdk.EventMask.POINTER_MOTION_MASK); + self.on('motion-notify-event', (self, event) => { + if (!self.attribute.clicked) return; + const [_, cursorX, cursorY] = event.get_coords(); + const widgetWidth = self.get_allocation().width; + const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES / widgetWidth); + switchToWorkspace(wsId); + }) + self.on('button-press-event', (self, event) => { + if (!(event.get_button()[1] === 1)) return; // We're only interested in left-click here + self.attribute.clicked = true; + const [_, cursorX, cursorY] = event.get_coords(); + const widgetWidth = self.get_allocation().width; + const wsId = Math.ceil(cursorX * NUM_OF_WORKSPACES / widgetWidth); + switchToWorkspace(wsId); + }) + self.on('button-release-event', (self) => self.attribute.clicked = false); + } +}); diff --git a/.config/ags/scss/_bar.scss b/.config/ags/scss/_bar.scss index f8356609..ccb35c5b 100644 --- a/.config/ags/scss/_bar.scss +++ b/.config/ags/scss/_bar.scss @@ -4,6 +4,8 @@ $black: black; $white: white; $bar_ws_width: 1.774rem; +$bar_ws_width_focus: 0.614rem; +$bar_ws_width_focus_active: 2.045rem; $bar_subgroup_bg: $surfaceVariant; @mixin bar-group-rounding { @@ -19,6 +21,15 @@ $bar_subgroup_bg: $surfaceVariant; min-height: 2.727rem; } +.bar-bg-focus { + background-color: $t_background; + min-height: 1.364rem; +} + +.bar-bg-focus-batterylow { + background-color: mix($background, $errorContainer, 80%); +} + .bar-sidespace { min-width: 1.5rem; } @@ -84,6 +95,7 @@ $bar_subgroup_bg: $surfaceVariant; .bar-ws { min-width: $bar_ws_width; color: mix($onBackground, $background, 40%); + @if $darkmode ==true { color: mix($onBackground, $background, 45%); } @@ -99,6 +111,26 @@ $bar_subgroup_bg: $surfaceVariant; color: $onSurfaceVariant; } +// Focus is the bar mode name, not the workspace state! + +.bar-ws-focus { + background-color: $bar_subgroup_bg; + min-width: $bar_ws_width_focus; +} + +.bar-ws-focus-active { + min-width: $bar_ws_width_focus_active; + background-color: $onBackground; +} + +.bar-ws-focus-occupied { + background-color: mix($onBackground, $background, 40%); + + @if $darkmode ==true { + background-color: mix($onBackground, $background, 45%); + } +} + .bar-separator { @include full-rounding; min-width: 0.341rem; @@ -250,17 +282,17 @@ $bar_subgroup_bg: $surfaceVariant; padding: 0.341rem; } -.bar-space-button > box:first-child { +.bar-space-button>box:first-child { @include full-rounding; padding: 0rem 0.682rem; } -.bar-space-button:hover > box:first-child, -.bar-space-button:focus > box:first-child { +.bar-space-button:hover>box:first-child, +.bar-space-button:focus>box:first-child { background-color: $hovercolor; } -.bar-space-button:active > box:first-child { +.bar-space-button:active>box:first-child { background-color: $activecolor; } @@ -270,7 +302,7 @@ $bar_subgroup_bg: $surfaceVariant; } } -.bar-space-area-rightmost > box { +.bar-space-area-rightmost>box { padding-right: 2.386rem; } diff --git a/.config/ags/scss/_lib_mixins.scss b/.config/ags/scss/_lib_mixins.scss index ba5babff..ece6a3dd 100644 --- a/.config/ags/scss/_lib_mixins.scss +++ b/.config/ags/scss/_lib_mixins.scss @@ -60,7 +60,7 @@ $rounding_large: 1.705rem; @mixin readingfont { // The most readable fonts, for a comfortable reading experience - // in stuff like ChatGPT widget + // in stuff like AI chat on sidebar font-family: "Lexend", "Noto Sans", sans-serif; // font-weight: 500; } diff --git a/.config/ags/services/gpt.js b/.config/ags/services/gpt.js index 63a55e6c..63727cf8 100644 --- a/.config/ags/services/gpt.js +++ b/.config/ags/services/gpt.js @@ -62,7 +62,7 @@ Utils.exec(`mkdir -p ${GLib.get_user_cache_dir()}/ags/user/ai`); const CHAT_MODELS = ["gpt-3.5-turbo-1106", "gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-3.5-turbo-0613"] const ONE_CYCLE_COUNT = 3; -class ChatGPTMessage extends Service { +class GPTMessage extends Service { static { Service.register(this, { @@ -122,7 +122,7 @@ class ChatGPTMessage extends Service { } } -class ChatGPTService extends Service { +class GPTService extends Service { static { Service.register(this, { 'initialized': [], @@ -240,14 +240,14 @@ class ChatGPTService extends Service { } addMessage(role, message) { - this._messages.push(new ChatGPTMessage(role, message)); + this._messages.push(new GPTMessage(role, message)); this.emit('newMsg', this._messages.length - 1); } send(msg) { - this._messages.push(new ChatGPTMessage('user', msg)); + this._messages.push(new GPTMessage('user', msg)); this.emit('newMsg', this._messages.length - 1); - const aiResponse = new ChatGPTMessage('assistant', 'thinking...', true, false) + const aiResponse = new GPTMessage('assistant', 'thinking...', true, false) this._messages.push(aiResponse); this.emit('newMsg', this._messages.length - 1); @@ -283,7 +283,7 @@ class ChatGPTService extends Service { } } -export default new ChatGPTService(); +export default new GPTService(); diff --git a/.config/ags/variables.js b/.config/ags/variables.js index a367114e..29e39000 100644 --- a/.config/ags/variables.js +++ b/.config/ags/variables.js @@ -1,6 +1,6 @@ import Variable from 'resource:///com/github/Aylur/ags/variable.js'; import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js'; -const { exec } = Utils; +const { exec, execAsync } = Utils; import Gdk from 'gi://Gdk'; // Global vars for external control (through keybinds) @@ -8,9 +8,20 @@ export const showMusicControls = Variable(false, {}) export const showColorScheme = Variable(false, {}) globalThis['openMusicControls'] = showMusicControls; globalThis['openColorScheme'] = showColorScheme; + globalThis['mpris'] = Mpris; // Screen size export const SCREEN_WIDTH = Number(exec(`bash -c "xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f1 | head -1" | awk '{print $1}'`)); export const SCREEN_HEIGHT = Number(exec(`bash -c "xrandr --current | grep '*' | uniq | awk '{print $1}' | cut -d 'x' -f2 | head -1" | awk '{print $1}'`)); +// Mode switching +export const currentShellMode = Variable('normal', {}) // normal, focus +globalThis['currentMode'] = currentShellMode; +globalThis['cycleMode'] = () => { + if (currentShellMode.value === 'normal') { + currentShellMode.value = 'focus'; + } else { + currentShellMode.value = 'normal'; + } +} diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index d892c022..6b099477 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -73,6 +73,7 @@ bind = Control+Super, Slash, exec, pkill anyrun || anyrun bindr = Control+Super, R, exec, killall ags ydotool; ags & bindr = Control+Super+Alt, R, exec, hyprctl reload; killall ags ydotool; ags & bind = Control+Super, T, exec, ~/.config/ags/scripts/color_generation/switchwall.sh +bind = Control+Alt, Slash, exec, ags run-js 'cycleMode();' bindir = Super, Super_L, exec, ags -t 'overview' bind = Super, Tab, exec, ags -t 'overview' bind = Super, Slash, exec, ags -t 'cheatsheet'