Files
z-bar-qt/Modules/TrayMenu.qml
Zacharias-Brohn d56a0260fb formatter
2026-02-24 23:20:11 +01:00

393 lines
8.5 KiB
QML

pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Hyprland
import QtQml
import qs.Effects
import qs.Config
PanelWindow {
id: root
property color backgroundColor: DynamicColors.tPalette.m3surface
required property PanelWindow bar
property int biggestWidth: 0
property color disabledHighlightColor: DynamicColors.layer(DynamicColors.palette.m3primaryContainer, 0)
property color disabledTextColor: DynamicColors.layer(DynamicColors.palette.m3onSurface, 0)
property int entryHeight: 30
property alias focusGrab: grab.active
property color highlightColor: DynamicColors.tPalette.m3primaryContainer
property int menuItemCount: menuOpener.children.values.length
property var menuStack: []
property real scaleValue: 0
property color textColor: DynamicColors.palette.m3onSurface
required property point trayItemRect
required property QsMenuHandle trayMenu
signal finishedLoading
signal menuActionTriggered
function goBack() {
if (root.menuStack.length > 0) {
menuChangeAnimation.start();
root.biggestWidth = 0;
root.trayMenu = root.menuStack.pop();
listLayout.positionViewAtBeginning();
backEntry.visible = false;
}
}
function updateMask() {
root.mask.changed();
}
color: "transparent"
// onTrayMenuChanged: {
// listLayout.forceLayout();
// }
visible: false
mask: Region {
id: mask
item: menuRect
}
onMenuActionTriggered: {
if (root.menuStack.length > 0) {
backEntry.visible = true;
}
}
onVisibleChanged: {
if (!visible)
root.menuStack.pop();
backEntry.visible = false;
openAnim.start();
}
QsMenuOpener {
id: menuOpener
menu: root.trayMenu
}
anchors {
bottom: true
left: true
right: true
top: true
}
HyprlandFocusGrab {
id: grab
active: false
windows: [root]
onCleared: {
closeAnim.start();
}
}
SequentialAnimation {
id: menuChangeAnimation
ParallelAnimation {
NumberAnimation {
duration: MaterialEasing.standardTime / 2
easing.bezierCurve: MaterialEasing.expressiveEffects
from: 0
property: "x"
target: translateAnim
to: -listLayout.width / 2
}
NumberAnimation {
duration: MaterialEasing.standardTime / 2
easing.bezierCurve: MaterialEasing.standard
from: 1
property: "opacity"
target: columnLayout
to: 0
}
}
PropertyAction {
property: "menu"
target: columnLayout
}
ParallelAnimation {
NumberAnimation {
duration: MaterialEasing.standardTime / 2
easing.bezierCurve: MaterialEasing.standard
from: 0
property: "opacity"
target: columnLayout
to: 1
}
NumberAnimation {
duration: MaterialEasing.standardTime / 2
easing.bezierCurve: MaterialEasing.expressiveEffects
from: listLayout.width / 2
property: "x"
target: translateAnim
to: 0
}
}
}
ParallelAnimation {
id: closeAnim
onFinished: {
root.visible = false;
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: menuRect
to: 0
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
from: 1
property: "opacity"
targets: [menuRect, shadowRect]
to: 0
}
}
ParallelAnimation {
id: openAnim
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
from: 0
property: "implicitHeight"
target: menuRect
to: listLayout.contentHeight + (root.menuStack.length > 0 ? root.entryHeight + 10 : 10)
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
from: 0
property: "opacity"
targets: [menuRect, shadowRect]
to: 1
}
}
ShadowRect {
id: shadowRect
anchors.fill: menuRect
radius: menuRect.radius
}
Rectangle {
id: menuRect
clip: true
color: root.backgroundColor
implicitHeight: listLayout.contentHeight + (root.menuStack.length > 0 ? root.entryHeight + 10 : 10)
implicitWidth: listLayout.contentWidth + 10
radius: 8
x: Math.round(root.trayItemRect.x - (menuRect.implicitWidth / 2) + 11)
y: Math.round(root.trayItemRect.y - 5)
Behavior on implicitHeight {
NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Behavior on implicitWidth {
NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
ColumnLayout {
id: columnLayout
anchors.fill: parent
anchors.margins: 5
spacing: 0
transform: [
Translate {
id: translateAnim
x: 0
y: 0
}
]
ListView {
id: listLayout
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
contentHeight: contentItem.childrenRect.height
contentWidth: root.biggestWidth
model: menuOpener.children
spacing: 0
delegate: Rectangle {
id: menuItem
property var child: QsMenuOpener {
menu: menuItem.modelData
}
property bool containsMouseAndEnabled: mouseArea.containsMouse && menuItem.modelData.enabled
property bool containsMouseAndNotEnabled: mouseArea.containsMouse && !menuItem.modelData.enabled
required property int index
required property QsMenuEntry modelData
anchors.left: parent.left
anchors.right: parent.right
color: menuItem.modelData.isSeparator ? "#20FFFFFF" : containsMouseAndEnabled ? root.highlightColor : containsMouseAndNotEnabled ? root.disabledHighlightColor : "transparent"
height: menuItem.modelData.isSeparator ? 1 : root.entryHeight
radius: 4
visible: true
width: widthMetrics.width + (menuItem.modelData.icon ?? "" ? 30 : 0) + (menuItem.modelData.hasChildren ? 30 : 0) + 20
Behavior on color {
CAnim {
duration: 150
}
}
Component.onCompleted: {
var biggestWidth = root.biggestWidth;
var currentWidth = widthMetrics.width + (menuItem.modelData.icon ?? "" ? 30 : 0) + (menuItem.modelData.hasChildren ? 30 : 0) + 20;
if (currentWidth > biggestWidth) {
root.biggestWidth = currentWidth;
}
}
TextMetrics {
id: widthMetrics
text: menuItem.modelData.text
}
MouseArea {
id: mouseArea
acceptedButtons: Qt.LeftButton
anchors.fill: parent
hoverEnabled: true
preventStealing: true
propagateComposedEvents: true
onClicked: {
if (!menuItem.modelData.hasChildren) {
if (menuItem.modelData.enabled) {
menuItem.modelData.triggered();
closeAnim.start();
}
} else {
root.menuStack.push(root.trayMenu);
menuChangeAnimation.start();
root.biggestWidth = 0;
root.trayMenu = menuItem.modelData;
listLayout.positionViewAtBeginning();
root.menuActionTriggered();
}
}
}
RowLayout {
anchors.fill: parent
Text {
id: menuText
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.leftMargin: 10
color: menuItem.modelData.enabled ? root.textColor : root.disabledTextColor
text: menuItem.modelData.text
}
Image {
id: iconImage
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.maximumHeight: 20
Layout.maximumWidth: 20
Layout.rightMargin: 10
fillMode: Image.PreserveAspectFit
layer.enabled: true
source: menuItem.modelData.icon
sourceSize.height: height
sourceSize.width: width
layer.effect: ColorOverlay {
color: menuItem.modelData.enabled ? "white" : "gray"
}
}
Text {
id: textArrow
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.bottomMargin: 5
Layout.maximumHeight: 20
Layout.maximumWidth: 20
Layout.rightMargin: 10
color: menuItem.modelData.enabled ? "white" : "gray"
text: ""
visible: menuItem.modelData.hasChildren ?? false
}
}
}
}
Rectangle {
id: backEntry
Layout.fillWidth: true
Layout.preferredHeight: root.entryHeight
color: mouseAreaBack.containsMouse ? "#15FFFFFF" : "transparent"
radius: 4
visible: false
MouseArea {
id: mouseAreaBack
anchors.fill: parent
hoverEnabled: true
onClicked: {
root.goBack();
}
}
Text {
anchors.fill: parent
anchors.leftMargin: 10
color: "white"
text: "Back "
verticalAlignment: Text.AlignVCenter
}
}
}
}
}