diff --git a/ags/app.ts b/ags/app.ts index e153390..8a25f48 100644 --- a/ags/app.ts +++ b/ags/app.ts @@ -26,6 +26,7 @@ App.start({ Wireplumber.getDefault().getDefaultSink().connect("notify::volume", () => !Windows.isVisible(ControlCenter) && triggerOSD(OSDModes.SINK)); + } }); diff --git a/ags/scripts/arg-handler.ts b/ags/scripts/arg-handler.ts index a94223c..b74919a 100644 --- a/ags/scripts/arg-handler.ts +++ b/ags/scripts/arg-handler.ts @@ -19,7 +19,7 @@ export function handleArguments(request: string): any { return handleVolumeArgs(args); case "reload": - restartInstance({ log: false, instanceName: "astal" }); + restartInstance(); return "Reloading instance..." default: diff --git a/ags/scripts/notification-handler.ts b/ags/scripts/notification-handler.ts index fdc8a13..29186c3 100644 --- a/ags/scripts/notification-handler.ts +++ b/ags/scripts/notification-handler.ts @@ -1,63 +1,97 @@ import AstalNotifd from "gi://AstalNotifd"; import { timeout } from "astal/time"; +import { Connectable } from "astal/binding"; +import { GObject, register, property, signal } from "astal"; +import { Windows } from "../windows"; -const notifd: AstalNotifd.Notifd = new AstalNotifd.Notifd({ - ignoreTimeout: false, - dontDisturb: false -}); +@register() +class Notifications extends GObject.Object implements Connectable { -export let notifications: Array = getNotifd().notifications; -export let notificationHistory: Array = []; + private static instance: Notifications; + private notifd: AstalNotifd.Notifd; -notifd.connect("notified", (_source: AstalNotifd.Notifd, id: number, _replaced: boolean) => { - addNotification(getNotifd().get_notification(id)); -}); + public notifications: Array = []; + public notificationHistory: Array = []; -function addNotification(notification: AstalNotifd.Notification) { - prependArray(notifications, getNotifd().get_notification(notification.id)); + @signal() + declare "notification-added": (notification: AstalNotifd.Notification) => void; - // default timeout if undefined - let notificationTimeout = 4000; - switch(notification.urgency) { - case AstalNotifd.Urgency.LOW: - notificationTimeout = 2000; - break; - case AstalNotifd.Urgency.NORMAL: - notificationTimeout = 4000; - break; + public static getDefault(): Notifications { + if(!Notifications.instance) { + Notifications.instance = new Notifications(); + this.instance._init(); + } + + return Notifications.instance; } - notification.urgency !== AstalNotifd.Urgency.CRITICAL && timeout(notificationTimeout, () => { - notificationTimeout--; - if(notificationTimeout === 0) { - removeNotification(notification.id); - addToNotificationHistory(notification); - }; - }); -} + constructor() { + super(); + this.notifd = new AstalNotifd.Notifd({ + ignoreTimeout: true, + dontDisturb: false + } as AstalNotifd.Notifd.ConstructorProps); -export function removeNotification(notificationId: number) { - notifications = notifications.filter((notification: AstalNotifd.Notification) => - notification.id !== notificationId); -} + this.getNotifd().connect("notified", (_source: AstalNotifd.Notifd, id: number, _replaced: boolean) => { + this.addNotification(this.getNotifd().get_notification(id)); + }); + } -function addToNotificationHistory(notification: AstalNotifd.Notification) { - prependArray(notificationHistory, notification); -} + public addNotification(notification: AstalNotifd.Notification) { + this.prependArray(this.notifications, this.getNotifd().get_notification(notification.id)); -export function removeFromNotificationHistory(notificationId: number) { - notifications = notifications.filter((curNotification: AstalNotifd.Notification) => - curNotification.id !== notificationId); -} + // default timeout if undefined + let notificationTimeout = 4000; -function prependArray(array: Array, item: any) { - let tmpArray = array; - tmpArray.reverse(); - tmpArray.push(item); - array = tmpArray.reverse(); -} + switch(notification.urgency) { + case AstalNotifd.Urgency.LOW: + notificationTimeout = 2000; + break; + case AstalNotifd.Urgency.NORMAL: + notificationTimeout = 4000; + break; + } -export function getNotifd(): AstalNotifd.Notifd { - return notifd; + notification.urgency !== AstalNotifd.Urgency.CRITICAL ? + timeout(notificationTimeout, () => { + this.notifications.map((item: AstalNotifd.Notification) => + item.id === notification.id && (() => { + this.removeNotification(notification.id); + this.addToNotificationHistory(notification); + })()) + }) + : this.addToNotificationHistory(notification); + + this.emit("notification-added", notification); + } + + public removeNotification(notificationId: number) { + if(this.notifications.length === 1) + Windows.close(Windows.getWindow("floating-notifications")!); + + this.notifications = this.notifications.filter((notification: AstalNotifd.Notification) => + notification.id !== notificationId); + + this.emit("notification-removed", notificationId); + } + + public addToNotificationHistory(notification: AstalNotifd.Notification) { + this.prependArray(this.notificationHistory, notification); + } + + public removeFromNotificationHistory(notificationId: number) { + this.notificationHistory = this.notificationHistory.filter((curNotification: AstalNotifd.Notification) => + curNotification.id !== notificationId); + } + + private prependArray(array: Array, item: any): Array { + let tmpArray = array.reverse(); + tmpArray.push(item); + return tmpArray.reverse(); + } + + public getNotifd(): AstalNotifd.Notifd { + return this.notifd; + } } diff --git a/ags/scripts/reload-handler.ts b/ags/scripts/reload-handler.ts index 088d35b..d04f82a 100644 --- a/ags/scripts/reload-handler.ts +++ b/ags/scripts/reload-handler.ts @@ -1,30 +1,18 @@ import { monitorFile, Process } from "astal"; -import { getUserDirs } from "./user"; import { App } from "astal/gtk3"; const monitoringPaths = [ "./scripts", "./window", "./app.ts", "env.d.ts" ]; -export interface InstanceProps { - instanceName?: string; - log?: boolean; -} - -export function restartInstance(props: InstanceProps = { instanceName: "astal", log: false }): void { - Process.exec_async(`astal -q ${props.instanceName}`, () => {}); - Process.exec_async(`ags run ${ props.log && `--log-file - ${ getUserDirs().cache}/ags-${ App.instanceName || "astal" }.log` }`.replaceAll('\n', ' ').trim(), - () => {} - ) +export function restartInstance(instanceName?: string): void { + Process.exec_async(`astal -q ${ instanceName || App.instanceName || "astal" }`, () => {}); + Process.exec_async(`ags run`, () => {}); } export function monitorPaths(): void { monitoringPaths.map((path: string) => { monitorFile( path, - () => restartInstance({ - instanceName: App.instanceName || "astal", - log: true - }) + () => restartInstance() ) }); } diff --git a/ags/scripts/style-handler.ts b/ags/scripts/style-handler.ts index 0f376ce..08bb97a 100644 --- a/ags/scripts/style-handler.ts +++ b/ags/scripts/style-handler.ts @@ -23,7 +23,7 @@ export function reloadStyle(): void { export function compileStyle(): void { console.log("[LOG] Compiling sass (stylesheet)"); Process.exec(`mkdir -p ${stylePath}`); - Process.exec(`bash -c "sass -I ./style ./style.scss ${stylePath}/style.css"`); + Process.exec(`sh -c "sass -I ./style ./style.scss ${stylePath}/style.css"`); } export function applyStyle(): void { diff --git a/ags/style.scss b/ags/style.scss index 2325934..5cf8c3e 100644 --- a/ags/style.scss +++ b/ags/style.scss @@ -1,55 +1,18 @@ @use "sass:color"; -@use "./style/bar"; + @use "./style/wal"; +@use "./style/mixins"; + +@use "./style/bar"; @use "./style/osd"; @use "./style/control-center"; +@use "./style/center-window"; + * { - all: unset; - transition: 120ms linear; - color: color.adjust($color: wal.$foreground, $lightness: 15%); + @include mixins.reset-props; } -.button-row { - & > button { - background: color.scale($color: wal.$foreground, $lightness: -30%, $alpha: 70%); - margin: 0 1px; - border-radius: 2px; - - &:hover { - background: color.scale($color: wal.$foreground, $lightness: -30%, $alpha: 100%); - } - - &:first-child { - border-top-left-radius: 10px; - border-bottom-left-radius: 10px; - } - - &:last-child { - border-top-right-radius: 10px; - border-bottom-right-radius: 10px; - } - } -} - -menu { - padding: 4px; - background: wal.$background; - border-radius: 14px; - - & separator { - margin: 0 4px; - color: wal.$background; - } - - & menuitem { - padding: 8px 0px; - border-radius: 10px; - font-size: 12px; - font-weight: 600; - - &:hover { - background: wal.$color1; - } - } +window * { + @include mixins.default-styles; } diff --git a/ags/style/_bar.scss b/ags/style/_bar.scss index 161989a..b48096e 100644 --- a/ags/style/_bar.scss +++ b/ags/style/_bar.scss @@ -1,39 +1,31 @@ @use "sass:color"; -@use "./wal"; @use "./mixins"; +@use "./colors"; .bar-container { + @include mixins.reset-props; + @include mixins.default-styles; + padding: 6px; padding-bottom: 0px; - & { - button { - padding: 6px 8px; - border-radius: 12px; - - &:hover { - background: wal.$color1; - } - } - - label { - font-size: 12px; - font-family: "Cantarell", "Noto Sans"; - font-weight: 500; - } + label { + @include mixins.reset-props; + font-size: 12px; + font-weight: 500; } // Style widget groups & > .bar-centerbox > * { - background: rgba($color: wal.$background, $alpha: .6); + background: colors.$bg-translucent; padding: 5px; border-radius: 18px; // Style widgets - & > *, - & > * > button - & > * { + & > button, + & > eventbox, + & > box { margin: 0 2px; &:first-child { @@ -43,22 +35,33 @@ &:last-child { margin-right: 0; } + + & > button, + & > eventbox > box { + padding: 4px 8px; + border-radius: 12px; + } + + & > button:hover, + & > eventbox:hover > box { + background: colors.$bg-primary; + } } } .workspaces { + @include mixins.reset-props; padding: 2px 2px; & button { - all: unset; border-radius: 16px; transition: 80ms linear; padding: 12px 12px; - background: wal.$color1; + background: colors.$bg-tertiary; margin: 1px 2px; &.focus { - background: wal.$foreground; + background: colors.$fg-primary; padding: 12px 20px; } } @@ -76,58 +79,47 @@ font-size: 9px; font-family: monospace; font-weight: 600; - color: color.adjust($color: wal.$foreground, $lightness: -11%); - margin-top: 1px; + color: colors.$fg-disabled; + margin-top: 0px; } & > .title { - font-size: 11.5px; + font-size: 12px; font-weight: 500; margin-top: -2px; } } } - .logo button { - padding: 0 11px; - padding-right: 16px; + .media-eventbox { + & > .media { + border-radius: 12px; + background: colors.$bg-primary; + padding: 0 8px; + } - & label { + & .nf { + margin-right: 4px; font-size: 14px; } - } - .media-eventbox { - & > .media > box { - border-radius: 12px; - background: wal.$color1; - padding: 0 7px; - - & .icon { - margin-right: 6px; - font-size: 14px; - } - } - - &.reveal { - & .media > box { - transition: 50ms linear; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - & .media-controls { + & .media-controls { + transition: none; padding-left: 6px; border-top-right-radius: 12px; border-bottom-right-radius: 12px; - transition: unset; - background: linear-gradient(to left, color.adjust($color: wal.$color1, $lightness: -15%) 45px, wal.$color1); + background: linear-gradient(to left, colors.$bg-primary 45px, colors.$bg-primary); & > button { - margin: 0px 1px; + margin: 4px 1px; border-radius: 4px; + label { + font-size: 8px; + } + &:hover { - background: wal.$color2; + background: colors.$bg-secondary; } &:first-child { @@ -143,6 +135,13 @@ } } } + + + &.reveal { + & .media > box { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } } } @@ -170,26 +169,32 @@ } .audio { - &:hover > box { - background: wal.$color1; - } + @include mixins.reset-props; - & .notification-bell { - padding-left: 10px; - padding-right: 4px; + &:hover > box { + background: colors.$bg-primary; } & > box { - padding: 0 9px; + padding: 0 8px; border-radius: 12px; - } - & .sink .icon { - margin-right: 6px; - } + & > * > * { + margin: 0 2px; + } - & .source .icon { - margin-right: 4px; + & .nf { + margin: { + right: 3px; + left: 2px; + }; + + font-size: 12px; + } + + & .bell { + margin: 0 4px; + } } } } diff --git a/ags/style/_center-window.scss b/ags/style/_center-window.scss new file mode 100644 index 0000000..fc3682a --- /dev/null +++ b/ags/style/_center-window.scss @@ -0,0 +1,40 @@ +@use "sass:color"; +@use "./wal"; +@use "./functions" as funs; // Did you know that you can use the 'as' keyword? I just found out! + +.center-window-container { + background: wal.$background; + border-radius: 18px; + padding: 12px; + + & .left { + .top { + .time { + font-size: 22px; + font-weight: 800; + } + + .date { + font-size: 14px; + font-weight: 500; + color: funs.toRGB(color.adjust($color: wal.$foreground, $lightness: -15%)); + } + } + } + + & .calendar-box { + padding: 5px; + & calendar { + border-radius: 6px; + padding: 2px; + + &.view { + background: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -35%)); + + & header { + background: funs.toRGB(color.adjust($color: wal.$background, $lightness: -20%)); + } + } + } + } +} diff --git a/ags/style/_colors.scss b/ags/style/_colors.scss new file mode 100644 index 0000000..b9ddd46 --- /dev/null +++ b/ags/style/_colors.scss @@ -0,0 +1,12 @@ +@use "sass:color"; +@use "./wal"; +@use "./functions" as funs; + +$bg-primary: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -35%)); +$bg-secondary: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -16%)); +$bg-tertiary: funs.toRGB(color.adjust($color: $bg-secondary, $lightness: 10%)); +$bg-light: wal.$foreground; +$bg-translucent: funs.toRGB(color.change($color: $bg-primary, $alpha: 72%)); +$fg-primary: wal.$foreground; +$fg-light: $bg-primary; +$fg-disabled: funs.toRGB(color.adjust($color: wal.$foreground, $lightness: -11%)); diff --git a/ags/style/_control-center.scss b/ags/style/_control-center.scss index 64df74d..c97524b 100644 --- a/ags/style/_control-center.scss +++ b/ags/style/_control-center.scss @@ -1,5 +1,6 @@ @use "sass:color"; @use "./wal"; +@use "./functions" as funs; .control-center-container { background: rgba(wal.$background, .65); @@ -29,7 +30,11 @@ & .button-row { & button { - padding: 4px 6px; + padding: 7px; + margin: { + top: 2px; + bottom: 2px; + }; } } } @@ -49,20 +54,29 @@ } } - icon { - background-size: 48px; + .icon.nf { + margin-right: 8px; + font-size: 15px; } trough { - background: color.adjust($color: wal.$color1, $lightness: -20%); - min-height: .8em; + background: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -20%)); border-radius: 8px; } trough highlight { background: wal.$color1; - min-height: inherit; - border-radius: inherit; + 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/_functions.scss b/ags/style/_functions.scss new file mode 100644 index 0000000..18bc08f --- /dev/null +++ b/ags/style/_functions.scss @@ -0,0 +1,14 @@ +@use "sass:color"; + + +/** + * GTK3 only supports sRGB color space, unfortunatly + */ +@function toRGB($color) { + @return rgba( + color.channel($color, "red"), + color.channel($color, "green"), + color.channel($color, "blue"), + color.alpha($color) + ); +} diff --git a/ags/style/_mixins.scss b/ags/style/_mixins.scss index adf150d..d07c513 100644 --- a/ags/style/_mixins.scss +++ b/ags/style/_mixins.scss @@ -1,8 +1,81 @@ @use "sass:color"; @use "./wal"; +@use "./colors"; +@use "./functions" as funs; @mixin reset-props { all: unset; transition: 120ms linear; - color: color.adjust($color: wal.$foreground, $lightness: -15%); + font-family: "Cantarell", "Noto Sans", + "Noto Sans CJK JP", "Noto Sans CJK KR", + "Noto Sans CJK HK", "Noto Sans CJK SC", + "Noto Sans CJK TC", sans-serif, + "Symbols Nerd Font Mono"; + color: colors.$fg-primary; +} + +@mixin default-styles { + .button-row { + & > button { + background: colors.$bg-secondary; + margin: 0 1px; + padding: 0 6px; + border-radius: 2px; + + &:hover { + background: colors.$bg-tertiary; + } + + &:first-child { + border-top-left-radius: 10px; + border-bottom-left-radius: 10px; + } + + &:last-child { + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; + } + } + } + + label.nf, + button.nf label { + font-size: 12px; + font-family: "Symbols Nerd Font Mono", "Noto Sans Nerd Font Mono", + "0xProto Nerd Font Mono", "Fira Code Nerd Font Mono", + "Symbols Nerd Font", "Noto Sans Nerd Font", "Fira Code Nerd Font", + "Font Awesome"; + } + + & menu { + padding: 4px; + background: wal.$background; + border-radius: 14px; + + & separator { + margin: 0 4px; + color: wal.$background; + } + + & menuitem { + padding: 8px 16px; + border-radius: 10px; + font-size: 12px; + font-weight: 600; + + &:hover { + background: wal.$color1; + } + } + } + + & tooltip { + padding: 8px; + border-radius: 14px; + + & label { + font-size: 14px; + color: colors.$fg-primary; + } + } } diff --git a/ags/style/_osd.scss b/ags/style/_osd.scss index 5ded07b..364fcd6 100644 --- a/ags/style/_osd.scss +++ b/ags/style/_osd.scss @@ -1,8 +1,9 @@ @use "sass:color"; @use "./wal"; +@use "./functions" as funs; .osd { - background: color.change($color: wal.$background, $alpha: 65%); + background: funs.toRGB(color.change($color: wal.$background, $alpha: 65%)); padding: 14px 16px; border-radius: 20px; @@ -23,7 +24,7 @@ levelbar { trough block { border-radius: 2px; - background: color.adjust($color: wal.$color1, $lightness: -36%); + background: funs.toRGB(color.adjust($color: wal.$color1, $lightness: -36%)); &.empty { border-radius: 2px; diff --git a/ags/widget/bar/Audio.ts b/ags/widget/bar/Audio.ts index 1c55283..98c1cce 100644 --- a/ags/widget/bar/Audio.ts +++ b/ags/widget/bar/Audio.ts @@ -21,11 +21,11 @@ export function Audio() { child: new Widget.Box({ children: [ new Widget.Label({ - className: "icon nf", + className: "nf", label: "󰕾" } as Widget.LabelProps), new Widget.Label({ - className: "icon nf", + className: "volume", label: bind(Wireplumber.getDefault().getDefaultSink(), "volume").as((volume: number) => Math.floor(volume * 100) + "%") } as Widget.LabelProps) @@ -42,22 +42,21 @@ export function Audio() { child: new Widget.Box({ children: [ new Widget.Label({ - className: "icon", + className: "nf", label: "󰍬" } as Widget.LabelProps), new Widget.Label({ + className: "volume", label: bind(Wireplumber.getDefault().getDefaultSource(), "volume").as((volume: number) => Math.floor(volume * 100) + "%") } as Widget.LabelProps) ] }) } as Widget.EventBoxProps), - new Widget.Box({ - className: "notification-bell", - child: new Widget.Label({ - label: "󰂚" - } as Widget.LabelProps) - } as Widget.BoxProps) + new Widget.Label({ + className: "bell nf", + label: "󰂚" + } as Widget.LabelProps) ] } as Widget.BoxProps) } as Widget.EventBoxProps); diff --git a/ags/widget/bar/FocusedClient.ts b/ags/widget/bar/FocusedClient.ts index 8296d1c..56a02a6 100644 --- a/ags/widget/bar/FocusedClient.ts +++ b/ags/widget/bar/FocusedClient.ts @@ -17,13 +17,13 @@ export function FocusedClient() { (getAppIcon(client.initialClass) || client.initialClass) : "image-missing" - ), - iconSize: Gtk.IconSize.SMALL_TOOLBAR + ) }), new Widget.Box({ className: "text-content", orientation: Gtk.Orientation.VERTICAL, homogeneous: false, + valign: Gtk.Align.CENTER, children: [ new Widget.Label({ className: "class", diff --git a/ags/widget/bar/Logo.ts b/ags/widget/bar/Logo.ts index 94ef404..2a11914 100644 --- a/ags/widget/bar/Logo.ts +++ b/ags/widget/bar/Logo.ts @@ -7,7 +7,8 @@ export function Logo() { //tooltipText: tr("bar.logo.tooltip"), child: new Widget.Button({ onClick: () => AstalHyprland.get_default().dispatch("exec", "anyrun"), - label: "" + className: "nf", + label: "", } as Widget.ButtonProps) } as Widget.BoxProps); } diff --git a/ags/widget/bar/Media.ts b/ags/widget/bar/Media.ts index 970be73..e05e744 100644 --- a/ags/widget/bar/Media.ts +++ b/ags/widget/bar/Media.ts @@ -1,4 +1,4 @@ -import { bind, Process } from "astal"; +import { bind, GLib, Process } from "astal"; import { Gtk, Widget } from "astal/gtk3"; import AstalMpris from "gi://AstalMpris"; import { Separator, SeparatorProps } from "../Separator"; @@ -19,25 +19,29 @@ export function Media(): Gtk.Widget { transitionDuration: 260, revealChild: false, child: new Widget.Box({ - className: "media-controls", + className: "media-controls button-row", expand: false, homogeneous: false, children: bind(mpris, "players").as((players: Array) => players[0] ? [ new Widget.Button({ - className: "link", - label: "󰌷", - visible: bind(players[0], "metadata").as(metadata => - metadata?.["xesam:url"] ? true : false), - onClick: () => Process.exec(`echo ${players[0].metadata.url}"`) + 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", + className: "previous nf", label: "󰒮", + tooltipText: "Previous", onClick: () => players[0].canGoPrevious && players[0].previous() } as Widget.ButtonProps), new Widget.Button({ - className: "pause", + 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: () => { @@ -48,7 +52,7 @@ export function Media(): Gtk.Widget { } } as Widget.ButtonProps), new Widget.Button({ - className: "next", + className: "next nf", label: "󰒭", onClick: () => players[0].canGoNext && players[0].next() } as Widget.ButtonProps) @@ -69,7 +73,7 @@ export function Media(): Gtk.Widget { children: bind(mpris, "players").as((players: Array) => players[0] ? [ new Widget.Label({ - className: "icon", + className: "player-icon nf", label: bind(players[0], "busName").as((busName: string) => { const playerName: string = busName.split('.')[busName.split('.').length-1]; return playerIcons[playerName as keyof typeof playerIcons] || "󰎇"; diff --git a/ags/widget/bar/Tray.ts b/ags/widget/bar/Tray.ts index a372ec3..f19432d 100644 --- a/ags/widget/bar/Tray.ts +++ b/ags/widget/bar/Tray.ts @@ -1,4 +1,4 @@ -import { bind } from "astal"; +import { bind, Gio } from "astal"; import { Gtk, Widget } from "astal/gtk3"; import AstalTray from "gi://AstalTray" @@ -19,7 +19,7 @@ export function Tray() { direction: Gtk.ArrowType.DOWN, halign: Gtk.Align.CENTER, child: new Widget.Icon({ - gIcon: bind(item, "gicon"), + gicon: bind(item, "gicon"), iconSize: Gtk.IconSize.SMALL_TOOLBAR }) } as Widget.MenuButtonProps) diff --git a/ags/widget/center-window/BigMedia.ts b/ags/widget/center-window/BigMedia.ts new file mode 100644 index 0000000..7b323bc --- /dev/null +++ b/ags/widget/center-window/BigMedia.ts @@ -0,0 +1,6 @@ +import { Gtk, Widget } from "astal/gtk3"; + +export const BigMedia: Gtk.Widget = new Widget.Box({ + className: "big-media", + //TODO +} as Widget.BoxProps); diff --git a/ags/widget/center-window/NotificationHistory.ts b/ags/widget/center-window/NotificationHistory.ts new file mode 100644 index 0000000..4519489 --- /dev/null +++ b/ags/widget/center-window/NotificationHistory.ts @@ -0,0 +1,73 @@ +import { bind } from "astal"; +import { Gtk, Widget } from "astal/gtk3"; +import AstalNotifd from "gi://AstalNotifd"; +import { Notifications } from "../../scripts/notification-handler"; + +export const NotificationHistory: Gtk.Widget = new Widget.Scrollable({ + hscroll: Gtk.PolicyType.NEVER, + vscroll: Gtk.PolicyType.AUTOMATIC, + child: new Widget.Box({ + className: "notifications", + children: bind(Notifications, "notificationHistory").as((history: Array) => + history && history.length > 0 && history.map((notification: AstalNotifd.Notification) => + new Widget.Box({ + className: "notification", + hexpand: true, + orientation: Gtk.Orientation.VERTICAL, + children: [ + new Widget.Box({ + className: "top", + expand: true, + children: [ + new Widget.Box({ + className: "app", + children: [ + new Widget.Icon({ + icon: notification.appIcon || notification.appName.toLowerCase(), + iconSize: Gtk.IconSize.LARGE_TOOLBAR + }), + new Widget.Label({ + className: "name", + label: notification.appName || "Unknown" + } as Widget.LabelProps) + ] + } as Widget.BoxProps), + new Widget.Button({ + className: "remove", + label: "󱎘", + onClick: () => Notifications.removeFromNotificationHistory(notification.id) + } as Widget.ButtonProps) + ] + } as Widget.BoxProps), + new Widget.Box({ + className: "content", + expand: true, + children: [ + new Widget.Box({ + className: "image", + visible: notification.image !== "", + css: `.image { background-image: url('${notification.image}') }` + } as Widget.BoxProps), + new Widget.Box({ + orientation: Gtk.Orientation.VERTICAL, + children: [ + new Widget.Label({ + className: "summary", + useMarkup: true, + label: notification.summary + } as Widget.LabelProps), + new Widget.Label({ + className: "body", + useMarkup: true, + label: notification.body + } as Widget.LabelProps) + ] + } as Widget.BoxProps) + ] + } as Widget.BoxProps) + ] + } as Widget.BoxProps) + ) + ) + } as Widget.BoxProps) +} as Widget.ScrollableProps) diff --git a/ags/widget/control-center/QuickActions.ts b/ags/widget/control-center/QuickActions.ts index 5766f03..b4f56cf 100644 --- a/ags/widget/control-center/QuickActions.ts +++ b/ags/widget/control-center/QuickActions.ts @@ -10,6 +10,7 @@ const uptime = new Variable("Just turned on") function LockButton(): Widget.Button { return new Widget.Button({ + className: "nf", label: "󰌾", onClick: () => AstalHyprland.get_default().dispatch("exec", "hyprlock") } as Widget.ButtonProps) @@ -17,6 +18,7 @@ function LockButton(): Widget.Button { function ColorPickerButton(): Widget.Button { return new Widget.Button({ + className: "nf", label: "󰴱", onClick: () => AstalHyprland.get_default().dispatch( "exec", @@ -27,6 +29,7 @@ function ColorPickerButton(): Widget.Button { function ScreenshotButton(): Widget.Button { return new Widget.Button({ + className: "nf", label: "󰹑", onClick: () => Process.exec_async( "bash -c 'hyprshot -m region -o $HOME/Screenshots'", @@ -37,6 +40,7 @@ function ScreenshotButton(): Widget.Button { function SelectWallpaperButton(): Widget.Button { return new Widget.Button({ + className: "nf", label: "󰸉", onClick: () => Process.exec_async( "bash -c 'sh $HOME/.config/hypr/scripts/change-wallpaper.sh'", @@ -47,6 +51,7 @@ function SelectWallpaperButton(): Widget.Button { function LogoutButton(): Widget.Button { return new Widget.Button({ + className: "nf", label: "󰗽", onClick: () => Process.exec_async( "bash -c 'wlogout -b 5'", diff --git a/ags/widget/control-center/Sliders.ts b/ags/widget/control-center/Sliders.ts index 62374cb..7dbc19c 100644 --- a/ags/widget/control-center/Sliders.ts +++ b/ags/widget/control-center/Sliders.ts @@ -10,9 +10,10 @@ export const Sliders: Gtk.Widget = new Widget.Box({ new Widget.Box({ className: "sink speaker", children: [ - new Widget.Icon({ - icon: "audio-volume-high-symbolic" - } as Widget.IconProps), + new Widget.Label({ + className: "nf icon", + label: "󰕾" + } as Widget.LabelProps), new Widget.Slider({ drawValue: false, hexpand: true, @@ -26,9 +27,10 @@ export const Sliders: Gtk.Widget = new Widget.Box({ new Widget.Box({ className: "source microphone", children: [ - new Widget.Icon({ - icon: "microphone-sensitivity-high-symbolic" - } as Widget.IconProps), + new Widget.Label({ + className: "nf icon", + label: "󰍬" + } as Widget.LabelProps), new Widget.Slider({ drawValue: false, hexpand: true, @@ -42,6 +44,10 @@ export const Sliders: Gtk.Widget = new Widget.Box({ /*new Widget.Box({ className: "brightness screen", children: [ + new Widget.Label({ + className: "icon nf", + label: "󰃠" + } as Widget.LabelProps), new Widget.Slider({ drawValue: false, hexpand: true, diff --git a/ags/widget/control-center/Tiles.ts b/ags/widget/control-center/Tiles.ts index 31778c8..63c3582 100644 --- a/ags/widget/control-center/Tiles.ts +++ b/ags/widget/control-center/Tiles.ts @@ -5,6 +5,7 @@ export const tileList: Array = [ export const Tiles: Widget.Box = new Widget.Box({ child: new Gtk.Grid({ + visible: true, orientation: Gtk.Orientation.HORIZONTAL, rowHomogeneous: true } as Gtk.Grid.ConstructorProps) diff --git a/ags/window/CenterWindow.ts b/ags/window/CenterWindow.ts index 36e8d2e..934ffa9 100644 --- a/ags/window/CenterWindow.ts +++ b/ags/window/CenterWindow.ts @@ -3,7 +3,6 @@ import { GLib } from "astal"; import { getDateTime } from "../scripts/time"; - export const CenterWindow: Widget.Window = new Widget.Window({ className: "center-window", namespace: "center-window", @@ -12,7 +11,8 @@ export const CenterWindow: Widget.Window = new Widget.Window({ layer: Astal.Layer.OVERLAY, exclusivity: Astal.Exclusivity.NORMAL, visible: false, - height_request: 600, + height_request: 400, + margin_top: 10, anchor: Astal.WindowAnchor.TOP, child: new Widget.Box({ className: "center-window-container", @@ -23,7 +23,7 @@ export const CenterWindow: Widget.Window = new Widget.Window({ width_request: 300, children: [ new Widget.Box({ - className: "top time date", + className: "top", orientation: Gtk.Orientation.VERTICAL, children: [ new Widget.Label({ @@ -37,7 +37,7 @@ export const CenterWindow: Widget.Window = new Widget.Window({ dateTime.format("%A, %B %d %Y")) } as Widget.LabelProps) ] - } as Widget.BoxProps) + } as Widget.BoxProps), ] } as Widget.BoxProps), new Widget.Box({ @@ -46,6 +46,7 @@ export const CenterWindow: Widget.Window = new Widget.Window({ new Widget.Box({ className: "calendar-box", child: new Gtk.Calendar({ + visible: true, show_heading: true, show_day_names: true, show_week_numbers: false diff --git a/ags/window/ControlCenter.ts b/ags/window/ControlCenter.ts index c13721f..04d6e6e 100644 --- a/ags/window/ControlCenter.ts +++ b/ags/window/ControlCenter.ts @@ -20,7 +20,7 @@ export const ControlCenter: Widget.Window = new Widget.Window({ layer: Astal.Layer.OVERLAY, margin_top: 10, margin_right: 10, - width_request: 450, + width_request: 400, monitor: 0, visible: false } as Widget.WindowProps, widgetsContainer); diff --git a/ags/window/FloatingNotifications.ts b/ags/window/FloatingNotifications.ts index 0250b6f..ba2446d 100644 --- a/ags/window/FloatingNotifications.ts +++ b/ags/window/FloatingNotifications.ts @@ -1,26 +1,29 @@ import { Astal, Gtk, Widget } from "astal/gtk3"; -import { getNotifd, notifications, removeNotification } from "../scripts/notification-handler"; import AstalNotifd from "gi://AstalNotifd"; import { bind } from "astal"; +import { Notifications } from "../scripts/notification-handler"; export const FloatingNotifications: Widget.Window = new Widget.Window({ - className: "floating-notifications", namespace: "floating-notifications", canFocus: false, anchor: Astal.WindowAnchor.RIGHT, monitor: 0, layer: Astal.Layer.OVERLAY, visible: false, + width_request: 350, exclusivity: Astal.Exclusivity.NORMAL, child: new Widget.Box({ - className: "notifications", + className: "floating-notifications-container", orientation: Gtk.Orientation.VERTICAL, homogeneous: false, - children: bind(getNotifd(), "notifications").as(() => { - notifications.length > 0 ? notifications.map((notification: AstalNotifd.Notification) => + 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", @@ -35,13 +38,40 @@ export const FloatingNotifications: Widget.Window = new Widget.Window({ } as Widget.LabelProps), new Widget.Button({ className: "close-button", - onClick: () => removeNotification(notification.id) + 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) - ) : new Widget.Box({}) + ) }) } as Widget.BoxProps) } as Widget.WindowProps); diff --git a/ags/window/LogoutMenu.ts b/ags/window/LogoutMenu.ts new file mode 100644 index 0000000..6e1285e --- /dev/null +++ b/ags/window/LogoutMenu.ts @@ -0,0 +1,72 @@ +import { Astal, Gdk, Gtk, Widget } from "astal/gtk3"; +import { getDateTime } from "../scripts/time"; +import { execAsync, GLib, Process } from "astal"; + + +const { TOP, LEFT, RIGHT, BOTTOM } = Astal.WindowAnchor; + +export const LogoutMenu: Widget.Window = new Widget.Window({ + namespace: "logout-menu", + anchor: TOP | LEFT | RIGHT | BOTTOM, + layer: Astal.Layer.OVERLAY, + 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", () => {}), + child: new Widget.Box({ + homogeneous: false, + orientation: Gtk.Orientation.VERTICAL, + children: [ + new Widget.Box({ + className: "top", + expand: true, + orientation: Gtk.Orientation.VERTICAL, + children: [ + new Widget.Label({ + className: "time", + label: getDateTime().as((dateTime: GLib.DateTime) => + dateTime.format("%H:%M")) + } as Widget.LabelProps), + new Widget.Label({ + className: "date", + label: getDateTime().as((dateTime: GLib.DateTime) => + dateTime.format("%A, %B %d %Y")) + } as Widget.LabelProps) + ] + } as Widget.BoxProps), + new Widget.Box({ + className: "button-row", + homogeneous: true, + expand: true, + valign: Gtk.Align.CENTER, + children: [ + new Widget.Button({ + className: "poweroff nf", + label: "󰐥", + onClick: "ask user if it's fr!" + } as Widget.ButtonProps), + new Widget.Button({ + className: "reboot nf", + label: "󰜉", + onClick: "ask user if it's fr!" + } as Widget.ButtonProps), + new Widget.Button({ + className: "suspend nf", + label: "󰤄", + onClick: "ask user if it's fr!" + } as Widget.ButtonProps), + new Widget.Button({ + className: "logout nf", + label: "󰗽", + onClick: () => execAsync("astal close logout-menu && bash -c 'loginctl terminate-user $USER'") + } as Widget.ButtonProps), + ] + } as Widget.BoxProps) + ] + }) + } as Widget.EventBoxProps) +} as Widget.WindowProps); diff --git a/ags/windows.ts b/ags/windows.ts index 7bb385f..e0d7ec7 100644 --- a/ags/windows.ts +++ b/ags/windows.ts @@ -6,6 +6,7 @@ import { ControlCenter } from "./window/ControlCenter"; import { CenterWindow } from "./window/CenterWindow"; import { FloatingNotifications } from "./window/FloatingNotifications"; import { GObject } from "astal"; +import { LogoutMenu } from "./window/LogoutMenu"; /** * get open windows / interact with windows(e.g.: close, open or toggle) @@ -20,7 +21,7 @@ export const Windows = GObject.registerClass({ WindowsClass.windowsMap.set("osd", OSD); WindowsClass.windowsMap.set("control-center", ControlCenter); WindowsClass.windowsMap.set("center-window", CenterWindow); - WindowsClass.windowsMap.set("floating-notifications", FloatingNotifications); + WindowsClass.windowsMap.set("logout-menu", LogoutMenu); } public _init(...args: any[]) {