tray menu sub-menus, animations, better width/height calculations

This commit is contained in:
Zacharias-Brohn
2025-11-03 01:17:05 +01:00
parent 63777ab876
commit 2da31b485e
3 changed files with 166 additions and 60 deletions
+27
View File
@@ -0,0 +1,27 @@
pragma Singleton
import Quickshell
Singleton {
id: root
// thanks to Soramane :>
// expressive curves => thanks end cutie ;)
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
readonly property int emphasizedAccelTime: 200
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
readonly property int emphasizedDecelTime: 400
readonly property int emphasizedTime: 500
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1]
readonly property int expressiveDefaultSpatialTime: 500
readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1]
readonly property int expressiveEffectsTime: 200
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1]
readonly property int expressiveFastSpatialTime: 350
readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]
readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]
readonly property int standardAccelTime: 200
readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]
readonly property int standardDecelTime: 250
readonly property int standardTime: 300
}
+13 -3
View File
@@ -14,7 +14,6 @@ MouseArea {
id: root id: root
required property SystemTrayItem item required property SystemTrayItem item
property var menuHandle
implicitWidth: 22 implicitWidth: 22
implicitHeight: 22 implicitHeight: 22
@@ -46,14 +45,16 @@ MouseArea {
ImageAnalyser { ImageAnalyser {
id: analyser id: analyser
sourceItem: icon sourceItem: icon
rescaleSize: 20 rescaleSize: 22
} }
TrayMenu { TrayMenu {
id: trayMenu id: trayMenu
menu: root.item.menu menu: menuOpener
anchor.item: root anchor.item: root
anchor.edges: Edges.Bottom anchor.edges: Edges.Bottom
anchor.rect.x: 11
anchor.rect.y: 25
onVisibleChanged: { onVisibleChanged: {
if ( grab.active && !visible ) { if ( grab.active && !visible ) {
grab.active = false; grab.active = false;
@@ -74,8 +75,17 @@ MouseArea {
if ( mouse.button === Qt.LeftButton ) { if ( mouse.button === Qt.LeftButton ) {
root.item.activate(); root.item.activate();
} else if ( mouse.button === Qt.RightButton ) { } else if ( mouse.button === Qt.RightButton ) {
if ( trayMenu.menu != menuOpener ) {
trayMenu.menu = menuOpener;
}
trayMenu.visible = !trayMenu.visible; trayMenu.visible = !trayMenu.visible;
grab.active = true; grab.active = true;
} }
} }
QsMenuOpener {
id: menuOpener
menu: root.item?.menu
}
} }
+126 -57
View File
@@ -10,75 +10,119 @@ PopupWindow {
id: root id: root
signal menuActionTriggered() signal menuActionTriggered()
required property QsMenuHandle menu required property QsMenuOpener menu
property int height: { property var menuStack: []
function calcHeight() {
let count = 0; let count = 0;
for (let i = 0; i < repeater.count; i++) { let separatorCount = 0;
if (!repeater.itemAt(i).modelData.isSeparator) count++; for (let i = 0; i < listLayout.count; i++) {
if (!listLayout.model.values[i].isSeparator) {
count++;
} else {
separatorCount++;
}
} }
return count * (entryHeight + 3); if ( root.menuStack.length > 0 ) {
} count++;
property int entryHeight: 30 }
property int maxWidth: 100 return (count * entryHeight) + ((count - 1) * 2) + (separatorCount * 3) + 10;
implicitWidth: Math.max(100, maxWidth + 20)
implicitHeight: height
color: "transparent"
anchor.margins {
left: -implicitWidth
} }
QsMenuOpener { function calcWidth() {
id: menuOpener let menuWidth = 0;
menu: root.menu for ( let i = 0; i < listLayout.count; i++ ) {
if ( !listLayout.model.values[i].isSeparator ) {
let entry = listLayout.model.values[i];
tempMetrics.text = entry.text;
let textWidth = tempMetrics.width + 20 + (entry.icon ?? "" ? 30 : 0) + (entry.hasChildren ? 30 : 0);
if ( textWidth > menuWidth ) {
menuWidth = textWidth;
}
}
}
return menuWidth;
}
function goBack() {
if ( root.menuStack.length > 0 ) {
root.menu = root.menuStack.pop();
}
}
onVisibleChanged: {
if ( visible ) {
root.menuStack = [];
}
}
TextMetrics {
id: tempMetrics
text: ""
}
property int height: calcHeight()
property int entryHeight: 30
property int maxWidth: calcWidth()
implicitWidth: maxWidth + 20
implicitHeight: height
color: "transparent"
anchor.gravity: Edges.Bottom
Behavior on implicitHeight {
NumberAnimation {
duration: MaterialEasing.standardTime
easing.bezierCurve: MaterialEasing.standard
}
}
Behavior on implicitWidth {
NumberAnimation {
duration: MaterialEasing.standardTime
easing.bezierCurve: MaterialEasing.standard
}
} }
Rectangle { Rectangle {
id: menuRect id: menuRect
anchors.fill: parent anchors.fill: parent
color: "#90000000" color: "#80151515"
radius: 8 radius: 8
border.color: "#10FFFFFF" border.color: "#40FFFFFF"
ColumnLayout { ColumnLayout {
id: columnLayout
anchors.fill: parent anchors.fill: parent
anchors.margins: 5 anchors.margins: 5
spacing: 2 spacing: 0
Repeater { ListView {
id: repeater id: listLayout
model: menuOpener.children Layout.fillWidth: true
Rectangle { Layout.preferredHeight: root.calcHeight() - ( root.menuStack.length > 0 ? root.entryHeight + 10 : 0 )
spacing: 2
model: ScriptModel {
values: [...root.menu?.children.values]
}
delegate: Rectangle {
id: menuItem id: menuItem
required property QsMenuEntry modelData
property var child: QsMenuOpener {
menu: menuItem.modelData
}
property bool containsMouseAndEnabled: mouseArea.containsMouse && menuItem.modelData.enabled property bool containsMouseAndEnabled: mouseArea.containsMouse && menuItem.modelData.enabled
property bool containsMouseAndNotEnabled: mouseArea.containsMouse && !menuItem.modelData.enabled property bool containsMouseAndNotEnabled: mouseArea.containsMouse && !menuItem.modelData.enabled
width: root.implicitWidth width: root.implicitWidth
Layout.maximumWidth: parent.width anchors.left: parent.left
Layout.fillHeight: true anchors.right: parent.right
height: root.entryHeight height: menuItem.modelData.isSeparator ? 1 : root.entryHeight
color: modelData.isSeparator ? "transparent" : containsMouseAndEnabled ? "#15FFFFFF" : containsMouseAndNotEnabled ? "#08FFFFFF" : "transparent" color: menuItem.modelData.isSeparator ? "#20FFFFFF" : containsMouseAndEnabled ? "#15FFFFFF" : containsMouseAndNotEnabled ? "#08FFFFFF" : "transparent"
radius: 4 radius: 4
visible: modelData.isSeparator ? false : true visible: true
required property QsMenuEntry modelData
TextMetrics {
id: textMetrics
font: menuText.font
text: menuItem.modelData.text
}
Component.onCompleted: {
// Measure text width to determine maximumWidth
var textWidth = textMetrics.width + 20 + (iconImage.source ? iconImage.width + 10 : 0);
if ( textWidth > 0 && textWidth > root.maxWidth ) {
root.maxWidth = textWidth
}
}
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
preventStealing: true preventStealing: true
propagateComposedEvents: true propagateComposedEvents: true
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onClicked: { onClicked: {
if ( !menuItem.modelData.hasChildren ) { if ( !menuItem.modelData.hasChildren ) {
@@ -88,14 +132,8 @@ propagateComposedEvents: true
root.visible = false; root.visible = false;
} }
} else { } else {
subMenuComponent.createObject( null, { root.menuStack.push(root.menu);
menu: menuItem.modelData, root.menu = menuItem.child;
anchor: {
item: menuItem,
edges: Edges.Right
},
visible: true
})
} }
} }
} }
@@ -124,13 +162,44 @@ propagateComposedEvents: true
color: menuItem.modelData.enabled ? "white" : "gray" color: menuItem.modelData.enabled ? "white" : "gray"
} }
} }
Text {
id: textArrow
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.rightMargin: 10
Layout.bottomMargin: 5
Layout.maximumWidth: 20
Layout.maximumHeight: 20
text: ""
color: menuItem.modelData.enabled ? "white" : "gray"
visible: menuItem.modelData.hasChildren ?? false
}
} }
} }
} }
Rectangle {
visible: true
Layout.fillWidth: true
Layout.preferredHeight: root.entryHeight
color: mouseAreaBack.containsMouse ? "#15FFFFFF" : "transparent"
radius: 4
MouseArea {
id: mouseAreaBack
anchors.fill: parent
hoverEnabled: true
onClicked: {
root.goBack();
}
}
Text {
anchors.fill: parent
anchors.leftMargin: 10
verticalAlignment: Text.AlignVCenter
text: "Back "
color: "white"
}
}
} }
} }
Component {
id: subMenuComponent
SubMenu {}
}
} }