diff --git a/Modules/MaterialEasing.qml b/Modules/MaterialEasing.qml new file mode 100644 index 0000000..846da9f --- /dev/null +++ b/Modules/MaterialEasing.qml @@ -0,0 +1,27 @@ +pragma Singleton +import Quickshell + +Singleton { + id: root + + // thanks to Soramane :> + // expressive curves => thanks end cutie ;) + readonly property list emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] + readonly property list emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] + readonly property int emphasizedAccelTime: 200 + readonly property list emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] + readonly property int emphasizedDecelTime: 400 + readonly property int emphasizedTime: 500 + readonly property list expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] + readonly property int expressiveDefaultSpatialTime: 500 + readonly property list expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] + readonly property int expressiveEffectsTime: 200 + readonly property list expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] + readonly property int expressiveFastSpatialTime: 350 + readonly property list standard: [0.2, 0, 0, 1, 1, 1] + readonly property list standardAccel: [0.3, 0, 1, 1, 1, 1] + readonly property int standardAccelTime: 200 + readonly property list standardDecel: [0, 0, 0, 1, 1, 1] + readonly property int standardDecelTime: 250 + readonly property int standardTime: 300 +} diff --git a/Modules/TrayItem.qml b/Modules/TrayItem.qml index c6350da..962692c 100644 --- a/Modules/TrayItem.qml +++ b/Modules/TrayItem.qml @@ -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 + } } diff --git a/Modules/TrayMenu.qml b/Modules/TrayMenu.qml index 207f979..b0918c5 100644 --- a/Modules/TrayMenu.qml +++ b/Modules/TrayMenu.qml @@ -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 {} - } }