From 579070733cf9605e96f045158ba56dcc5a9fdb80 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Wed, 2 Apr 2025 22:12:06 +1100 Subject: feat: define bar layout via config --- scss/bar.scss | 6 +- src/config/defaults.ts | 59 ++++++++-------- src/config/funcs.ts | 24 ++++--- src/config/types.ts | 34 +++++---- src/modules/bar.tsx | 185 +++++++++++++++++++++++++++---------------------- 5 files changed, 172 insertions(+), 136 deletions(-) diff --git a/scss/bar.scss b/scss/bar.scss index fa28895..ca292fa 100644 --- a/scss/bar.scss +++ b/scss/bar.scss @@ -280,7 +280,7 @@ margin-right: -1px; } - .power { + .last { padding-right: lib.s(12); } } @@ -304,11 +304,11 @@ margin-bottom: -1px; } - .os-icon { + .first { padding-top: lib.s(12); } - .power { + .last { padding-bottom: lib.s(12); } } diff --git a/src/config/defaults.ts b/src/config/defaults.ts index c67d140..570ed16 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -10,43 +10,44 @@ export default { bar: { vertical: true, style: "gaps", // One of "gaps", "panel", "embedded" - modules: { - osIcon: { - enabled: true, - }, - activeWindow: { - enabled: true, - }, - mediaPlaying: { - enabled: true, + layout: { + type: "centerbox", // One of "centerbox", "flowbox" + centerbox: { + start: ["osIcon", "activeWindow", "mediaPlaying", "brightnessSpacer"], + center: ["workspaces"], + end: [ + "volumeSpacer", + "tray", + "statusIcons", + "pkgUpdates", + "notifCount", + "battery", + "dateTime", + "power", + ], }, + flowbox: [ + "osIcon", + "workspaces", + "brightnessSpacer", + "activeWindow", + "volumeSpacer", + "dateTime", + "tray", + "battery", + "statusIcons", + "notifCount", + "power", + ], + }, + modules: { workspaces: { - enabled: true, shown: 5, }, - tray: { - enabled: true, - }, - statusIcons: { - enabled: true, - }, - pkgUpdates: { - enabled: true, - }, - notifCount: { - enabled: true, - }, - battery: { - enabled: true, - }, dateTime: { - enabled: true, format: "%d/%m/%y %R", detailedFormat: "%c", }, - power: { - enabled: true, - }, }, }, launcher: { diff --git a/src/config/funcs.ts b/src/config/funcs.ts index 253164e..5b9efa4 100644 --- a/src/config/funcs.ts +++ b/src/config/funcs.ts @@ -25,16 +25,20 @@ const isCorrectType = (v: any, type: string | string[] | number[], path: string) try { // Recursively check type const type = JSON.parse(arrType); - const valid = v.filter((item, i) => - Object.entries(type).some(([k, t]) => { - if (!item[k]) { - console.warn(`Invalid shape for ${path}[${i}]: ${JSON.stringify(item)} != ${arrType}`); - return false; - } - return !isCorrectType(item[k], t as any, `${path}[${i}].${k}`); - }) - ); - v.splice(0, v.length, ...valid); // In-place filter + if (Array.isArray(type)) { + v.splice(0, v.length, ...v.filter((item, i) => isCorrectType(item, type, `${path}[${i}]`))); + } else { + const valid = v.filter((item, i) => + Object.entries(type).some(([k, t]) => { + if (!item[k]) { + console.warn(`Invalid shape for ${path}[${i}]: ${JSON.stringify(item)} != ${arrType}`); + return false; + } + return !isCorrectType(item[k], t as any, `${path}[${i}].${k}`); + }) + ); + v.splice(0, v.length, ...valid); // In-place filter + } } catch { const valid = v.filter((item, i) => { if (typeof item !== arrType) { diff --git a/src/config/types.ts b/src/config/types.ts index c5baf99..f5ca03a 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -1,9 +1,25 @@ const BOOL = "boolean"; const STR = "string"; const NUM = "number"; -const ARR = (type: string) => `array of ${type}`; +const ARR = (type: string | string[]) => `array of ${typeof type === "string" ? type : JSON.stringify(type)}`; const OBJ_ARR = (shape: object) => ARR(JSON.stringify(shape)); +const barModules = [ + "osIcon", + "activeWindow", + "mediaPlaying", + "brightnessSpacer", + "workspaces", + "volumeSpacer", + "tray", + "statusIcons", + "pkgUpdates", + "notifCount", + "battery", + "dateTime", + "power", +]; + export default { "style.transparency": ["off", "normal", "high"], "style.borders": BOOL, @@ -11,20 +27,14 @@ export default { // Bar "bar.vertical": BOOL, "bar.style": ["gaps", "panel", "embedded"], - "bar.modules.osIcon.enabled": BOOL, - "bar.modules.activeWindow.enabled": BOOL, - "bar.modules.mediaPlaying.enabled": BOOL, - "bar.modules.workspaces.enabled": BOOL, + "bar.layout.type": ["centerbox", "flowbox"], + "bar.layout.centerbox.start": ARR(barModules), + "bar.layout.centerbox.center": ARR(barModules), + "bar.layout.centerbox.end": ARR(barModules), + "bar.layout.flowbox": ARR(barModules), "bar.modules.workspaces.shown": NUM, - "bar.modules.tray.enabled": BOOL, - "bar.modules.statusIcons.enabled": BOOL, - "bar.modules.pkgUpdates.enabled": BOOL, - "bar.modules.notifCount.enabled": BOOL, - "bar.modules.battery.enabled": BOOL, - "bar.modules.dateTime.enabled": BOOL, "bar.modules.dateTime.format": STR, "bar.modules.dateTime.detailedFormat": STR, - "bar.modules.power.enabled": BOOL, // Launcher "launcher.style": ["lines", "round"], "launcher.actionPrefix": STR, diff --git a/src/modules/bar.tsx b/src/modules/bar.tsx index 386a1ba..c5a4fbb 100644 --- a/src/modules/bar.tsx +++ b/src/modules/bar.tsx @@ -10,7 +10,7 @@ import { setupCustomTooltip } from "@/utils/widgets"; import ScreenCorner from "@/widgets/screencorner"; import { execAsync, Variable } from "astal"; import { bind, kebabify } from "astal/binding"; -import { App, Astal, Gtk, type Widget } from "astal/gtk3"; +import { App, Astal, Gtk, Widget } from "astal/gtk3"; import { bar as config } from "config"; import AstalBattery from "gi://AstalBattery"; import AstalBluetooth from "gi://AstalBluetooth"; @@ -20,12 +20,14 @@ import AstalNotifd from "gi://AstalNotifd"; import AstalTray from "gi://AstalTray"; import AstalWp from "gi://AstalWp"; -interface SpacerClassNameProps { - beforeSpacer?: boolean; - afterSpacer?: boolean; +interface ClassNameProps { + beforeSpacer: boolean; + afterSpacer: boolean; + first: boolean; + last: boolean; } -interface ModuleProps extends SpacerClassNameProps { +interface ModuleProps extends ClassNameProps { monitor: Monitor; } @@ -83,13 +85,33 @@ const switchPane = (monitor: Monitor, name: string) => { } }; -const spacerClassName = ({ beforeSpacer, afterSpacer }: SpacerClassNameProps) => - `${beforeSpacer ? "before-spacer" : ""} ${afterSpacer ? "after-spacer" : ""}`; +const getClassName = ({ beforeSpacer, afterSpacer, first, last }: ClassNameProps) => + `${beforeSpacer ? "before-spacer" : ""} ${afterSpacer ? "after-spacer" : ""}` + + ` ${first ? "first" : ""} ${last ? "last" : ""}`; + +const getModule = (module: string) => { + module = module.toLowerCase(); + if (module === "osicon") return OSIcon; + if (module === "activewindow") return ActiveWindow; + if (module === "mediaplaying") return MediaPlaying; + if (module === "workspaces") return Workspaces; + if (module === "tray") return Tray; + if (module === "statusicons") return StatusIcons; + if (module === "pkgupdates") return PkgUpdates; + if (module === "notifcount") return NotifCount; + if (module === "battery") return Battery; + if (module === "datetime") return DateTime; + if (module === "power") return Power; + if (module === "brightnessspacer") return BrightnessSpacer; + if (module === "volumespacer") return VolumeSpacer; + return () => null; +}; + +const isSpacer = (module?: string) => module?.toLowerCase().endsWith("spacer") ?? false; const OSIcon = ({ monitor, ...props }: ModuleProps) => ( ); -const BluetoothDevice = (device: AstalBluetooth.Device) => ( +const BluetoothDevice = ({ monitor, device }: { monitor: Monitor; device: AstalBluetooth.Device }) => ( ); -const Bluetooth = () => ( +const Bluetooth = ({ monitor }: { monitor: Monitor }) => ( - {bind(AstalBluetooth.get_default(), "devices").as(d => d.map(BluetoothDevice))} + {bind(AstalBluetooth.get_default(), "devices").as(d => + d.map(d => ) + )} ); const StatusIcons = ({ monitor, ...props }: ModuleProps) => ( - - - + + + ); const PkgUpdates = ({ monitor, ...props }: ModuleProps) => (