From 22722c27c4ccc24908dd779fb016ab31f7b36086 Mon Sep 17 00:00:00 2001 From: matt1432 Date: Sun, 7 Apr 2024 14:29:12 -0400 Subject: [PATCH] feat(ags): use hyprland animations for popup windows --- devices/wim/config/hypr/main.conf | 1 - modules/ags/config/global-types.d.ts | 6 +- modules/ags/config/ts/applauncher/app-item.ts | 2 +- modules/ags/config/ts/applauncher/main.ts | 5 +- modules/ags/config/ts/bar/binto.ts | 1 - modules/ags/config/ts/bar/fullscreen.ts | 33 +- modules/ags/config/ts/bar/items/cal-opener.ts | 4 +- .../ags/config/ts/bar/items/notif-button.ts | 6 +- .../ags/config/ts/bar/items/quick-settings.ts | 6 +- modules/ags/config/ts/bar/wim.ts | 1 - modules/ags/config/ts/misc/popup.ts | 308 ++---------------- modules/ags/config/ts/notifications/binto.ts | 3 +- modules/ags/config/ts/notifications/center.ts | 2 +- modules/ags/config/ts/notifications/wim.ts | 1 + modules/ags/config/ts/osd/main.ts | 10 +- modules/ags/config/ts/powermenu.ts | 1 + .../config/ts/quick-settings/button-grid.ts | 2 +- modules/ags/config/ts/setup.ts | 2 +- modules/ags/default.nix | 23 +- 19 files changed, 103 insertions(+), 314 deletions(-) diff --git a/devices/wim/config/hypr/main.conf b/devices/wim/config/hypr/main.conf index cd6781b3..680ad837 100644 --- a/devices/wim/config/hypr/main.conf +++ b/devices/wim/config/hypr/main.conf @@ -23,7 +23,6 @@ animations { enabled = yes bezier = myBezier, 0.05, 0.9, 0.1, 1.05 - bezier = easeInOutBack, 0.68, -0.6, 0.32, 1.6 bezier = easeInBack, 0.36, 0, 0.66, -0.56 bezier = easeOutBack, 0.34, 1.56, 0.64, 1 diff --git a/modules/ags/config/global-types.d.ts b/modules/ags/config/global-types.d.ts index 214055be..92ec6d02 100644 --- a/modules/ags/config/global-types.d.ts +++ b/modules/ags/config/global-types.d.ts @@ -120,12 +120,12 @@ Var, 'is_listening' | 'is_polling' | 'value', Widget[] >; +export type HyprTransition = 'slide' | 'slide top' | 'slide bottom' | 'slide left' | +'slide right' | 'popin' | 'fade'; export type CloseType = 'none' | 'stay' | 'released' | 'clicked'; export type PopupWindowProps = WindowProps & { - transition?: RevealerProps['transition'] - transition_duration?: number - bezier?: string + transition?: HyprTransition; on_open?(self: PopupWindow): void on_close?(self: PopupWindow): void blur?: boolean diff --git a/modules/ags/config/ts/applauncher/app-item.ts b/modules/ags/config/ts/applauncher/app-item.ts index 875e13b4..3db5d14a 100644 --- a/modules/ags/config/ts/applauncher/app-item.ts +++ b/modules/ags/config/ts/applauncher/app-item.ts @@ -54,7 +54,7 @@ export default (app: Application) => { attribute: { app }, on_primary_click_release: () => { - App.closeWindow('applauncher'); + App.closeWindow('win-applauncher'); app.launch(); }, diff --git a/modules/ags/config/ts/applauncher/main.ts b/modules/ags/config/ts/applauncher/main.ts index ab4807af..c0844a3e 100644 --- a/modules/ags/config/ts/applauncher/main.ts +++ b/modules/ags/config/ts/applauncher/main.ts @@ -71,7 +71,7 @@ const Applauncher = (window_name = 'applauncher') => { const appList = Applications.query(text || ''); if (appList[0]) { - App.closeWindow(window_name); + App.closeWindow(`win-${window_name}`); appList[0].launch(); } }, @@ -112,7 +112,7 @@ const Applauncher = (window_name = 'applauncher') => { setup: (self) => { self.hook(App, (_, name, visible) => { - if (name !== window_name) { + if (name !== `win-${window_name}`) { return; } @@ -150,6 +150,7 @@ const Applauncher = (window_name = 'applauncher') => { export default () => PopupWindow({ name: 'applauncher', + transition: 'slide top', keymode: 'on-demand', content: Applauncher(), }); diff --git a/modules/ags/config/ts/bar/binto.ts b/modules/ags/config/ts/bar/binto.ts index e5bc5d96..b6e3b7f0 100644 --- a/modules/ags/config/ts/bar/binto.ts +++ b/modules/ags/config/ts/bar/binto.ts @@ -16,7 +16,6 @@ export default () => BarRevealer({ monitor: 1, exclusivity: 'exclusive', anchor: ['bottom', 'left', 'right'], - transition: 'slide_up', bar: Box({ vertical: true, children: [ diff --git a/modules/ags/config/ts/bar/fullscreen.ts b/modules/ags/config/ts/bar/fullscreen.ts index be6e912a..e74457fb 100644 --- a/modules/ags/config/ts/bar/fullscreen.ts +++ b/modules/ags/config/ts/bar/fullscreen.ts @@ -38,7 +38,7 @@ Hyprland.connect('event', (hyprObj) => { } }); -export default ({ bar, transition, monitor = 0, ...rest }) => { +export default ({ anchor, bar, monitor = 0, ...rest }) => { const BarVisible = Variable(true); FullscreenState.connect('changed', (v) => { @@ -83,9 +83,25 @@ export default ({ bar, transition, monitor = 0, ...rest }) => { visible: BarVisible.bind().as((v) => !v), }); - const rev = Revealer({ - transition, + const vertical = anchor.includes('left') && anchor.includes('right'); + const isBottomOrLeft = ( + anchor.includes('left') && anchor.includes('right') && anchor.includes('bottom') + ) || ( + anchor.includes('top') && anchor.includes('bottom') && anchor.includes('left') + ); + + let transition: 'slide_up' | 'slide_down' | 'slide_left' | 'slide_right'; + + if (vertical) { + transition = isBottomOrLeft ? 'slide_up' : 'slide_down'; + } + else { + transition = isBottomOrLeft ? 'slide_right' : 'slide_left'; + } + + const barWrap = Revealer({ reveal_child: BarVisible.bind(), + transition, child: bar, }); @@ -94,6 +110,7 @@ export default ({ bar, transition, monitor = 0, ...rest }) => { layer: 'overlay', monitor, margins: [-1, -1, -1, -1], + anchor, ...rest, attribute: { @@ -105,13 +122,11 @@ export default ({ bar, transition, monitor = 0, ...rest }) => { css: 'min-height: 1px; padding: 1px;', hexpand: true, hpack: 'fill', - vertical: transition === 'slide_up' || - transition === 'slide_down', + vertical, - children: transition === 'slide_up' || - transition === 'slide_left' ? - [buffer, rev] : - [rev, buffer], + children: isBottomOrLeft ? + [buffer, barWrap] : + [barWrap, buffer], }), }).on('enter-notify-event', () => { if (!BarVisible.value) { diff --git a/modules/ags/config/ts/bar/items/cal-opener.ts b/modules/ags/config/ts/bar/items/cal-opener.ts index ab18300d..e8b764d7 100644 --- a/modules/ags/config/ts/bar/items/cal-opener.ts +++ b/modules/ags/config/ts/bar/items/cal-opener.ts @@ -5,11 +5,11 @@ import Clock from './clock'; export default () => CursorBox({ class_name: 'toggle-off', - on_primary_click_release: () => App.toggleWindow('calendar'), + on_primary_click_release: () => App.toggleWindow('win-calendar'), setup: (self) => { self.hook(App, (_, windowName, visible) => { - if (windowName === 'calendar') { + if (windowName === 'win-calendar') { self.toggleClassName('toggle-on', visible); } }); diff --git a/modules/ags/config/ts/bar/items/notif-button.ts b/modules/ags/config/ts/bar/items/notif-button.ts index 69418846..6221790b 100644 --- a/modules/ags/config/ts/bar/items/notif-button.ts +++ b/modules/ags/config/ts/bar/items/notif-button.ts @@ -14,18 +14,18 @@ export default () => CursorBox({ class_name: 'toggle-off', on_primary_click_release: (self) => { - (App.getWindow('notification-center') as PopupWindow) + (App.getWindow('win-notification-center') as PopupWindow) .set_x_pos( self.get_allocation(), 'right', ); - App.toggleWindow('notification-center'); + App.toggleWindow('win-notification-center'); }, setup: (self) => { self.hook(App, (_, windowName, visible) => { - if (windowName === 'notification-center') { + if (windowName === 'win-notification-center') { self.toggleClassName('toggle-on', visible); } }); diff --git a/modules/ags/config/ts/bar/items/quick-settings.ts b/modules/ags/config/ts/bar/items/quick-settings.ts index 3dc189c0..7c8d0123 100644 --- a/modules/ags/config/ts/bar/items/quick-settings.ts +++ b/modules/ags/config/ts/bar/items/quick-settings.ts @@ -32,18 +32,18 @@ export default () => { class_name: 'toggle-off', on_primary_click_release: (self) => { - (App.getWindow('quick-settings') as PopupWindow) + (App.getWindow('win-quick-settings') as PopupWindow) .set_x_pos( self.get_allocation(), 'right', ); - App.toggleWindow('quick-settings'); + App.toggleWindow('win-quick-settings'); }, setup: (self) => { self.hook(App, (_, windowName, visible) => { - if (windowName === 'quick-settings') { + if (windowName === 'win-quick-settings') { self.toggleClassName('toggle-on', visible); } }); diff --git a/modules/ags/config/ts/bar/wim.ts b/modules/ags/config/ts/bar/wim.ts index 3c392ac6..7c188a0d 100644 --- a/modules/ags/config/ts/bar/wim.ts +++ b/modules/ags/config/ts/bar/wim.ts @@ -21,7 +21,6 @@ const SPACING = 12; export default () => BarRevealer({ anchor: ['top', 'left', 'right'], exclusivity: 'exclusive', - transition: 'slide_down', bar: CenterBox({ css: 'margin: 5px 5px 5px 5px', class_name: 'bar', diff --git a/modules/ags/config/ts/misc/popup.ts b/modules/ags/config/ts/misc/popup.ts index 13df3748..f29772c1 100644 --- a/modules/ags/config/ts/misc/popup.ts +++ b/modules/ags/config/ts/misc/popup.ts @@ -1,8 +1,7 @@ import Gtk from 'gi://Gtk?version=3.0'; const Hyprland = await Service.import('hyprland'); -const { Box, Overlay, register } = Widget; - +const { Box, register } = Widget; const { timeout } = Utils; // Types @@ -11,15 +10,10 @@ import { Variable as Var } from 'types/variable'; import { CloseType, - BoxGeneric, - OverlayGeneric, - PopupChild, + HyprTransition, PopupWindowProps, } from 'global-types'; -// FIXME: deal with overlay children? -// TODO: make props changes affect the widget - export class PopupWindow< Child extends Gtk.Widget, @@ -34,9 +28,8 @@ export class PopupWindow< } #content: Var; - #antiClip: Var; - #needsAnticlipping: boolean; #close_on_unfocus: CloseType; + #transition: HyprTransition; get content() { return this.#content.value; @@ -51,14 +44,22 @@ export class PopupWindow< return this.#close_on_unfocus; } - set close_on_unfocus(value: 'none' | 'stay' | 'released' | 'clicked') { + set close_on_unfocus(value: CloseType) { this.#close_on_unfocus = value; } + get transition() { + return this.#transition; + } + + set transition(t: HyprTransition) { + this.#transition = t; + Hyprland.messageAsync(`keyword layerrule animation ${t}, ${this.name}`); + } + constructor({ - transition = 'slide_down', + transition = 'slide top', transition_duration = 800, - bezier = 'cubic-bezier(0.68, -0.4, 0.32, 1.4)', on_open = () => {/**/}, on_close = () => {/**/}, @@ -73,10 +74,7 @@ export class PopupWindow< close_on_unfocus = 'released', ...rest }: PopupWindowProps) { - const needsAnticlipping = bezier.match(/-[0-9]/) !== null && - transition !== 'crossfade'; const contentVar = Variable(Box() as Gtk.Widget); - const antiClip = Variable(false); if (content) { contentVar.setValue(content); @@ -84,19 +82,16 @@ export class PopupWindow< super({ ...rest, - name, + name: `win-${name}`, visible, anchor, layer, attribute, setup: () => { const id = App.connect('config-parsed', () => { - // Set close delay dynamically - App.closeWindowDelay[name] = transition_duration; - // Add way to make window open on startup if (visible) { - App.openWindow(`${name}`); + App.openWindow(`win-${name}`); } // This connection should always run only once @@ -105,266 +100,33 @@ export class PopupWindow< if (blur) { Hyprland.messageAsync('[[BATCH]] ' + - `keyword layerrule ignorealpha[0.97],${name}; ` + - `keyword layerrule blur,${name}`); + `keyword layerrule ignorealpha 0.97, win-${name}; ` + + `keyword layerrule blur, win-${name}`); } + + Hyprland.messageAsync( + `keyword layerrule animation ${transition}, win-${name}`, + ); }, - child: Overlay({ - overlays: [Box({ - css: ` - min-height: 1px; - min-width: 1px; - padding: 1px; - `, - setup: (self) => { - // Make sure child doesn't - // get bigger than it should - const MAX_ANCHORS = 4; + child: contentVar.bind(), + }); - self.hpack = 'center'; - self.vpack = 'center'; - - if (anchor.includes('top') && - anchor.includes('bottom')) { - self.vpack = 'center'; - } - else if (anchor.includes('top')) { - self.vpack = 'start'; - } - else if (anchor.includes('bottom')) { - self.vpack = 'end'; - } - - if (anchor.includes('left') && - anchor.includes('right')) { - self.hpack = 'center'; - } - else if (anchor.includes('left')) { - self.hpack = 'start'; - } - else if (anchor.includes('right')) { - self.hpack = 'end'; - } - - if (anchor.length === MAX_ANCHORS) { - self.hpack = 'center'; - self.vpack = 'center'; - } - - if (needsAnticlipping) { - const reorder_child = (position: number) => { - // If unanchored, we have another anticlip widget - // so we can't change the order - if (anchor.length !== 0) { - for (const ch of self.children) { - if (ch !== contentVar.value) { - self.reorder_child(ch, position); - - return; - } - } - } - }; - - self.hook(antiClip, () => { - if (transition === 'slide_down') { - self.vertical = true; - reorder_child(-1); - } - else if (transition === 'slide_up') { - self.vertical = true; - reorder_child(0); - } - else if (transition === 'slide_right') { - self.vertical = false; - reorder_child(-1); - } - else if (transition === 'slide_left') { - self.vertical = false; - reorder_child(0); - } - }); - } - }, - - children: contentVar.bind().transform((v) => { - if (needsAnticlipping) { - return [ - // Add an anticlip widget when unanchored - // to not have a weird animation - anchor.length === 0 && Box({ - css: ` - min-height: 100px; - min-width: 100px; - padding: 2px; - `, - visible: antiClip.bind(), - }), - v, - Box({ - css: ` - min-height: 100px; - min-width: 100px; - padding: 2px; - `, - visible: antiClip.bind(), - }), - ]; - } - else { - return [v]; - } - }) as PopupChild, - })], - - setup: (self) => { - self.on('get-child-position', (_, ch) => { - const overlay = contentVar.value - .get_parent() as OverlayGeneric; - - if (ch === overlay) { - const alloc = overlay.get_allocation(); - - (self.child as BoxGeneric).css = ` - min-height: ${alloc.height}px; - min-width: ${alloc.width}px; - `; - } + this.hook(App, (_, currentName, isOpen) => { + if (currentName === `win-${name}`) { + if (isOpen) { + on_open(this); + } + else { + timeout(Number(transition_duration), () => { + on_close(this); }); - }, - - child: Box({ - css: ` - min-height: 1px; - min-width: 1px; - padding: 1px; - `, - - setup: (self) => { - let currentTimeout: number; - - self.hook(App, (_, currentName, isOpen) => { - if (currentName === name) { - const overlay = contentVar.value - .get_parent() as OverlayGeneric; - - const alloc = overlay.get_allocation(); - const height = antiClip ? - alloc.height + 100 + 10 : - alloc.height + 10; - - if (needsAnticlipping) { - antiClip.setValue(true); - - const thisTimeout = timeout( - transition_duration, - () => { - // Only run the timeout if there isn't a newer timeout - if (thisTimeout === - currentTimeout) { - antiClip.setValue(false); - } - }, - ); - - currentTimeout = thisTimeout; - } - - let css = ''; - - /* Margin: top | right | bottom | left */ - switch (transition) { - case 'slide_down': - css = `margin: - -${height}px - 0 - ${height}px - 0 - ;`; - break; - - case 'slide_up': - css = `margin: - ${height}px - 0 - -${height}px - 0 - ;`; - break; - - case 'slide_left': - css = `margin: - 0 - -${height}px - 0 - ${height}px - ;`; - break; - - case 'slide_right': - css = `margin: - 0 - ${height}px - 0 - -${height}px - ;`; - break; - - case 'crossfade': - css = ` - opacity: 0; - min-height: 1px; - min-width: 1px; - `; - break; - - default: - break; - } - - if (isOpen) { - on_open(this); - - // To get the animation, we need to set the css - // to hide the widget and then timeout to have - // the animation - overlay.css = css; - timeout(10, () => { - overlay.css = ` - transition: margin - ${transition_duration}ms - ${bezier}, - - opacity - ${transition_duration}ms - ${bezier}; - `; - }); - } - else { - timeout(transition_duration, () => { - on_close(this); - }); - - overlay.css = `${css} - transition: margin - ${transition_duration}ms ${bezier}, - - opacity - ${transition_duration}ms ${bezier}; - `; - } - } - }); - }, - }), - }), + } + } }); this.#content = contentVar; this.#close_on_unfocus = close_on_unfocus; - this.#needsAnticlipping = needsAnticlipping; - this.#antiClip = antiClip; + this.#transition = transition; } set_x_pos( diff --git a/modules/ags/config/ts/notifications/binto.ts b/modules/ags/config/ts/notifications/binto.ts index 4eabe753..975b30be 100644 --- a/modules/ags/config/ts/notifications/binto.ts +++ b/modules/ags/config/ts/notifications/binto.ts @@ -9,6 +9,7 @@ import PopupWindow from '../misc/popup.ts'; export const NotifPopups = () => Window({ name: 'notifications', anchor: ['bottom', 'left'], + layer: 'overlay', monitor: 1, child: PopUpsWidget(), @@ -18,7 +19,7 @@ export const NotifPopups = () => Window({ export const NotifCenter = () => PopupWindow({ name: 'notification-center', anchor: ['bottom', 'right'], - transition: 'slide_up', + transition: 'slide bottom', monitor: 1, content: NotifCenterWidget(), diff --git a/modules/ags/config/ts/notifications/center.ts b/modules/ags/config/ts/notifications/center.ts index a16d7423..73b25d1b 100644 --- a/modules/ags/config/ts/notifications/center.ts +++ b/modules/ags/config/ts/notifications/center.ts @@ -76,7 +76,7 @@ const ClearButton = () => CursorBox({ on_primary_click_release: () => { Notifications.clear(); - timeout(1000, () => App.closeWindow('notification-center')); + timeout(1000, () => App.closeWindow('win-notification-center')); }, setup: (self) => { diff --git a/modules/ags/config/ts/notifications/wim.ts b/modules/ags/config/ts/notifications/wim.ts index 68a3cc20..3acc89e5 100644 --- a/modules/ags/config/ts/notifications/wim.ts +++ b/modules/ags/config/ts/notifications/wim.ts @@ -8,6 +8,7 @@ import PopupWindow from '../misc/popup.ts'; export const NotifPopups = () => Window({ name: 'notifications', + layer: 'overlay', anchor: ['top', 'left'], child: PopUpsWidget(), }); diff --git a/modules/ags/config/ts/osd/main.ts b/modules/ags/config/ts/osd/main.ts index 7e5b36c8..f584902d 100644 --- a/modules/ags/config/ts/osd/main.ts +++ b/modules/ags/config/ts/osd/main.ts @@ -41,6 +41,8 @@ const OSDs = () => { }), ); + stack.show_all(); + // Delay popup method so it // doesn't show any OSDs at launch timeout(1000, () => { @@ -49,13 +51,13 @@ const OSDs = () => { stack.attribute.popup = (osd: string) => { ++count; stack.shown = osd; - App.openWindow('osd'); + App.openWindow('win-osd'); timeout(HIDE_DELAY, () => { --count; if (count === 0) { - App.closeWindow('osd'); + App.closeWindow('win-osd'); } }); }; @@ -71,8 +73,6 @@ export default () => PopupWindow({ anchor: ['bottom'], exclusivity: 'ignore', close_on_unfocus: 'stay', - transition: 'slide_up', - transition_duration, - bezier: 'ease', + transition: 'slide bottom', content: OSDs(), }); diff --git a/modules/ags/config/ts/powermenu.ts b/modules/ags/config/ts/powermenu.ts index c10f8b1d..055c142e 100644 --- a/modules/ags/config/ts/powermenu.ts +++ b/modules/ags/config/ts/powermenu.ts @@ -44,5 +44,6 @@ const PowermenuWidget = () => CenterBox({ export default () => PopupWindow({ name: 'powermenu', + transition: 'slide bottom', content: PowermenuWidget(), }); diff --git a/modules/ags/config/ts/quick-settings/button-grid.ts b/modules/ags/config/ts/quick-settings/button-grid.ts index e70f15a0..401b182b 100644 --- a/modules/ags/config/ts/quick-settings/button-grid.ts +++ b/modules/ags/config/ts/quick-settings/button-grid.ts @@ -327,7 +327,7 @@ const SecondRow = () => Row({ command: () => { execAsync(['lock']).catch(print); }, - secondary_command: () => App.openWindow('powermenu'), + secondary_command: () => App.openWindow('win-powermenu'), icon: 'system-lock-screen-symbolic', }), ], diff --git a/modules/ags/config/ts/setup.ts b/modules/ags/config/ts/setup.ts index 17d8fb59..287db148 100644 --- a/modules/ags/config/ts/setup.ts +++ b/modules/ags/config/ts/setup.ts @@ -26,7 +26,7 @@ export default () => { name: 'openAppLauncher', gesture: 'UD', edge: 'T', - command: () => App.openWindow('applauncher'), + command: () => App.openWindow('win-applauncher'), }); TouchGestures.addGesture({ diff --git a/modules/ags/default.nix b/modules/ags/default.nix index c6b5d0af..bbdeb784 100644 --- a/modules/ags/default.nix +++ b/modules/ags/default.nix @@ -94,19 +94,30 @@ in { wayland.windowManager.hyprland = { settings = { - animations.animation = [ - # Ags takes care of doing the animations - "layers, 0" + animations = { + bezier = [ + "easeInOutBack, 0.68, -0.6, 0.32, 1.6" + ]; + + animation = [ + "fadeLayersIn, 0" + "fadeLayersOut, 1, 3000, default" + "layers, 1, 8, easeInOutBack, slide left" + ]; + }; + + layerrule = [ + "noanim, ^(?!win-).*" ]; exec-once = [ "ags" - "sleep 3; ags -r 'App.openWindow(\"applauncher\")'" + "sleep 3; ags -r 'App.openWindow(\"win-applauncher\")'" ]; bind = [ - "$mainMod SHIFT, E, exec, ags -t powermenu" - "$mainMod , D, exec, ags -t applauncher" + "$mainMod SHIFT, E, exec, ags -t win-powermenu" + "$mainMod , D, exec, ags -t win-applauncher" ]; binde = [ ## Brightness control