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
required property SystemTrayItem item
property var menuHandle
implicitWidth: 22
implicitHeight: 22
@@ -46,14 +45,16 @@ MouseArea {
ImageAnalyser {
id: analyser
sourceItem: icon
rescaleSize: 20
rescaleSize: 22
}
TrayMenu {
id: trayMenu
menu: root.item.menu
menu: menuOpener
anchor.item: root
anchor.edges: Edges.Bottom
anchor.rect.x: 11
anchor.rect.y: 25
onVisibleChanged: {
if ( grab.active && !visible ) {
grab.active = false;
@@ -74,8 +75,17 @@ MouseArea {
if ( mouse.button === Qt.LeftButton ) {
root.item.activate();
} else if ( mouse.button === Qt.RightButton ) {
if ( trayMenu.menu != menuOpener ) {
trayMenu.menu = menuOpener;
}
trayMenu.visible = !trayMenu.visible;
grab.active = true;
}
}
QsMenuOpener {
id: menuOpener
menu: root.item?.menu
}
}
+126 -57
View File
@@ -10,75 +10,119 @@ PopupWindow {
id: root
signal menuActionTriggered()
required property QsMenuHandle menu
property int height: {
required property QsMenuOpener menu
property var menuStack: []
function calcHeight() {
let count = 0;
for (let i = 0; i < repeater.count; i++) {
if (!repeater.itemAt(i).modelData.isSeparator) count++;
let separatorCount = 0;
for (let i = 0; i < listLayout.count; i++) {
if (!listLayout.model.values[i].isSeparator) {
count++;
} else {
separatorCount++;
}
}
return count * (entryHeight + 3);
}
property int entryHeight: 30
property int maxWidth: 100
implicitWidth: Math.max(100, maxWidth + 20)
implicitHeight: height
color: "transparent"
anchor.margins {
left: -implicitWidth
if ( root.menuStack.length > 0 ) {
count++;
}
return (count * entryHeight) + ((count - 1) * 2) + (separatorCount * 3) + 10;
}
QsMenuOpener {
id: menuOpener
menu: root.menu
function calcWidth() {
let menuWidth = 0;
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 {
id: menuRect
anchors.fill: parent
color: "#90000000"
color: "#80151515"
radius: 8
border.color: "#10FFFFFF"
border.color: "#40FFFFFF"
ColumnLayout {
id: columnLayout
anchors.fill: parent
anchors.margins: 5
spacing: 2
Repeater {
id: repeater
model: menuOpener.children
Rectangle {
spacing: 0
ListView {
id: listLayout
Layout.fillWidth: true
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
required property QsMenuEntry modelData
property var child: QsMenuOpener {
menu: menuItem.modelData
}
property bool containsMouseAndEnabled: mouseArea.containsMouse && menuItem.modelData.enabled
property bool containsMouseAndNotEnabled: mouseArea.containsMouse && !menuItem.modelData.enabled
width: root.implicitWidth
Layout.maximumWidth: parent.width
Layout.fillHeight: true
height: root.entryHeight
color: modelData.isSeparator ? "transparent" : containsMouseAndEnabled ? "#15FFFFFF" : containsMouseAndNotEnabled ? "#08FFFFFF" : "transparent"
anchors.left: parent.left
anchors.right: parent.right
height: menuItem.modelData.isSeparator ? 1 : root.entryHeight
color: menuItem.modelData.isSeparator ? "#20FFFFFF" : containsMouseAndEnabled ? "#15FFFFFF" : containsMouseAndNotEnabled ? "#08FFFFFF" : "transparent"
radius: 4
visible: modelData.isSeparator ? false : 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
}
}
visible: true
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
preventStealing: true
propagateComposedEvents: true
propagateComposedEvents: true
acceptedButtons: Qt.LeftButton
onClicked: {
if ( !menuItem.modelData.hasChildren ) {
@@ -88,14 +132,8 @@ propagateComposedEvents: true
root.visible = false;
}
} else {
subMenuComponent.createObject( null, {
menu: menuItem.modelData,
anchor: {
item: menuItem,
edges: Edges.Right
},
visible: true
})
root.menuStack.push(root.menu);
root.menu = menuItem.child;
}
}
}
@@ -124,13 +162,44 @@ propagateComposedEvents: true
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 {}
}
}