feat: implement ease in (pop in) and pop out animations across all QuickShell popups

This commit is contained in:
Sho 2026-08-02 14:18:16 +09:00
parent e85c31fe29
commit f80ff3babd
5 changed files with 587 additions and 18 deletions

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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