From 6e9f2d4a7dc9163d99796419c3a2eaa3fa7112e1 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Sat, 15 Feb 2025 08:24:00 -0300 Subject: [PATCH] :sparkles: ags: add brightness class, media widget on center-window and more --- ags/scripts/arg-handler.ts | 38 +++++- ags/scripts/brightness.ts | 42 +++++++ ags/scripts/notification-handler.ts | 25 ++-- ags/scripts/volume.ts | 17 +-- ags/style/_bar.scss | 19 +-- ags/style/_center-window.scss | 69 ++++++++--- ags/style/_control-center.scss | 27 +--- ags/style/_mixins.scss | 24 +++- ags/style/_osd.scss | 2 +- ags/widget/PopupWindow.ts | 18 +++ ags/widget/bar/FocusedClient.ts | 4 + ags/widget/bar/Media.ts | 1 + ags/widget/center-window/BigMedia.ts | 112 ++++++++++++++++- .../NotificationHistory.ts | 0 ags/widget/control-center/Tiles.ts | 26 ++-- ags/widget/control-center/tiles/MoreTile.ts | 60 --------- ags/widget/control-center/tiles/NormalTile.ts | 58 --------- ags/widget/control-center/tiles/Tile.ts | 39 ++++++ ags/window/Bar.ts | 5 +- ags/window/CenterWindow.ts | 3 + ags/window/FloatingNotifications.ts | 117 +++++++++--------- ags/window/LogoutMenu.ts | 2 - ags/window/OSD.ts | 1 - 23 files changed, 441 insertions(+), 268 deletions(-) create mode 100644 ags/scripts/brightness.ts create mode 100644 ags/widget/PopupWindow.ts rename ags/widget/{center-window => control-center}/NotificationHistory.ts (100%) delete mode 100644 ags/widget/control-center/tiles/MoreTile.ts delete mode 100644 ags/widget/control-center/tiles/NormalTile.ts create mode 100644 ags/widget/control-center/tiles/Tile.ts diff --git a/ags/scripts/arg-handler.ts b/ags/scripts/arg-handler.ts index b74919a..625ad7d 100644 --- a/ags/scripts/arg-handler.ts +++ b/ags/scripts/arg-handler.ts @@ -71,14 +71,47 @@ function handleVolumeArgs(args: Array) { if(!args[1]) return `Please specify what you want to do!\n\n${volumeHelp()}` - if(!/(sink|source)\-mute/.test(args[1]) && !args[2]) + if(/^(sink|source)(\-increase|\-decrease|\-set)$/.test(args[1]) && !args[2]) return `You forgot to add a value to be set!`; + if(Number.isNaN(Number.parseFloat(args[2])) && Number.isSafeInteger(Number.parseFloat(args[2]))) + return `Argument "${args[2]} is not a valid number! Please use integers"`; + const command: Array = args[1].split('-'); + if(/help/.test(args[1])) + return volumeHelp(); + switch(command[1]) { case "set": + command[0] === "sink" ? + Wireplumber.getDefault().setSinkVolume(Number.parseInt(args[2])) + : + Wireplumber.getDefault().setSourceVolume(Number.parseInt(args[2])) return `Done! Set ${command[0]} volume to ${args[2]}`; + + case "mute": + command[0] === "sink" ? + Wireplumber.getDefault().toggleMuteSink() + : + Wireplumber.getDefault().toggleMuteSource() + return `Done toggling mute!`; + + case "increase": + command[0] === "sink" ? + Wireplumber.getDefault().increaseSinkVolume(Number.parseInt(args[2])) + : + Wireplumber.getDefault().increaseSourceVolume(Number.parseInt(args[2])) + + return `Done increasing volume by ${args[2]}`; + + case "decrease": + command[0] === "sink" ? + Wireplumber.getDefault().decreaseSinkVolume(Number.parseInt(args[2])) + : + Wireplumber.getDefault().decreaseSourceVolume(Number.parseInt(args[2])) + + return `Done decreasing volume to ${args[2]}`; } return `Couldn't resolve arguments! "${args.join(' ').replace(new RegExp(`^${args[0]}`), "")}"`; @@ -109,9 +142,10 @@ Options: close [window_name]: sets specified window's visibility to false. toggle [window_name]: toggles visibility of specified window. reload: creates a new astal instance and removes this one. + volume: wireplumber volume controller, see "volume help". help, -h, --help: shows this help message. -2024 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License. +2025 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License. https://github.com/retrozinndev/Hyprland-Dots `.trim(); } diff --git a/ags/scripts/brightness.ts b/ags/scripts/brightness.ts new file mode 100644 index 0000000..4ce516b --- /dev/null +++ b/ags/scripts/brightness.ts @@ -0,0 +1,42 @@ +import { exec, execAsync, GObject, monitorFile, Process, readFileAsync, register, signal } from "astal"; +import { Connectable } from "astal/binding"; + + +/** !!TODO!! Needs more work and testing + * I(retrozinndev) don't have a monitor that has software-controlled brightness + */ +@register({ GTypeName: "Brightness" }) +class Brightness extends GObject.Object implements Connectable { + private readonly backlight: string|undefined; + private max: number; + private brightness: number; + + @signal(Number) + declare brightnessChanged: (value: number) => void; + + constructor(backlightDevice?: string) { + super(); + this.backlight = backlightDevice || ""; + this.max = Number.parseInt(exec(`brightnessctl -d ${backlightDevice} max`)) + this.brightness = Number.parseInt(exec(`brightnessctl -d ${backlightDevice} get`)) + + readFileAsync(`/sys/class/backlight/${backlightDevice}/brightness`).catch(() => { + throw new Error(`Couldn't find backlight ${backlightDevice}`); + }); + + monitorFile(`/sys/class/backlight/${backlightDevice}/brightness`, async () => { + this.brightness = Number.parseInt(await execAsync(`brightnessctl -d ${backlightDevice} get`)); + this.max = Number.parseInt(await execAsync(`brightnessctl -d ${backlightDevice} max`)); + + this.emit("brightness-changed", this.brightness); + }); + } + + public setBrightness(newBrightness: number): void { + execAsync(`brightnessctl -d ${this.backlight} set ${newBrightness || this.brightness}`).catch(() => { + throw new Error(`Couldn't set brightness of backlight ${this.backlight}`); + }); + + this.emit("brightness-changed", newBrightness); + } +} diff --git a/ags/scripts/notification-handler.ts b/ags/scripts/notification-handler.ts index 29186c3..95f6085 100644 --- a/ags/scripts/notification-handler.ts +++ b/ags/scripts/notification-handler.ts @@ -1,29 +1,32 @@ import AstalNotifd from "gi://AstalNotifd"; import { timeout } from "astal/time"; import { Connectable } from "astal/binding"; -import { GObject, register, property, signal } from "astal"; +import { GObject, register, signal } from "astal"; import { Windows } from "../windows"; -@register() -class Notifications extends GObject.Object implements Connectable { +@register({ GTypeName: "Notifications" }) +class NotificationsClass extends GObject.Object implements Connectable { - private static instance: Notifications; + private static instance: NotificationsClass; private notifd: AstalNotifd.Notifd; public notifications: Array = []; public notificationHistory: Array = []; - @signal() - declare "notification-added": (notification: AstalNotifd.Notification) => void; + @signal(AstalNotifd.Notification) + declare notificationAdded: (added: AstalNotifd.Notification) => void; + + @signal(Number) + declare notificationRemoved: (id: number) => void; - public static getDefault(): Notifications { - if(!Notifications.instance) { - Notifications.instance = new Notifications(); + public static getDefault(): NotificationsClass { + if(!NotificationsClass.instance) { + NotificationsClass.instance = new NotificationsClass(); this.instance._init(); } - return Notifications.instance; + return NotificationsClass.instance; } constructor() { @@ -95,3 +98,5 @@ class Notifications extends GObject.Object implements Connectable { return this.notifd; } } + +export const Notifications = new NotificationsClass(); diff --git a/ags/scripts/volume.ts b/ags/scripts/volume.ts index 2066c94..6c908a2 100644 --- a/ags/scripts/volume.ts +++ b/ags/scripts/volume.ts @@ -1,10 +1,11 @@ -import { GObject } from "astal"; +import { GObject, register } from "astal"; import AstalWp from "gi://AstalWp"; -export const Wireplumber = GObject.registerClass({ - GTypeName: "Wireplumber", - Signals: {} -}, class WireplumberClass extends GObject.Object { +export { WireplumberClass as Wireplumber }; + + +@register({ GTypeName: "Wireplumber" }) +class WireplumberClass extends GObject.Object { private static astalWireplumber: (AstalWp.Wp|null) = AstalWp.get_default(); private static inst: WireplumberClass; @@ -14,8 +15,8 @@ export const Wireplumber = GObject.registerClass({ private maxSinkVolume: number = 100; private maxSourceVolume: number = 100; - _init(...props: any[]) { - super._init(props); + constructor() { + super(); if(!WireplumberClass.astalWireplumber) throw new Error("Audio features will not work correctly! Please install wireplumber first", { @@ -145,4 +146,4 @@ export const Wireplumber = GObject.registerClass({ return this.muteSource(); } -}); +} diff --git a/ags/style/_bar.scss b/ags/style/_bar.scss index b48096e..0a6646b 100644 --- a/ags/style/_bar.scss +++ b/ags/style/_bar.scss @@ -112,27 +112,10 @@ & > button { margin: 4px 1px; - border-radius: 4px; - label { + & label { font-size: 8px; } - - &:hover { - background: colors.$bg-secondary; - } - - &:first-child { - border-top-left-radius: 12px; - border-bottom-left-radius: 12px; - margin-left: 0; - } - - &:last-child { - border-top-right-radius: 12px; - border-bottom-right-radius: 12px; - margin-right: 0; - } } } diff --git a/ags/style/_center-window.scss b/ags/style/_center-window.scss index fc3682a..ba39da2 100644 --- a/ags/style/_center-window.scss +++ b/ags/style/_center-window.scss @@ -1,38 +1,75 @@ @use "sass:color"; @use "./wal"; -@use "./functions" as funs; // Did you know that you can use the 'as' keyword? I just found out! +@use "./colors"; +@use "./functions" as funs; .center-window-container { - background: wal.$background; + background: colors.$bg-translucent; border-radius: 18px; padding: 12px; & .left { - .top { - .time { - font-size: 22px; + & > .top { + padding-bottom: 10px; + + & .time { + font-size: 28px; font-weight: 800; } - .date { + & .date { font-size: 14px; font-weight: 500; - color: funs.toRGB(color.adjust($color: wal.$foreground, $lightness: -15%)); + color: colors.$fg-disabled; + } + } + + & .big-media { + padding: 6px 16px; + + & > box > .image { + background-size: cover; + background-position: center center; + border-radius: 10px; + } + + & > .info { + padding: { + top: 4px; + bottom: 6px; + }; + + & .title { + font-size: 16px; + font-weight: 700; + } + + & .artist { + font-size: 14px; + font-weight: 600; + color: colors.$fg-disabled; + } + } + + & > .controls { + padding: 8px 0; } } } - & .calendar-box { - padding: 5px; - & calendar { - border-radius: 6px; - padding: 2px; + & .right { + & .calendar-box { + padding: 5px; + & calendar { + border-radius: 6px; + padding: 2px; - &.view { - background: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -35%)); + &.view { + background: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -35%)); - & header { - background: funs.toRGB(color.adjust($color: wal.$background, $lightness: -20%)); + & header { + background: funs.toRGB(color.adjust($color: wal.$background, $lightness: -20%)); + } } } } diff --git a/ags/style/_control-center.scss b/ags/style/_control-center.scss index c97524b..6fc4c27 100644 --- a/ags/style/_control-center.scss +++ b/ags/style/_control-center.scss @@ -1,9 +1,14 @@ @use "sass:color"; @use "./wal"; +@use "./colors"; @use "./functions" as funs; +@use "./mixins"; .control-center-container { - background: rgba(wal.$background, .65); + @include mixins.reset-props; + @include mixins.default-styles; + + background: colors.$bg-translucent; border-radius: 24px; padding: 20px 14px; @@ -58,25 +63,5 @@ margin-right: 8px; font-size: 15px; } - - trough { - background: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -20%)); - border-radius: 8px; - } - - trough highlight { - background: wal.$color1; - border-top-left-radius: inherit; - border-bottom-left-radius: inherit; - } - - trough slider { - min-width: 1.2em; - min-height: 1.2em; - border-radius: 50%; - margin: -3px 0; - background: wal.$foreground; - margin-left: -1px; - } } } diff --git a/ags/style/_mixins.scss b/ags/style/_mixins.scss index d07c513..ac75827 100644 --- a/ags/style/_mixins.scss +++ b/ags/style/_mixins.scss @@ -19,7 +19,7 @@ & > button { background: colors.$bg-secondary; margin: 0 1px; - padding: 0 6px; + padding: 4px 6px; border-radius: 2px; &:hover { @@ -78,4 +78,26 @@ color: colors.$fg-primary; } } + + & trough { + background: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -20%)); + border-radius: 8px; + margin: 2px 0; + } + + & trough highlight { + background: wal.$color1; + min-height: .9em; + border-top-left-radius: inherit; + border-bottom-left-radius: inherit; + } + + & trough slider { + border-radius: 50%; + margin: -2px 0; + background: wal.$foreground; + margin-left: -1px; + min-width: 1.2em; + min-height: 1.2em; + } } diff --git a/ags/style/_osd.scss b/ags/style/_osd.scss index 364fcd6..9d72c49 100644 --- a/ags/style/_osd.scss +++ b/ags/style/_osd.scss @@ -8,7 +8,7 @@ border-radius: 20px; .icon { - margin-right: 14px; + margin-right: 10px; font-size: 24px; } diff --git a/ags/widget/PopupWindow.ts b/ags/widget/PopupWindow.ts new file mode 100644 index 0000000..5961c2c --- /dev/null +++ b/ags/widget/PopupWindow.ts @@ -0,0 +1,18 @@ +import { Astal, Gtk, Widget } from "astal/gtk3"; + + +const { TOP, BOTTOM, LEFT, RIGHT }: typeof Astal.WindowAnchor = Astal.WindowAnchor; + +/** + * Creates a screen-size window and opens the provided window after it. + * When clicking in the transparent background window, it closes(hides) + * the provided window. + * @param window the window to be rendered and closed when clicking outside of it + */ +export function PopupWindow(window: Gtk.Window) { + const bgWindow: Gtk.Window = new Widget.Window({ + namespace: "popup-bg-window", + anchor: TOP | BOTTOM | LEFT | RIGHT, + + } as Widget.WindowProps); +} diff --git a/ags/widget/bar/FocusedClient.ts b/ags/widget/bar/FocusedClient.ts index fb9930c..fcafa29 100644 --- a/ags/widget/bar/FocusedClient.ts +++ b/ags/widget/bar/FocusedClient.ts @@ -29,11 +29,15 @@ export function FocusedClient() { new Widget.Label({ className: "class", xalign: 0, + max_width_chars: 65, + truncate: false, label: bind(focusedClient, "class") } as Widget.LabelProps), new Widget.Label({ className: "title", xalign: 0, + max_width_chars: 48, + truncate: false, label: bind(focusedClient, "title") } as Widget.LabelProps) ] : [] diff --git a/ags/widget/bar/Media.ts b/ags/widget/bar/Media.ts index e05e744..6ab151a 100644 --- a/ags/widget/bar/Media.ts +++ b/ags/widget/bar/Media.ts @@ -54,6 +54,7 @@ export function Media(): Gtk.Widget { new Widget.Button({ className: "next nf", label: "󰒭", + tooltipText: "Next", onClick: () => players[0].canGoNext && players[0].next() } as Widget.ButtonProps) ] : new Widget.Label({ diff --git a/ags/widget/center-window/BigMedia.ts b/ags/widget/center-window/BigMedia.ts index 7b323bc..687749a 100644 --- a/ags/widget/center-window/BigMedia.ts +++ b/ags/widget/center-window/BigMedia.ts @@ -1,6 +1,116 @@ +import { AstalIO, bind, GLib, Process, timeout } from "astal"; import { Gtk, Widget } from "astal/gtk3"; +import AstalMpris from "gi://AstalMpris"; + +let dragTimer: (AstalIO.Time|undefined); export const BigMedia: Gtk.Widget = new Widget.Box({ className: "big-media", - //TODO + orientation: Gtk.Orientation.VERTICAL, + homogeneous: false, + children: bind(AstalMpris.get_default(), "players").as((players: Array) => + players[0] ? [ + new Widget.Box({ + halign: Gtk.Align.CENTER, + child: new Widget.Box({ + className: "image", + hexpand: false, + orientation: Gtk.Orientation.VERTICAL, + visible: bind(players[0], "coverArt").as((coverArt: string) => + coverArt !== ""), + css: bind(players[0], "coverArt").as((coverArt: string) => + `.image { background-image: url('${coverArt}'); }`), + width_request: 132, + height_request: 128 + } as Widget.BoxProps) + } as Widget.BoxProps), + new Widget.Box({ + className: "info", + orientation: Gtk.Orientation.VERTICAL, + children: [ + new Widget.Label({ + className: "title", + tooltipText: bind(players[0], "title").as((title: string) => !title ? "No Title" : title), + label: bind(players[0], "title").as((title: string) => !title ? "No Title" : title), + truncate: true + } as Widget.LabelProps), + new Widget.Label({ + className: "artist", + tooltipText: bind(players[0], "artist").as((artist: string) => !artist ? "No Artist" : artist), + label: bind(players[0], "artist").as((artist: string) => !artist ? "No Artist" : artist), + truncate: true + } as Widget.LabelProps) + ] + } as Widget.BoxProps), + new Widget.Box({ + className: "progress", + hexpand: true, + visible: bind(players[0], "canSeek"), + children: [ + /*new Widget.Label({ + className: "elapsed", + label: bind(players[0], "position").as((position: number) => + Math.floor(position).toString()) + }),*/ + new Widget.Slider({ + min: 0, + hexpand: true, + max: bind(players[0], "length").as((length: number) => + Math.floor(length)), + value: bind(players[0], "position").as((position: number) => + Math.floor(position)), + onDragged: (slider: Widget.Slider) => { + if(dragTimer === undefined) + dragTimer = timeout(600, () => + players[0].set_position(Math.round(slider.value))); + else { + dragTimer.cancel(); + dragTimer = timeout(600, () => + players[0].set_position(Math.round(slider.value))); + } + } + }) + ] + }), + new Widget.Box({ + className: "controls button-row", + hexpand: true, + halign: Gtk.Align.CENTER, + children: [ + new Widget.Button({ + className: "link nf", + label: "󰌹", + tooltipText: "Copy link to Clipboard", + visible: bind(players[0], "metadata").as((_metadata: GLib.HashTable) => + players[0].get_meta("xesam:url") === null), + onClick: () => Process.exec(`wl-copy ${players[0].get_meta("xesam:url")?.get_string()[0]}`) + } as Widget.ButtonProps), + new Widget.Button({ + className: "previous nf", + label: "󰒮", + tooltipText: "Previous", + onClick: () => players[0].canGoPrevious && players[0].previous() + } as Widget.ButtonProps), + new Widget.Button({ + className: "pause nf", + tooltipText: bind(players[0], "playback_status").as((status: AstalMpris.PlaybackStatus) => + status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play"), + label: bind(players[0], "playbackStatus").as((status: AstalMpris.PlaybackStatus) => + status === AstalMpris.PlaybackStatus.PLAYING ? "󰏤" : "󰐊"), + onClick: () => { + players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ? + players[0].play() + : + players[0].pause() + } + } as Widget.ButtonProps), + new Widget.Button({ + className: "next nf", + label: "󰒭", + tooltipText: "Next", + onClick: () => players[0].canGoNext && players[0].next() + } as Widget.ButtonProps) + ] + }) + ] : new Widget.Box({ className: "empty no-media" })) } as Widget.BoxProps); diff --git a/ags/widget/center-window/NotificationHistory.ts b/ags/widget/control-center/NotificationHistory.ts similarity index 100% rename from ags/widget/center-window/NotificationHistory.ts rename to ags/widget/control-center/NotificationHistory.ts diff --git a/ags/widget/control-center/Tiles.ts b/ags/widget/control-center/Tiles.ts index 63c3582..a1f7c4e 100644 --- a/ags/widget/control-center/Tiles.ts +++ b/ags/widget/control-center/Tiles.ts @@ -1,12 +1,22 @@ import { Gtk, Widget } from "astal/gtk3"; -export const tileList: Array = [ -] +export const tileList: Array = []; -export const Tiles: Widget.Box = new Widget.Box({ - child: new Gtk.Grid({ +export function TilesWidget(): Gtk.Widget { + const tilesFlowBox: Gtk.FlowBox = new Gtk.FlowBox({ visible: true, - orientation: Gtk.Orientation.HORIZONTAL, - rowHomogeneous: true - } as Gtk.Grid.ConstructorProps) -} as Widget.BoxProps); + noShowAll: false, + orientation: Gtk.Orientation.HORIZONTAL + } as Gtk.Grid.ConstructorProps); + + tileList.map((item: Gtk.Widget) => + tilesFlowBox.insert(item, -1)); + + return new Widget.Box({ + children: [ + tilesFlowBox + ] + } as Widget.BoxProps); +} + +export const Tiles: Gtk.Widget = TilesWidget(); diff --git a/ags/widget/control-center/tiles/MoreTile.ts b/ags/widget/control-center/tiles/MoreTile.ts deleted file mode 100644 index 2b3973f..0000000 --- a/ags/widget/control-center/tiles/MoreTile.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Binding } from "astal"; -import { Gtk, Widget } from "astal/gtk3"; - -export interface MoreTileProps { - className?: string | Binding; - iconName?: string | Binding; - iconSize?: Gtk.IconSize; - title: string | Binding; - description?: string | Binding; - defaultToggleState?: boolean; - onToggledOn: Function; - onToggledOff: Function; - onClickMore: Function; -} - -export function MoreTile(props: MoreTileProps): Gtk.Widget { - - let toggleState: boolean = props?.defaultToggleState !== undefined ? - props.defaultToggleState : false; - - const mainEventBox = new Widget.EventBox({ - onClick: () => toggleState ? props.onToggledOff() : props.onToggledOn(), - expand: true, - child: new Widget.Box({ - className: props?.className || "", - expand: true, - children: [ - new Widget.Icon({ - iconName: props?.iconName, - visible: props.iconName !== undefined, - iconSize: props.iconSize || Gtk.IconSize.BUTTON - }), - new Widget.Box({ - className: "text", - orientation: Gtk.Orientation.VERTICAL, - children: [ - new Widget.Label({ - className: "title", - label: props.title - } as Widget.LabelProps), - new Widget.Label({ - className: "description", - visible: props?.description !== undefined, - label: props?.description - } as Widget.LabelProps) - ] - } as Widget.BoxProps), - new Widget.Button({ - onClick: () => props.onClickMore(), - child: new Widget.Icon({ - iconName: "go-next", - iconSize: Gtk.IconSize.BUTTON - } as Widget.IconProps), - } as Widget.ButtonProps) - ] - } as Widget.BoxProps) - } as Widget.EventBoxProps); - - return mainEventBox; -} diff --git a/ags/widget/control-center/tiles/NormalTile.ts b/ags/widget/control-center/tiles/NormalTile.ts deleted file mode 100644 index 7c360c6..0000000 --- a/ags/widget/control-center/tiles/NormalTile.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Binding, Variable } from "astal"; -import { Gtk, Widget } from "astal/gtk3"; - -export interface NormalTileProps { - className?: string | Binding; - iconName?: string | Binding; - iconSize?: Gtk.IconSize; - title: string | Binding; - description?: string | Binding; - toggleState?: boolean | Binding; - onToggledOn: Function; - onToggledOff: Function; -} - -export function MoreTile(props: NormalTileProps): Gtk.Widget { - - const mainEventBox = new Widget.EventBox({ - onClick: () => toggleState ? props.onToggledOff() : props.onToggledOn(), - expand: true, - child: new Widget.Box({ - className: props?.className || "", - expand: true, - children: [ - new Widget.Icon({ - iconName: props?.iconName, - visible: props.iconName !== undefined, - iconSize: props.iconSize || Gtk.IconSize.BUTTON - }), - new Widget.Box({ - className: "text", - orientation: Gtk.Orientation.VERTICAL, - children: [ - new Widget.Label({ - className: "title", - label: props.title - } as Widget.LabelProps), - new Widget.Label({ - className: "description", - visible: props?.description !== undefined, - label: props?.description - } as Widget.LabelProps) - ] - } as Widget.BoxProps) - ] - } as Widget.BoxProps) - } as Widget.EventBoxProps); - - function toggleOn(): void { - mainEventBox.set_class_name(mainEventBox + "") - props.onToggledOn(); - } - - function toggleOff(): void { - props.onToggledOff(); - } - - return mainEventBox; -} diff --git a/ags/widget/control-center/tiles/Tile.ts b/ags/widget/control-center/tiles/Tile.ts new file mode 100644 index 0000000..78bdb39 --- /dev/null +++ b/ags/widget/control-center/tiles/Tile.ts @@ -0,0 +1,39 @@ +import { Binding } from "astal"; +import { Gtk, Widget } from "astal/gtk3"; + +export type TileProps = { + className?: string | Binding; + iconName?: string | Binding; + visible?: boolean | Binding; + iconSize?: number | Binding; + title: string | Binding; + description?: string | Binding; + defaultToggleState?: boolean; + onToggledOn: () => void; + onToggledOff: () => void; + onClickMore?: () => void; +} + +export function Tile(props: TileProps): Widget.Box { + + const toggleButton = new Gtk.ToggleButton(); + toggleButton.set_active(props.defaultToggleState || false); + + const moreButton = new Widget.Button({ + className: "more", + visible: props.onClickMore + }); + + return new Widget.Box({ + className: (typeof Binding) === (typeof props.className) ? + (props.className as Binding).as((clsName: (string|undefined)) => + `tile ${clsName || ""}`) + : + props.className, + visible: props.visible, + children: [ + toggleButton, + moreButton + ] + }) +} diff --git a/ags/window/Bar.ts b/ags/window/Bar.ts index 7c49d91..416246c 100644 --- a/ags/window/Bar.ts +++ b/ags/window/Bar.ts @@ -1,4 +1,4 @@ -import { Gdk, Astal, Gtk, Widget } from "astal/gtk3"; +import { Astal, Gtk, Widget } from "astal/gtk3"; import { Clock } from "../widget/bar/Clock"; import { Logo } from "../widget/bar/Logo"; @@ -11,12 +11,11 @@ import { Media } from "../widget/bar/Media"; export const Bar: Widget.Window = new Widget.Window({ monitor: 0, namespace: "top-bar", - anchor: Astal.WindowAnchor.TOP, + anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT, layer: Astal.Layer.TOP, exclusivity: Astal.Exclusivity.EXCLUSIVE, canFocus: false, visible: true, - widthRequest: Gdk.Screen.get_default()?.get_monitor_geometry(0)?.width, child: new Widget.Box({ className: "bar-container", child: new Widget.CenterBox({ diff --git a/ags/window/CenterWindow.ts b/ags/window/CenterWindow.ts index 934ffa9..1163386 100644 --- a/ags/window/CenterWindow.ts +++ b/ags/window/CenterWindow.ts @@ -2,6 +2,7 @@ import { Astal, Gtk, Widget } from "astal/gtk3"; import { GLib } from "astal"; import { getDateTime } from "../scripts/time"; +import { BigMedia } from "../widget/center-window/BigMedia"; export const CenterWindow: Widget.Window = new Widget.Window({ className: "center-window", @@ -25,6 +26,7 @@ export const CenterWindow: Widget.Window = new Widget.Window({ new Widget.Box({ className: "top", orientation: Gtk.Orientation.VERTICAL, + valign: Gtk.Align.START, children: [ new Widget.Label({ className: "time", @@ -38,6 +40,7 @@ export const CenterWindow: Widget.Window = new Widget.Window({ } as Widget.LabelProps) ] } as Widget.BoxProps), + BigMedia ] } as Widget.BoxProps), new Widget.Box({ diff --git a/ags/window/FloatingNotifications.ts b/ags/window/FloatingNotifications.ts index ba2446d..3d2d343 100644 --- a/ags/window/FloatingNotifications.ts +++ b/ags/window/FloatingNotifications.ts @@ -3,10 +3,65 @@ import AstalNotifd from "gi://AstalNotifd"; import { bind } from "astal"; import { Notifications } from "../scripts/notification-handler"; +function NotificationWidget(notification: AstalNotifd.Notification): Gtk.Widget { + return new Widget.Box({ + className: "notification", + homogeneous: false, + expand: false, + orientation: Gtk.Orientation.VERTICAL, + children: [ + new Widget.Box({ + className: "top", + orientation: Gtk.Orientation.HORIZONTAL, + hexpand: true, + vexpand: false, + children: [ + new Widget.Label({ + className: "app-name", + halign: Gtk.Align.START, + label: notification.appName || "Unknown Application" + } as Widget.LabelProps), + new Widget.Button({ + className: "close-button", + onClick: () => Notifications.removeNotification(notification.id) + } as Widget.ButtonProps) + ] + } as Widget.BoxProps), + new Widget.Box({ + className: "content", + orientation: Gtk.Orientation.HORIZONTAL, + children: [ + new Widget.Box({ + className: "image", + visible: notification.image !== "", + css: `box.image { background-image: url('${notification.image}'); }` + } as Widget.BoxProps), + new Widget.Box({ + className: "text", + orientation: Gtk.Orientation.VERTICAL, + children: [ + new Widget.Label({ + className: "summary", + useMarkup: true, + label: notification.summary + }), + new Widget.Label({ + className: "body", + useMarkup: true, + label: notification.body + } as Widget.LabelProps) + ] + } as Widget.BoxProps) + ] + } as Widget.BoxProps) + ] + } as Widget.BoxProps); +} + export const FloatingNotifications: Widget.Window = new Widget.Window({ namespace: "floating-notifications", canFocus: false, - anchor: Astal.WindowAnchor.RIGHT, + anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT, monitor: 0, layer: Astal.Layer.OVERLAY, visible: false, @@ -16,62 +71,8 @@ export const FloatingNotifications: Widget.Window = new Widget.Window({ className: "floating-notifications-container", orientation: Gtk.Orientation.VERTICAL, homogeneous: false, - children: bind(Notifications, "notifications").as((notifications: Array) => { - console.log("something changed!"); - return notifications.map((notification: AstalNotifd.Notification) => - new Widget.Box({ - className: "notification", - homogeneous: false, - expand: false, - orientation: Gtk.Orientation.VERTICAL, - children: [ - new Widget.Box({ - className: "top", - orientation: Gtk.Orientation.HORIZONTAL, - hexpand: true, - vexpand: false, - children: [ - new Widget.Label({ - className: "app-name", - halign: Gtk.Align.START, - label: notification.appName || "Unknown Application" - } as Widget.LabelProps), - new Widget.Button({ - className: "close-button", - onClick: () => Notifications.removeNotification(notification.id) - } as Widget.ButtonProps) - ] - } as Widget.BoxProps), - new Widget.Box({ - className: "content", - orientation: Gtk.Orientation.HORIZONTAL, - children: [ - new Widget.Box({ - className: "image", - visible: notification.image !== "", - css: `.image { background-image: url('${notification.image}'); }` - } as Widget.BoxProps), - new Widget.Box({ - className: "text", - orientation: Gtk.Orientation.VERTICAL, - children: [ - new Widget.Label({ - className: "summary", - useMarkup: true, - label: notification.summary - }), - new Widget.Label({ - className: "body", - useMarkup: true, - label: notification.body - } as Widget.LabelProps) - ] - } as Widget.BoxProps) - ] - } as Widget.BoxProps) - ] - } as Widget.BoxProps) - ) - }) + children: bind(Notifications, "notifications").as((notifications: Array) => + notifications.map((notification: AstalNotifd.Notification) => + NotificationWidget(notification))) } as Widget.BoxProps) } as Widget.WindowProps); diff --git a/ags/window/LogoutMenu.ts b/ags/window/LogoutMenu.ts index 6e1285e..50274c7 100644 --- a/ags/window/LogoutMenu.ts +++ b/ags/window/LogoutMenu.ts @@ -12,8 +12,6 @@ export const LogoutMenu: Widget.Window = new Widget.Window({ exclusivity: Astal.Exclusivity.IGNORE, monitor: 0, visible: false, - widthRequest: Gdk.Screen.get_default()?.get_monitor_geometry(0)?.width, - height_request: Gdk.Screen.get_default()?.get_monitor_geometry(0)?.height, child: new Widget.EventBox({ className: "logout-menu", onClick: () => Process.exec_async("astal close logout-menu", () => {}), diff --git a/ags/window/OSD.ts b/ags/window/OSD.ts index 6d27780..8e16562 100644 --- a/ags/window/OSD.ts +++ b/ags/window/OSD.ts @@ -1,7 +1,6 @@ import { bind, Binding, Variable } from "astal"; import { Astal, Gtk, Widget } from "astal/gtk3"; import { Wireplumber } from "../scripts/volume"; -import AstalWp from "gi://AstalWp?version=0.1"; export enum OSDModes { SINK,