import Quickshell import QtQuick import QtQuick.Layouts import qs.Services MouseArea { id: root anchors.centerIn: parent height: parent.implicitHeight width: musicRow.implicitWidth state: "popup-closed" hoverEnabled: true onEntered: state = "popup-open" property string fontFamily: "FiraCode Nerd Font" property int fontSize: 14 property int contractAnimDuration: 150 states: [ State { name: "popup-closed" PropertyChanges { target: popup; visible: false } PropertyChanges { target: musicRow; visible: true; spacing: 4 } PropertyChanges { target: songContainer; bottomRightRadius: 4; topRightRadius: 4 } PropertyChanges { target: cavaContainer; bottomLeftRadius: 4; topLeftRadius: 4 } PropertyChanges { target: song; opacity: 1 } PropertyChanges { target: cava; opacity: 1 } PropertyChanges { target: popupRect width: musicRow.width height: 36 opacity: 0 border.color: Flexoki.bg } }, State { name: "popup-open" PropertyChanges { target: popup; visible: true } PropertyChanges { target: musicRow; visible: false; spacing: -1 } PropertyChanges { target: songContainer; bottomRightRadius: 0; topRightRadius: 0 } PropertyChanges { target: cavaContainer; bottomLeftRadius: 0; topLeftRadius: 0 } PropertyChanges { target: song; opacity: 0 } PropertyChanges { target: cava; opacity: 0 } PropertyChanges { target: popupRect width: 700 height: 500 opacity: 1 border.color: Flexoki.ui2 } } ] transitions: [ Transition { from: "popup-closed" to: "popup-open" SequentialAnimation { // show immediately so mouseleave works PropertyAction { target: popup; property: "visible" } ParallelAnimation { NumberAnimation { target: songContainer properties: "bottomRightRadius,topRightRadius" duration: root.contractAnimDuration easing.type: Easing.InQuad } NumberAnimation { target: cavaContainer properties: "bottomLeftRadius,topLeftRadius" duration: root.contractAnimDuration easing.type: Easing.InQuad } NumberAnimation { target: musicRow property: "spacing" duration: root.contractAnimDuration easing.type: Easing.OutQuad } NumberAnimation { targets: [song, cava] property: "opacity" duration: root.contractAnimDuration easing.type: Easing.OutQuad } } PropertyAction { target: popupRect; property: "opacity" } PropertyAction { target: musicRow; property: "visible" } ParallelAnimation { NumberAnimation { target: popupRect property: "width" duration: 250 easing.type: Easing.OutQuad } SpringAnimation { target: popupRect property: "height" spring: 5 mass: 0.5 damping: 0.2 epsilon: 0.25 } ColorAnimation { target: popupRect property: "border.color" duration: 250 easing.type: Easing.OutQuad } } } }, Transition { // TODO: this transition flickers sometimes from: "popup-open" to: "popup-closed" SequentialAnimation { ParallelAnimation { NumberAnimation { target: popupRect property: "width" duration: 250 easing.type: Easing.OutQuad } NumberAnimation { target: popupRect property: "height" duration: 250 easing.type: Easing.OutQuad } ColorAnimation { target: popupRect property: "border.color" duration: 250 easing.type: Easing.OutQuad } } PropertyAction { target: musicRow; property: "visible" } PropertyAction { target: popup; properties: "visible" } PropertyAction { target: popupRect; property: "opacity" } ParallelAnimation { NumberAnimation { target: songContainer properties: "bottomRightRadius,topRightRadius" duration: root.contractAnimDuration easing.type: Easing.InQuad } NumberAnimation { target: cavaContainer properties: "bottomLeftRadius,topLeftRadius" duration: root.contractAnimDuration easing.type: Easing.InQuad } NumberAnimation { target: musicRow property: "spacing" duration: root.contractAnimDuration easing.type: Easing.OutQuad } NumberAnimation { targets: [song, cava] property: "opacity" duration: root.contractAnimDuration easing.type: Easing.OutQuad } } } }, ] PopupWindow { id: popup anchor.item: root anchor.rect.x: root.width / 2 - width / 2 anchor.rect.y: 0 implicitWidth: 1920 implicitHeight: 600 color: "transparent" visible: false Rectangle { id: popupRect anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top color: Flexoki.bg border.width: 2 radius: 18 MouseArea { anchors.fill: parent hoverEnabled: true onExited: root.state = "popup-closed" } RowLayout { id: popupContent anchors.fill: parent anchors.topMargin: 32 anchors.bottomMargin: 32 anchors.leftMargin: 100 anchors.rightMargin: 100 spacing: 60 Image { id: cover asynchronous: true source: "file://" + Song.coverPath sourceSize.width: 300 sourceSize.height: 300 width: 300 height: 300 Connections { target: Song function onCoverLoaded() { cover.source = "file://" + Song.coverPath } } } ColumnLayout { Text { Layout.alignment: Qt.AlignHCenter color: Flexoki.re text: Song.artist font.pixelSize: root.fontSize } Text { Layout.alignment: Qt.AlignHCenter color: Flexoki.tx text: Song.album font.pixelSize: root.fontSize } Text { Layout.alignment: Qt.AlignHCenter color: Flexoki.tx text: Song.title font.pixelSize: root.fontSize } } } } } Row { id: musicRow anchors.centerIn: parent height: parent.height spacing: 4 Rectangle { id: songContainer implicitWidth: song.implicitWidth + 16*2 height: parent.height color: Flexoki.bg bottomLeftRadius: 18 topLeftRadius: 18 bottomRightRadius: 4 topRightRadius: 4 Text { id: song anchors.centerIn: parent text: { if (!Song.file) return "Not playing" var text = Song.file if (Song.title) { if (Song.artist) { text = Song.artist + " – " + Song.title } else { text = Song.title } } if (text.length > 60) { text = text.substring(0, 60) + "…" } return text } color: Flexoki.tx font { family: root.fontFamily; pixelSize: root.fontSize } } } Rectangle { id: cavaContainer implicitWidth: cava.implicitWidth + 16*2 height: parent.height color: Flexoki.bg bottomLeftRadius: 4 topLeftRadius: 4 bottomRightRadius: 18 topRightRadius: 18 Row { id: cava anchors.centerIn: parent height: 15 + 4 spacing: 2 Repeater { model: Cava.heights delegate: Rectangle { required property int modelData y: 15 - modelData width: 8 height: modelData + 4 radius: 4 color: Flexoki.tx Behavior on height { NumberAnimation { duration: 1000 / 30; easing.type: Easing.Linear } } Behavior on y { NumberAnimation { duration: 1000 / 30; easing.type: Easing.Linear } } } } } } } }