From f80ff3babd422bbf252cb80541810c9424c4ccec Mon Sep 17 00:00:00 2001 From: Sho Date: Sun, 2 Aug 2026 14:18:16 +0900 Subject: [PATCH] feat: implement ease in (pop in) and pop out animations across all QuickShell popups --- quickshell/modules/dock/BottomDock.qml | 40 +- quickshell/modules/dock/ThemePicker.qml | 39 +- quickshell/modules/topbar/TopCenterBar.qml | 80 +++- quickshell/modules/topbar/TopLeftBar.qml | 40 +- quickshell/modules/topbar/TopRightBar.qml | 406 ++++++++++++++++++++- 5 files changed, 587 insertions(+), 18 deletions(-) diff --git a/quickshell/modules/dock/BottomDock.qml b/quickshell/modules/dock/BottomDock.qml index 5f0dc46..048ac98 100644 --- a/quickshell/modules/dock/BottomDock.qml +++ b/quickshell/modules/dock/BottomDock.qml @@ -417,18 +417,56 @@ Item { anchor.rect.x: Math.round(root.contextTargetX) anchor.rect.y: 0 anchor.edges: Edges.Top | Edges.Left - visible: PopupService.dockMenuOpen + visible: false color: "transparent" implicitWidth: dockCtxGlass.implicitWidth implicitHeight: dockCtxGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: dockMenuPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: dockMenuPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: dockContextMenu.visible = false + } + + Connections { + target: PopupService + function onDockMenuOpenChanged() { + if (PopupService.dockMenuOpen) { + dockMenuPopOut.running = false + dockContextMenu.visible = true + dockMenuPopIn.restart() + } else if (dockContextMenu.visible) { + dockMenuPopIn.running = false + dockMenuPopOut.restart() + } + } + } + GlassPanel { id: dockCtxGlass implicitWidth: 160 implicitHeight: ctxCol.implicitHeight + 16 anchors.fill: parent + opacity: dockContextMenu.animProgress + scale: 0.90 + 0.10 * dockContextMenu.animProgress + transformOrigin: Item.BottomLeft + ColumnLayout { id: ctxCol anchors.fill: parent diff --git a/quickshell/modules/dock/ThemePicker.qml b/quickshell/modules/dock/ThemePicker.qml index 0899e09..7e54ba4 100644 --- a/quickshell/modules/dock/ThemePicker.qml +++ b/quickshell/modules/dock/ThemePicker.qml @@ -7,7 +7,44 @@ import "../../theme" Item { id: root - visible: PopupService.themePickerOpen + visible: false + opacity: animProgress + scale: 0.90 + 0.10 * animProgress + transformOrigin: Item.BottomRight + + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: tpPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: tpPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: root.visible = false + } + + Connections { + target: PopupService + function onThemePickerOpenChanged() { + if (PopupService.themePickerOpen) { + tpPopOut.running = false + root.visible = true + tpPopIn.restart() + } else if (root.visible) { + tpPopIn.running = false + tpPopOut.restart() + } + } + } property string activeTab: "themes" // "themes" or "wallpapers" property string activeCategory: "All" diff --git a/quickshell/modules/topbar/TopCenterBar.qml b/quickshell/modules/topbar/TopCenterBar.qml index 90e2f9a..508e795 100644 --- a/quickshell/modules/topbar/TopCenterBar.qml +++ b/quickshell/modules/topbar/TopCenterBar.qml @@ -147,18 +147,56 @@ GlassPanel { anchor.rect.x: root.x + (root.width / 2) - (implicitWidth / 2) anchor.rect.y: 42 anchor.edges: Edges.Bottom - visible: PopupService.calendarMenuOpen + visible: false color: "transparent" implicitWidth: calendarGlass.implicitWidth implicitHeight: calendarGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: calPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: calPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: timeMenu.visible = false + } + + Connections { + target: PopupService + function onCalendarMenuOpenChanged() { + if (PopupService.calendarMenuOpen) { + calPopOut.running = false + timeMenu.visible = true + calPopIn.restart() + } else if (timeMenu.visible) { + calPopIn.running = false + calPopOut.restart() + } + } + } + GlassPanel { id: calendarGlass implicitWidth: calWidget.implicitWidth + 24 implicitHeight: calWidget.implicitHeight + 20 anchors.fill: parent + opacity: timeMenu.animProgress + scale: 0.90 + 0.10 * timeMenu.animProgress + transformOrigin: Item.Top + CalendarWidget { id: calWidget anchors.centerIn: parent @@ -173,18 +211,56 @@ GlassPanel { anchor.rect.x: root.x + (root.width / 2) - (implicitWidth / 2) anchor.rect.y: 42 anchor.edges: Edges.Bottom - visible: PopupService.mediaMenuOpen + visible: false color: "transparent" implicitWidth: playerGlass.implicitWidth implicitHeight: playerGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: mediaPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: mediaPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: mediaMenu.visible = false + } + + Connections { + target: PopupService + function onMediaMenuOpenChanged() { + if (PopupService.mediaMenuOpen) { + mediaPopOut.running = false + mediaMenu.visible = true + mediaPopIn.restart() + } else if (mediaMenu.visible) { + mediaPopIn.running = false + mediaPopOut.restart() + } + } + } + GlassPanel { id: playerGlass implicitWidth: 240 implicitHeight: playerLayout.implicitHeight + 24 anchors.fill: parent + opacity: mediaMenu.animProgress + scale: 0.90 + 0.10 * mediaMenu.animProgress + transformOrigin: Item.Top + ColumnLayout { id: playerLayout anchors.fill: parent diff --git a/quickshell/modules/topbar/TopLeftBar.qml b/quickshell/modules/topbar/TopLeftBar.qml index b9791a2..975bf25 100644 --- a/quickshell/modules/topbar/TopLeftBar.qml +++ b/quickshell/modules/topbar/TopLeftBar.qml @@ -129,18 +129,56 @@ GlassPanel { anchor.rect.x: 8 anchor.rect.y: 42 anchor.edges: Edges.Bottom | Edges.Left - visible: PopupService.sessionMenuOpen + visible: false color: "transparent" implicitWidth: contentGlass.implicitWidth implicitHeight: contentGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: sessPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: sessPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: sessionMenu.visible = false + } + + Connections { + target: PopupService + function onSessionMenuOpenChanged() { + if (PopupService.sessionMenuOpen) { + sessPopOut.running = false + sessionMenu.visible = true + sessPopIn.restart() + } else if (sessionMenu.visible) { + sessPopIn.running = false + sessPopOut.restart() + } + } + } + GlassPanel { id: contentGlass implicitWidth: menuLayout.implicitWidth + 16 implicitHeight: menuLayout.implicitHeight + 12 anchors.fill: parent + opacity: sessionMenu.animProgress + scale: 0.90 + 0.10 * sessionMenu.animProgress + transformOrigin: Item.TopLeft + ColumnLayout { id: menuLayout anchors.fill: parent diff --git a/quickshell/modules/topbar/TopRightBar.qml b/quickshell/modules/topbar/TopRightBar.qml index ccd7e21..2dee93e 100644 --- a/quickshell/modules/topbar/TopRightBar.qml +++ b/quickshell/modules/topbar/TopRightBar.qml @@ -402,18 +402,56 @@ GlassPanel { anchor.rect.x: Math.round(root.x + root.width - implicitWidth) anchor.rect.y: 40 anchor.edges: Edges.Bottom | Edges.Right - visible: PopupService.captureMenuOpen + visible: false color: "transparent" implicitWidth: capGlass.implicitWidth implicitHeight: capGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: capPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: capPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: captureMenu.visible = false + } + + Connections { + target: PopupService + function onCaptureMenuOpenChanged() { + if (PopupService.captureMenuOpen) { + capPopOut.running = false + captureMenu.visible = true + capPopIn.restart() + } else if (captureMenu.visible) { + capPopIn.running = false + capPopOut.restart() + } + } + } + GlassPanel { id: capGlass implicitWidth: 280 implicitHeight: Math.min(420, capCardLayout.implicitHeight + 24) anchors.fill: parent + opacity: captureMenu.animProgress + scale: 0.90 + 0.10 * captureMenu.animProgress + transformOrigin: Item.TopRight + ColumnLayout { id: capCardLayout anchors.fill: parent @@ -668,18 +706,56 @@ GlassPanel { anchor.rect.x: root.x + (root.width / 2) - (implicitWidth / 2) anchor.rect.y: 42 anchor.edges: Edges.Bottom - visible: PopupService.notificationMenuOpen + visible: false color: "transparent" implicitWidth: notifGlass.implicitWidth implicitHeight: notifGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: notifPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: notifPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: notifMenu.visible = false + } + + Connections { + target: PopupService + function onNotificationMenuOpenChanged() { + if (PopupService.notificationMenuOpen) { + notifPopOut.running = false + notifMenu.visible = true + notifPopIn.restart() + } else if (notifMenu.visible) { + notifPopIn.running = false + notifPopOut.restart() + } + } + } + GlassPanel { id: notifGlass implicitWidth: 320 implicitHeight: notifLayout.implicitHeight + 20 anchors.fill: parent + opacity: notifMenu.animProgress + scale: 0.90 + 0.10 * notifMenu.animProgress + transformOrigin: Item.Top + ColumnLayout { id: notifLayout anchors.fill: parent @@ -864,25 +940,63 @@ GlassPanel { } } - // Clipboard Manager Detached Popup Window + // Clipboard Manager PopupWindow { - id: clipboardMenu + id: clipMenu anchor.window: window anchor.rect.x: Math.round(root.x + root.width - implicitWidth) anchor.rect.y: 40 anchor.edges: Edges.Bottom | Edges.Right - visible: PopupService.clipboardMenuOpen + visible: false color: "transparent" implicitWidth: clipGlass.implicitWidth implicitHeight: clipGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: clipPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: clipPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: clipMenu.visible = false + } + + Connections { + target: PopupService + function onClipboardMenuOpenChanged() { + if (PopupService.clipboardMenuOpen) { + clipPopOut.running = false + clipMenu.visible = true + clipPopIn.restart() + } else if (clipMenu.visible) { + clipPopIn.running = false + clipPopOut.restart() + } + } + } + GlassPanel { id: clipGlass implicitWidth: 320 implicitHeight: Math.min(500, clipCardLayout.implicitHeight + 24) anchors.fill: parent + opacity: clipMenu.animProgress + scale: 0.90 + 0.10 * clipMenu.animProgress + transformOrigin: Item.TopRight + ColumnLayout { id: clipCardLayout anchors.fill: parent @@ -1056,23 +1170,61 @@ GlassPanel { // Bluetooth Control Detached Popup Window PopupWindow { - id: bluetoothMenu + id: btMenu anchor.window: window anchor.rect.x: Math.round(root.x + root.width - implicitWidth) anchor.rect.y: 40 anchor.edges: Edges.Bottom | Edges.Right - visible: PopupService.bluetoothMenuOpen + visible: false color: "transparent" implicitWidth: btGlass.implicitWidth implicitHeight: btGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: btPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: btPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: btMenu.visible = false + } + + Connections { + target: PopupService + function onBluetoothMenuOpenChanged() { + if (PopupService.bluetoothMenuOpen) { + btPopOut.running = false + btMenu.visible = true + btPopIn.restart() + } else if (btMenu.visible) { + btPopIn.running = false + btPopOut.restart() + } + } + } + GlassPanel { id: btGlass implicitWidth: 300 implicitHeight: BluetoothService.isPowered ? 240 : 80 anchors.fill: parent + opacity: btMenu.animProgress + scale: 0.90 + 0.10 * btMenu.animProgress + transformOrigin: Item.TopRight + ColumnLayout { id: btCardLayout anchors.fill: parent @@ -1325,18 +1477,56 @@ GlassPanel { anchor.rect.x: Math.round(root.x + root.width - implicitWidth) anchor.rect.y: 40 anchor.edges: Edges.Bottom | Edges.Right - visible: PopupService.audioMenuOpen + visible: false color: "transparent" implicitWidth: audioGlass.implicitWidth implicitHeight: audioGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: audioPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: audioPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: audioMenu.visible = false + } + +Connections { + target: PopupService + function onAudioMenuOpenChanged() { + if (PopupService.audioMenuOpen) { + audioPopOut.running = false + audioMenu.visible = true + audioPopIn.restart() + } else if (audioMenu.visible) { + audioPopIn.running = false + audioPopOut.restart() + } + } + } + GlassPanel { id: audioGlass implicitWidth: 300 implicitHeight: Math.min(480, audioCardLayout.implicitHeight + 24) anchors.fill: parent + opacity: audioMenu.animProgress + scale: 0.90 + 0.10 * audioMenu.animProgress + transformOrigin: Item.TopRight + ColumnLayout { id: audioCardLayout anchors.fill: parent @@ -1598,18 +1788,56 @@ GlassPanel { anchor.rect.x: Math.round(root.x + root.width - implicitWidth) anchor.rect.y: 40 anchor.edges: Edges.Bottom | Edges.Right - visible: PopupService.brightnessMenuOpen + visible: false color: "transparent" implicitWidth: brightGlass.implicitWidth implicitHeight: brightGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: brightPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: brightPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: brightMenu.visible = false + } + + Connections { + target: PopupService + function onBrightnessMenuOpenChanged() { + if (PopupService.brightnessMenuOpen) { + brightPopOut.running = false + brightMenu.visible = true + brightPopIn.restart() + } else if (brightMenu.visible) { + brightPopIn.running = false + brightPopOut.restart() + } + } + } + GlassPanel { id: brightGlass implicitWidth: 300 implicitHeight: Math.min(400, brightCardLayout.implicitHeight + 24) anchors.fill: parent + opacity: brightMenu.animProgress + scale: 0.90 + 0.10 * brightMenu.animProgress + transformOrigin: Item.TopRight + ColumnLayout { id: brightCardLayout anchors.fill: parent @@ -1766,18 +1994,56 @@ GlassPanel { anchor.rect.x: Math.round(root.x + root.width - implicitWidth) anchor.rect.y: 40 anchor.edges: Edges.Bottom | Edges.Right - visible: PopupService.mountMenuOpen + visible: false color: "transparent" implicitWidth: mountGlass.implicitWidth implicitHeight: mountGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: mountPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: mountPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: mountMenu.visible = false + } + + Connections { + target: PopupService + function onMountMenuOpenChanged() { + if (PopupService.mountMenuOpen) { + mountPopOut.running = false + mountMenu.visible = true + mountPopIn.restart() + } else if (mountMenu.visible) { + mountPopIn.running = false + mountPopOut.restart() + } + } + } + GlassPanel { id: mountGlass implicitWidth: 320 implicitHeight: Math.min(480, mountCardLayout.implicitHeight + 24) anchors.fill: parent + opacity: mountMenu.animProgress + scale: 0.90 + 0.10 * mountMenu.animProgress + transformOrigin: Item.TopRight + ColumnLayout { id: mountCardLayout anchors.fill: parent @@ -1976,18 +2242,56 @@ GlassPanel { anchor.rect.x: Math.round(root.x + root.width - implicitWidth) anchor.rect.y: 40 anchor.edges: Edges.Bottom | Edges.Right - visible: PopupService.networkMenuOpen + visible: false color: "transparent" implicitWidth: netGlass.implicitWidth implicitHeight: netGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: netPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: netPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: netMenu.visible = false + } + + Connections { + target: PopupService + function onNetworkMenuOpenChanged() { + if (PopupService.networkMenuOpen) { + netPopOut.running = false + netMenu.visible = true + netPopIn.restart() + } else if (netMenu.visible) { + netPopIn.running = false + netPopOut.restart() + } + } + } + GlassPanel { id: netGlass implicitWidth: 300 implicitHeight: NetworkService.isWifiPowered ? 240 : 80 anchors.fill: parent + opacity: netMenu.animProgress + scale: 0.90 + 0.10 * netMenu.animProgress + transformOrigin: Item.TopRight + ColumnLayout { id: netCardLayout anchors.fill: parent @@ -2178,18 +2482,56 @@ GlassPanel { anchor.rect.x: Math.round(root.x + root.width - implicitWidth) anchor.rect.y: 40 anchor.edges: Edges.Bottom | Edges.Right - visible: PopupService.batteryMenuOpen + visible: false color: "transparent" implicitWidth: batGlass.implicitWidth implicitHeight: batGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: batPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: batPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: batMenu.visible = false + } + + Connections { + target: PopupService + function onBatteryMenuOpenChanged() { + if (PopupService.batteryMenuOpen) { + batPopOut.running = false + batMenu.visible = true + batPopIn.restart() + } else if (batMenu.visible) { + batPopIn.running = false + batPopOut.restart() + } + } + } + GlassPanel { id: batGlass implicitWidth: 300 implicitHeight: Math.min(450, batCardLayout.implicitHeight + 24) anchors.fill: parent + opacity: batMenu.animProgress + scale: 0.90 + 0.10 * batMenu.animProgress + transformOrigin: Item.TopRight + ColumnLayout { id: batCardLayout anchors.fill: parent @@ -2444,18 +2786,56 @@ GlassPanel { anchor.rect.x: Math.round(Math.max(10, Math.min(window.width - 175, root.x + root.activeTrayX + 12 - 87))) anchor.rect.y: 40 anchor.edges: Edges.Bottom | Edges.Left - visible: PopupService.trayMenuOpen + visible: false color: "transparent" implicitWidth: trayGlass.implicitWidth implicitHeight: trayGlass.implicitHeight + property real animProgress: 0.0 + + NumberAnimation on animProgress { + id: trayPopIn + running: false + to: 1.0 + duration: 220 + easing.type: Easing.OutBack + easing.overshoot: 1.15 + } + + NumberAnimation on animProgress { + id: trayPopOut + running: false + to: 0.0 + duration: 160 + easing.type: Easing.InQuad + onFinished: trayContextMenu.visible = false + } + + Connections { + target: PopupService + function onTrayMenuOpenChanged() { + if (PopupService.trayMenuOpen) { + trayPopOut.running = false + trayContextMenu.visible = true + trayPopIn.restart() + } else if (trayContextMenu.visible) { + trayPopIn.running = false + trayPopOut.restart() + } + } + } + GlassPanel { id: trayGlass implicitWidth: 170 implicitHeight: trayCol.implicitHeight + 16 anchors.fill: parent + opacity: trayContextMenu.animProgress + scale: 0.90 + 0.10 * trayContextMenu.animProgress + transformOrigin: Item.TopLeft + ColumnLayout { id: trayCol anchors.left: parent.left