From 5481ec544b9b244e5278f1c71806fbb38d0b799e Mon Sep 17 00:00:00 2001 From: matt1432 Date: Tue, 15 Oct 2024 20:01:20 -0400 Subject: [PATCH] feat(agsV2): fix breaking changes and add notification impl --- lib/pkgs.nix | 8 +- nixosModules/ags/v2/app.ts | 4 +- nixosModules/ags/v2/default.nix | 24 +- nixosModules/ags/v2/lib.ts | 2 +- nixosModules/ags/v2/style.scss | 1 + .../ags/v2/widgets/bar/fullscreen.tsx | 3 +- .../ags/v2/widgets/bar/items/tray.tsx | 3 +- .../ags/v2/widgets/bar/items/workspaces.tsx | 3 +- nixosModules/ags/v2/widgets/bar/wim.tsx | 2 +- nixosModules/ags/v2/widgets/bg-fade/main.tsx | 2 +- nixosModules/ags/v2/widgets/corners/main.tsx | 2 +- .../ags/v2/widgets/corners/screen-corners.tsx | 2 +- .../ags/v2/widgets/misc/popup-window.tsx | 3 +- .../ags/v2/widgets/misc/separator.tsx | 2 +- nixosModules/ags/v2/widgets/notifs/center.tsx | 0 .../ags/v2/widgets/notifs/gesture.tsx | 249 ++++++++++++++++++ nixosModules/ags/v2/widgets/notifs/main.tsx | 14 + .../ags/v2/widgets/notifs/notification.tsx | 160 +++++++++++ nixosModules/ags/v2/widgets/notifs/popups.tsx | 48 ++++ nixosModules/ags/v2/widgets/notifs/style.scss | 32 +++ .../desktop/environment/config/kdeglobals | 4 +- 21 files changed, 540 insertions(+), 28 deletions(-) create mode 100644 nixosModules/ags/v2/widgets/notifs/center.tsx create mode 100644 nixosModules/ags/v2/widgets/notifs/gesture.tsx create mode 100644 nixosModules/ags/v2/widgets/notifs/main.tsx create mode 100644 nixosModules/ags/v2/widgets/notifs/notification.tsx create mode 100644 nixosModules/ags/v2/widgets/notifs/popups.tsx create mode 100644 nixosModules/ags/v2/widgets/notifs/style.scss diff --git a/lib/pkgs.nix b/lib/pkgs.nix index c347bcf2..8b16451b 100644 --- a/lib/pkgs.nix +++ b/lib/pkgs.nix @@ -32,7 +32,7 @@ packages, pname, }: let - inherit (pkgs.lib) concatMapStrings elemAt filter hasAttr length map optionalString splitString toLower; + inherit (pkgs.lib) concatMapStrings elemAt length map optionalString splitString toLower; withGirNames = map (package: { @@ -40,9 +40,13 @@ girName = if package.pname == "astal-wireplumber" then "AstalWp-0.1" + else if package.name == "astal-0.1.0" + then "AstalIO-0.1" + else if package.name == "astal-3.0.0" + then "Astal-3.0" else (concatMapStrings capitalise (splitString "-" package.pname)) + "-0.1"; }) - (filter (hasAttr "pname") packages); + packages; in { "${configPath}${optionalString (length packages == 1) "/${toLower (elemAt withGirNames 0).girName}"}".source = pkgs.callPackage diff --git a/nixosModules/ags/v2/app.ts b/nixosModules/ags/v2/app.ts index 2765fae1..f35a47d9 100644 --- a/nixosModules/ags/v2/app.ts +++ b/nixosModules/ags/v2/app.ts @@ -1,10 +1,11 @@ -import { App } from 'astal'; +import { App } from 'astal/gtk3'; import style from './style.scss'; import Bar from './widgets/bar/wim'; import BgFade from './widgets/bg-fade/main'; import Corners from './widgets/corners/main'; +import { NotifPopups } from './widgets/notifs/main'; App.start({ @@ -14,5 +15,6 @@ App.start({ Bar(); BgFade(); Corners(); + NotifPopups(); }, }); diff --git a/nixosModules/ags/v2/default.nix b/nixosModules/ags/v2/default.nix index 5e2b7671..b5902532 100644 --- a/nixosModules/ags/v2/default.nix +++ b/nixosModules/ags/v2/default.nix @@ -9,7 +9,7 @@ self: { inherit (self.inputs) agsV2; agsV2Packages = agsV2.packages.${pkgs.system}; - astalLibs = attrValues (removeAttrs agsV2.inputs.astal.packages.${pkgs.system} ["docs"]); + astalLibs = attrValues (removeAttrs agsV2.inputs.astal.packages.${pkgs.system} ["docs" "gjs"]); configDir = "/home/matt/.nix/nixosModules/ags/v2"; in { home = { @@ -42,21 +42,19 @@ self: { "${configDir}/tsconfig.json".source = pkgs.writers.writeJSON "tsconfig.json" { "$schema" = "https://json.schemastore.org/tsconfig"; "compilerOptions" = { + "experimentalDecorators" = true; + "strict" = true; "target" = "ES2023"; + "moduleResolution" = "Bundler"; + "jsx" = "react-jsx"; + "jsxImportSource" = "${agsV2Packages.gjs}/share/astal/gjs/gtk3"; + "paths" = { + "astal" = ["${agsV2Packages.gjs}/share/astal/gjs"]; + "astal/*" = ["${agsV2Packages.gjs}/share/astal/gjs/*"]; + }; + "skipLibCheck" = true; "module" = "ES2022"; "lib" = ["ES2023"]; - "strict" = true; - "moduleResolution" = "Bundler"; - "skipLibCheck" = true; - "checkJs" = true; - "allowJs" = true; - "experimentalDecorators" = true; - "jsx" = "react-jsx"; - "jsxImportSource" = "${agsV2Packages.astal3}/share/astal/gjs/src/jsx"; - "paths" = { - "astal" = ["${agsV2Packages.astal3}/share/astal/gjs"]; - "astal/*" = ["${agsV2Packages.astal3}/share/astal/gjs/src/*"]; - }; }; }; } diff --git a/nixosModules/ags/v2/lib.ts b/nixosModules/ags/v2/lib.ts index 120765b2..b7e4cd94 100644 --- a/nixosModules/ags/v2/lib.ts +++ b/nixosModules/ags/v2/lib.ts @@ -1,4 +1,4 @@ -import { Gdk } from 'astal'; +import { Gdk } from 'astal/gtk3'; import AstalHyprland from 'gi://AstalHyprland?version=0.1'; const Hyprland = AstalHyprland.get_default(); diff --git a/nixosModules/ags/v2/style.scss b/nixosModules/ags/v2/style.scss index 0c65f809..429f802f 100644 --- a/nixosModules/ags/v2/style.scss +++ b/nixosModules/ags/v2/style.scss @@ -45,3 +45,4 @@ window { } @import 'widgets/bar/style.scss'; +@import 'widgets/notifs/style.scss'; diff --git a/nixosModules/ags/v2/widgets/bar/fullscreen.tsx b/nixosModules/ags/v2/widgets/bar/fullscreen.tsx index 17e9dc10..41a3b5b4 100644 --- a/nixosModules/ags/v2/widgets/bar/fullscreen.tsx +++ b/nixosModules/ags/v2/widgets/bar/fullscreen.tsx @@ -1,4 +1,5 @@ -import { Astal, bind, Gdk, Gtk, Variable, Widget } from 'astal'; +import { Astal, Gdk, Gtk, Widget } from 'astal/gtk3'; +import { bind, Variable } from 'astal'; import AstalHyprland from 'gi://AstalHyprland?version=0.1'; const Hyprland = AstalHyprland.get_default(); diff --git a/nixosModules/ags/v2/widgets/bar/items/tray.tsx b/nixosModules/ags/v2/widgets/bar/items/tray.tsx index 67f98c30..cb641532 100644 --- a/nixosModules/ags/v2/widgets/bar/items/tray.tsx +++ b/nixosModules/ags/v2/widgets/bar/items/tray.tsx @@ -1,4 +1,5 @@ -import { App, bind, Gdk, Gtk, idle, Widget } from 'astal'; +import { App, Gdk, Gtk, Widget } from 'astal/gtk3'; +import { bind, idle } from 'astal'; import AstalTray from 'gi://AstalTray'; const Tray = AstalTray.get_default(); diff --git a/nixosModules/ags/v2/widgets/bar/items/workspaces.tsx b/nixosModules/ags/v2/widgets/bar/items/workspaces.tsx index 8dbafef5..23ba8cfd 100644 --- a/nixosModules/ags/v2/widgets/bar/items/workspaces.tsx +++ b/nixosModules/ags/v2/widgets/bar/items/workspaces.tsx @@ -1,4 +1,5 @@ -import { Gtk, timeout, Widget } from 'astal'; +import { Gtk, Widget } from 'astal/gtk3'; +import { timeout } from 'astal'; import AstalHyprland from 'gi://AstalHyprland?version=0.1'; const Hyprland = AstalHyprland.get_default(); diff --git a/nixosModules/ags/v2/widgets/bar/wim.tsx b/nixosModules/ags/v2/widgets/bar/wim.tsx index 84092642..8f4af7a7 100644 --- a/nixosModules/ags/v2/widgets/bar/wim.tsx +++ b/nixosModules/ags/v2/widgets/bar/wim.tsx @@ -1,4 +1,4 @@ -import { Astal, Gtk } from 'astal'; +import { Astal, Gtk } from 'astal/gtk3'; import Battery from './items/battery'; import Clock from './items/clock'; diff --git a/nixosModules/ags/v2/widgets/bg-fade/main.tsx b/nixosModules/ags/v2/widgets/bg-fade/main.tsx index c30dc90f..2f53a00f 100644 --- a/nixosModules/ags/v2/widgets/bg-fade/main.tsx +++ b/nixosModules/ags/v2/widgets/bg-fade/main.tsx @@ -1,4 +1,4 @@ -import { Astal } from 'astal'; +import { Astal } from 'astal/gtk3'; export default () => { diff --git a/nixosModules/ags/v2/widgets/corners/main.tsx b/nixosModules/ags/v2/widgets/corners/main.tsx index 6ebdb19b..a8c34d28 100644 --- a/nixosModules/ags/v2/widgets/corners/main.tsx +++ b/nixosModules/ags/v2/widgets/corners/main.tsx @@ -1,4 +1,4 @@ -import { Astal } from 'astal'; +import { Astal } from 'astal/gtk3'; import RoundedCorner from './screen-corners'; diff --git a/nixosModules/ags/v2/widgets/corners/screen-corners.tsx b/nixosModules/ags/v2/widgets/corners/screen-corners.tsx index 0a786c41..69dc74a8 100644 --- a/nixosModules/ags/v2/widgets/corners/screen-corners.tsx +++ b/nixosModules/ags/v2/widgets/corners/screen-corners.tsx @@ -1,4 +1,4 @@ -import { Gtk } from 'astal'; +import { Gtk } from 'astal/gtk3'; import Cairo from 'cairo'; diff --git a/nixosModules/ags/v2/widgets/misc/popup-window.tsx b/nixosModules/ags/v2/widgets/misc/popup-window.tsx index 9814d7d2..66a922a9 100644 --- a/nixosModules/ags/v2/widgets/misc/popup-window.tsx +++ b/nixosModules/ags/v2/widgets/misc/popup-window.tsx @@ -1,5 +1,6 @@ -import { Astal, Binding, idle, Widget } from 'astal'; +import { Astal, Widget } from 'astal/gtk3'; import { register, property } from 'astal/gobject'; +import { Binding, idle } from 'astal'; import AstalHyprland from 'gi://AstalHyprland?version=0.1'; const Hyprland = AstalHyprland.get_default(); diff --git a/nixosModules/ags/v2/widgets/misc/separator.tsx b/nixosModules/ags/v2/widgets/misc/separator.tsx index b22a95ab..aaf0aeca 100644 --- a/nixosModules/ags/v2/widgets/misc/separator.tsx +++ b/nixosModules/ags/v2/widgets/misc/separator.tsx @@ -1,4 +1,4 @@ -import { Widget } from 'astal'; +import { Widget } from 'astal/gtk3'; export default ({ diff --git a/nixosModules/ags/v2/widgets/notifs/center.tsx b/nixosModules/ags/v2/widgets/notifs/center.tsx new file mode 100644 index 00000000..e69de29b diff --git a/nixosModules/ags/v2/widgets/notifs/gesture.tsx b/nixosModules/ags/v2/widgets/notifs/gesture.tsx new file mode 100644 index 00000000..6ae0bb9a --- /dev/null +++ b/nixosModules/ags/v2/widgets/notifs/gesture.tsx @@ -0,0 +1,249 @@ +import { Gdk, Gtk, Widget } from 'astal/gtk3'; +import { register, property } from 'astal/gobject'; +import { idle } from 'astal'; + +import AstalNotifd from 'gi://AstalNotifd?version=0.1'; +const Notifications = AstalNotifd.get_default(); + +import { HasNotifs } from './notification'; + + +const display = Gdk.Display.get_default(); + +const MAX_OFFSET = 200; +const OFFSCREEN = 300; +const ANIM_DURATION = 500; +const SLIDE_MIN_THRESHOLD = 10; +const TRANSITION = 'transition: margin 0.5s ease, opacity 0.5s ease;'; +const MAX_LEFT = ` + margin-left: -${Number(MAX_OFFSET + OFFSCREEN)}px; + margin-right: ${Number(MAX_OFFSET + OFFSCREEN)}px; +`; +const MAX_RIGHT = ` + margin-left: ${Number(MAX_OFFSET + OFFSCREEN)}px; + margin-right: -${Number(MAX_OFFSET + OFFSCREEN)}px; +`; + +const slideLeft = `${TRANSITION} ${MAX_LEFT} opacity: 0;`; + +const slideRight = `${TRANSITION} ${MAX_RIGHT} opacity: 0;`; + +const defaultStyle = `${TRANSITION} margin: unset; opacity: 1;`; + +type NotifGestureWrapperProps = Widget.BoxProps & { + id: number + slide_in_from?: 'Left' | 'Right' +}; + +@register() +class NotifGestureWrapper extends Widget.EventBox { + readonly id: number; + + readonly slide_in_from: 'Left' | 'Right'; + + readonly slideAway: (side: 'Left' | 'Right') => void; + + @property(Boolean) + declare hovered: boolean; + + @property(Boolean) + declare dragging: boolean; + + constructor({ + id, + slide_in_from = 'Left', + ...rest + }: NotifGestureWrapperProps) { + super(); + this.id = id; + this.dragging = false; + this.hovered = false; + this.slide_in_from = slide_in_from; + + this.slideAway = (side: 'Left' | 'Right', force = false) => { + ((this.get_child() as Widget.Revealer).get_child() as Widget.Box) + .css = side === 'Left' ? slideLeft : slideRight; + + // Make it uninteractable + this.sensitive = false; + + setTimeout(() => { + (this.get_child() as Widget.Revealer).revealChild = false; + + setTimeout(() => { + // Kill notif and update HasNotifs after anim is done + if (force) { + Notifications.get_notification(this.id)?.dismiss(); + } + HasNotifs.set(Notifications.get_notifications().length > 0); + this.destroy(); + }, ANIM_DURATION); + }, ANIM_DURATION - 100); + }; + + this.connect('notify::dragging', () => { + if (!this.hovered && this.dragging) { + this.hovered = true; + } + }); + + // OnClick + this.connect('button-press-event', () => { + if (!display) { + return; + } + this.window.set_cursor(Gdk.Cursor.new_from_name( + display, + 'grabbing', + )); + if (!this.hovered) { + this.hovered = true; + } + }); + + // OnRelease + this.connect('button-release-event', () => { + if (!display) { + return; + } + this.window.set_cursor(Gdk.Cursor.new_from_name( + display, + 'grab', + )); + if (!this.hovered) { + this.hovered = true; + } + }); + + // OnHover + this.connect('enter-notify-event', () => { + if (!display) { + return; + } + this.window.set_cursor(Gdk.Cursor.new_from_name( + display, + 'grab', + )); + if (!this.hovered) { + this.hovered = true; + } + }); + + // OnHoverLost + this.connect('leave-notify-event', () => { + if (!display) { + return; + } + this.window.set_cursor(Gdk.Cursor.new_from_name( + display, + 'grab', + )); + + if (this.hovered) { + this.hovered = false; + } + }); + + const gesture = Gtk.GestureDrag.new(this); + + this.add( + + { + self + // When dragging + .hook(gesture, 'drag-update', () => { + let offset = gesture.get_offset()[1]; + + if (!offset || offset === 0) { + return; + } + + // Slide right + if (offset > 0) { + self.css = ` + opacity: 1; transition: none; + margin-left: ${offset}px; + margin-right: -${offset}px; + `; + } + + // Slide left + else { + offset = Math.abs(offset); + self.css = ` + opacity: 1; transition: none; + margin-right: ${offset}px; + margin-left: -${offset}px; + `; + } + + // Put a threshold on if a click is actually dragging + this.dragging = Math.abs(offset) > SLIDE_MIN_THRESHOLD; + + if (!display) { + return; + } + this.window.set_cursor(Gdk.Cursor.new_from_name( + display, + 'grabbing', + )); + }) + + // On drag end + .hook(gesture, 'drag-end', () => { + const offset = gesture.get_offset()[1]; + + if (!offset) { + return; + } + + // If crosses threshold after letting go, slide away + if (Math.abs(offset) > MAX_OFFSET) { + if (offset > 0) { + this.slideAway('Right'); + } + else { + this.slideAway('Left'); + } + } + else { + self.css = defaultStyle; + this.dragging = false; + + if (!display) { + return; + } + this.window.set_cursor(Gdk.Cursor.new_from_name( + display, + 'grab', + )); + } + }); + + // Reverse of slideAway, so it started at squeeze, then we go to slide + self.css = this.slide_in_from === 'Left' ? + slideLeft : + slideRight; + + idle(() => { + (self.get_parent() as Widget.Revealer).revealChild = true; + + setTimeout(() => { + // Then we go to center + self.css = defaultStyle; + }, ANIM_DURATION); + }); + }} + /> + , + ); + } +} + +export default (props: NotifGestureWrapperProps) => new NotifGestureWrapper(props); diff --git a/nixosModules/ags/v2/widgets/notifs/main.tsx b/nixosModules/ags/v2/widgets/notifs/main.tsx new file mode 100644 index 00000000..27eddb6c --- /dev/null +++ b/nixosModules/ags/v2/widgets/notifs/main.tsx @@ -0,0 +1,14 @@ +import { Astal } from 'astal/gtk3'; + +import Popups from './popups'; + + +export const NotifPopups = () => ( + + + +); diff --git a/nixosModules/ags/v2/widgets/notifs/notification.tsx b/nixosModules/ags/v2/widgets/notifs/notification.tsx new file mode 100644 index 00000000..2f81c601 --- /dev/null +++ b/nixosModules/ags/v2/widgets/notifs/notification.tsx @@ -0,0 +1,160 @@ +import { App, Gtk } from 'astal/gtk3'; +import { Variable } from 'astal'; + +import GLib from 'gi://GLib?version=2.0'; + +import AstalApps from 'gi://AstalApps?version=0.1'; +const Applications = AstalApps.Apps.new(); + +import AstalNotifd from 'gi://AstalNotifd?version=0.1'; +const Notifications = AstalNotifd.get_default(); + +import NotifGestureWrapper from './gesture'; + + +// Make a variable to connect to for Widgets +// to know when there are notifs or not +export const HasNotifs = Variable(false); + +const setTime = (time: number): string => { + return GLib.DateTime + .new_from_unix_local(time) + .format('%H:%M') ?? ''; +}; + +const NotifIcon = ({ notifObj }: { notifObj: AstalNotifd.Notification }) => { + let icon: string; + + if (notifObj.get_image() !== '') { + icon = notifObj.get_image(); + App.add_icons(icon); + } + else if (notifObj.get_app_icon() !== '') { + icon = notifObj.get_app_icon(); + } + else { + icon = Applications.query( + notifObj.get_app_name(), + false, + )[0].get_icon_name(); + } + + return ( + + + + ); +}; + +const BlockedApps = [ + 'Spotify', +]; + +export const Notification = ({ + id = 0, +}): ReturnType | undefined => { + const notifObj = Notifications.get_notification(id); + + if (!notifObj) { + return; + } + + if (BlockedApps.find((app) => app === notifObj.app_name)) { + notifObj.dismiss(); + + return; + } + + HasNotifs.set(Notifications.get_notifications().length > 0); + + return ( + + + {/* Content */} + + + + {/* Top of Content */} + + + + {/* Title */} + + + {/* Description */} + + + + {/* Actions */} + + {notifObj.get_actions().map((action) => ( + + ))} + + + + ) as ReturnType; +}; diff --git a/nixosModules/ags/v2/widgets/notifs/popups.tsx b/nixosModules/ags/v2/widgets/notifs/popups.tsx new file mode 100644 index 00000000..01129d1f --- /dev/null +++ b/nixosModules/ags/v2/widgets/notifs/popups.tsx @@ -0,0 +1,48 @@ +import AstalNotifd from 'gi://AstalNotifd?version=0.1'; +const Notifications = AstalNotifd.get_default(); + +import { Notification } from './notification'; + + +export default () => ( + { + const NotifsMap = new Map>(); + + const addPopup = (id: number) => { + if (!id) { + return; + } + + const NewNotif = Notification({ id }); + + if (NewNotif) { + // Use this instead of add to put it at the top + self.pack_end(NewNotif, false, false, 0); + self.show_all(); + + NotifsMap.set(id, NewNotif); + } + }; + + const handleResolved = (id: number) => { + const notif = NotifsMap.get(id); + + if (!notif) { + return; + } + + notif.slideAway('Left'); + NotifsMap.delete(id); + }; + + self + .hook(Notifications, 'notified', (_, id) => addPopup(id)) + .hook(Notifications, 'resolved', (_, id) => handleResolved(id)); + }} + /> +); diff --git a/nixosModules/ags/v2/widgets/notifs/style.scss b/nixosModules/ags/v2/widgets/notifs/style.scss new file mode 100644 index 00000000..c8d595be --- /dev/null +++ b/nixosModules/ags/v2/widgets/notifs/style.scss @@ -0,0 +1,32 @@ +.notification.widget { + // urgencies + // &.urgency ... + + .icon { + margin-right: 10px; + } + + .time { + margin: 3px; + } + + .close-button { + margin: 3px; + } + + .title { + font-weight: 800; + font-size: 20px; + margin-bottom: 5px; + } + + .description { + margin-bottom: 5px; + } + + .actions { + margin: 3px; + + .action-button {} + } +} diff --git a/nixosModules/desktop/environment/config/kdeglobals b/nixosModules/desktop/environment/config/kdeglobals index 719dadad..17898700 100644 --- a/nixosModules/desktop/environment/config/kdeglobals +++ b/nixosModules/desktop/environment/config/kdeglobals @@ -14,10 +14,10 @@ Show Inline Previews=true Show Preview=false Show Speedbar=true Show hidden files=true -Sort by=Name +Sort by=Date Sort directories first=true Sort hidden files last=false -Sort reversed=false +Sort reversed=true Speedbar Width=189 View Style=DetailTree