diff --git a/Modules/CustomTrayMenu.qml b/Modules/CustomTrayMenu.qml deleted file mode 100644 index 7ede033..0000000 --- a/Modules/CustomTrayMenu.qml +++ /dev/null @@ -1,47 +0,0 @@ -// CustomTrayMenu.qml -pragma ComponentBehavior: Bound -import QtQuick -import Quickshell -import QtQuick.Window // for Window, flags -import qs.Modules - -PopupWindow { - id: popup - color: "#00202020" - - required property QsMenuOpener trayMenu - - Column { - id: contentColumn - anchors.fill: parent - spacing: 4 - Repeater { - id: repeater - model: popup.trayMenu.children - Row { - id: entryRow - anchors.fill: parent - property var entry: modelData - MouseArea { - anchors.fill: parent - onClicked: { - if (entryRow.entry.triggered) { - entryRow.entry.triggered() - } - popup.visible = false - } - } - Image { - source: entryRow.entry.icon - width: 20; height: 20 - visible: entryRow.entry.icon !== "" - } - Text { - text: entryRow.entry.text - color: "black" - anchors.verticalCenter: parent.verticalCenter - } - } - } - } -} diff --git a/Modules/Resources.qml b/Modules/Resources.qml index 0b2a74c..93fc667 100644 --- a/Modules/Resources.qml +++ b/Modules/Resources.qml @@ -49,7 +49,7 @@ Item { Layout.alignment: Qt.AlignVCenter font.family: "Material Symbols Rounded" font.pixelSize: 16 - text: "󰢮" + text: "\ue30f" color: "#ffffff" } diff --git a/Modules/SubMenu.qml b/Modules/SubMenu.qml deleted file mode 100644 index 3f3d014..0000000 --- a/Modules/SubMenu.qml +++ /dev/null @@ -1,91 +0,0 @@ -pragma ComponentBehavior: Bound - -import Quickshell -import QtQuick -import QtQuick.Layouts -import Quickshell.Hyprland - -PopupWindow { - id: root - required property QsMenuHandle menu - property int height: entryCount * ( entryHeight + 3 ) - property int entryHeight: 30 - property int entryCount: 0 - implicitWidth: 300 - implicitHeight: height - color: "transparent" - - QsMenuOpener { - id: menuOpener - menu: root.menu - } - - HyprlandFocusGrab { - id: grab - windows: [ root ] - onCleared: { - root.visible = false; - } - } - - onVisibleChanged: { - grab.active = root.visible; - } - Rectangle { - id: menuRect - anchors.fill: parent - color: "#90000000" - radius: 8 - border.color: "#10FFFFFF" - ColumnLayout { - id: columnLayout - anchors.fill: parent - anchors.margins: 5 - spacing: 2 - Repeater { - id: repeater - model: menuOpener.children - Rectangle { - id: menuItem - width: root.implicitWidth - Layout.maximumWidth: parent.width - Layout.fillHeight: true - height: root.entryHeight - color: mouseArea.containsMouse && !modelData.isSeparator ? "#15FFFFFF" : "transparent" - radius: 4 - visible: modelData.isSeparator ? false : true - required property QsMenuEntry modelData - - Component.onCompleted: { - if ( !modelData.isSeparator ) { - root.entryCount += 1; - } - } - - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - preventStealing: true - propagateComposedEvents: true - acceptedButtons: Qt.LeftButton - onClicked: { - if ( !menuItem.modelData.hasChildren ) { - menuItem.modelData.triggered(); - root.visible = false; - } - } - } - - Text { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - text: menuItem.modelData.text - color: "white" - } - } - } - } - } -} diff --git a/Modules/SwayNC.qml b/Modules/SwayNC.qml index d047a9e..dfb2773 100644 --- a/Modules/SwayNC.qml +++ b/Modules/SwayNC.qml @@ -48,7 +48,7 @@ Item { Process { id: swayncProcess - command: ["swaync-client", "-t", "-sw"] + command: ["sh", "-c", "qs -p /home/zach/GitProjects/z-bar-qt/notification-test/shell.qml ipc call root showCenter"] running: false } diff --git a/Modules/TrayItem.qml b/Modules/TrayItem.qml index 1d4616b..b6bb4c0 100644 --- a/Modules/TrayItem.qml +++ b/Modules/TrayItem.qml @@ -3,8 +3,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Effects -import Caelestia import Quickshell +import Quickshell.DBusMenu import Quickshell.Widgets import Quickshell.Hyprland import Quickshell.Services.SystemTray @@ -38,35 +38,30 @@ MouseArea { smooth: false asynchronous: true - ImageAnalyser { - id: analyser - sourceItem: icon - rescaleSize: 22 - } - TrayMenu { id: trayMenu - menu: menuOpener + trayMenu: root.item?.menu trayItemRect: root.globalPos bar: root.bar } } + Connections { + target: trayMenu + function onVisibleChanged() { + if ( !trayMenu.visible ) { + trayMenu.trayMenu = null; + } + } + } + onClicked: { if ( mouse.button === Qt.LeftButton ) { root.item.activate(); } else if ( mouse.button === Qt.RightButton ) { - if ( trayMenu.menu != menuOpener ) { - trayMenu.menu = menuOpener; - } + trayMenu.trayMenu = root.item?.menu; trayMenu.visible = !trayMenu.visible; - console.log(root.x); + trayMenu.focusGrab = true; } } - - QsMenuOpener { - id: menuOpener - - menu: root.item?.menu - } } diff --git a/Modules/TrayMenu.qml b/Modules/TrayMenu.qml index c96d980..73f4366 100644 --- a/Modules/TrayMenu.qml +++ b/Modules/TrayMenu.qml @@ -1,24 +1,34 @@ pragma ComponentBehavior: Bound import Quickshell +import Quickshell.DBusMenu import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell.Hyprland +import QtQml PanelWindow { id: root signal menuActionTriggered() - required property QsMenuOpener menu + required property QsMenuHandle trayMenu required property point trayItemRect required property PanelWindow bar property var menuStack: [] property real scaleValue: 0 - - property int height: calcSize("h") + property alias focusGrab: grab.active property int entryHeight: 30 - property int maxWidth: calcSize("w") + property int biggestWidth: 0 + + QsMenuOpener { + id: menuOpener + menu: root.trayMenu + } + + // onTrayMenuChanged: { + // listLayout.forceLayout(); + // } visible: false color: "transparent" @@ -29,61 +39,29 @@ PanelWindow { bottom: true } - function calcSize(String) { - if ( String === "w" ) { - 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; - } - if ( String === "h" ) { - let count = 0; - let separatorCount = 0; - for (let i = 0; i < listLayout.count; i++) { - if (!listLayout.model.values[i].isSeparator) { - count++; - } else { - separatorCount++; - } - } - if ( root.menuStack.length > 0 ) { - backEntry.visible = true; - count++; - } - return (count * entryHeight) + ((count - 1) * 2) + (separatorCount * 3) + 10; - } - } + mask: Region { id: mask; item: menuRect } function goBack() { if ( root.menuStack.length > 0 ) { - root.menu = root.menuStack.pop(); + menuChangeAnimation.start(); + root.biggestWidth = 0; + root.trayMenu = root.menuStack.pop(); + listLayout.positionViewAtBeginning(); backEntry.visible = false; } } + function updateMask() { + root.mask.changed(); + } + onVisibleChanged: { - if ( !visible ) { - goBack(); - } else { + if ( visible ) { scaleValue = 0; scaleAnimation.start(); } } - TextMetrics { - id: tempMetrics - text: "" - } - - NumberAnimation { id: scaleAnimation target: root @@ -92,90 +70,85 @@ PanelWindow { to: 1 duration: 150 easing.type: Easing.OutCubic + onStopped: { + root.updateMask(); + } } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: { + HyprlandFocusGrab { + id: grab + windows: [ root ] + active: false + onCleared: { root.visible = false; } } - Behavior on menu { - SequentialAnimation { - 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 - } + SequentialAnimation { + id: menuChangeAnimation + ParallelAnimation { + NumberAnimation { + duration: MaterialEasing.standardTime / 2 + easing.bezierCurve: MaterialEasing.expressiveEffects + from: 0 + property: "x" + target: translateAnim + to: -listLayout.width / 2 } - PropertyAction { - property: "menu" + 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 } - 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 - } + NumberAnimation { + duration: MaterialEasing.standardTime / 2 + easing.bezierCurve: MaterialEasing.expressiveEffects + from: listLayout.width / 2 + property: "x" + target: translateAnim + to: 0 } } } + onMenuActionTriggered: { + if ( root.menuStack.length > 0 ) { + backEntry.visible = true; + } + } + Rectangle { id: menuRect - x: root.trayItemRect.x - ( menuRect.implicitWidth / 2 ) + 11 - y: root.trayItemRect.y - 5 - implicitHeight: root.height - implicitWidth: root.maxWidth + 20 + x: Math.round( root.trayItemRect.x - ( menuRect.implicitWidth / 2 ) + 11 ) + y: Math.round( root.trayItemRect.y - 5 ) + implicitWidth: listLayout.contentWidth + 10 + implicitHeight: listLayout.contentHeight + ( root.menuStack.length > 0 ? root.entryHeight + 10 : 10 ) color: "#80151515" radius: 8 border.color: "#40FFFFFF" clip: true - Behavior on implicitHeight { - NumberAnimation { - duration: MaterialEasing.standardTime - easing.bezierCurve: MaterialEasing.standard - } - } - - Behavior on implicitWidth { - NumberAnimation { - duration: MaterialEasing.standardTime - easing.bezierCurve: MaterialEasing.standard - } - } - transform: [ Scale { origin.x: menuRect.width / 2 @@ -185,6 +158,19 @@ PanelWindow { } ] + Behavior on implicitWidth { + NumberAnimation { + duration: MaterialEasing.standardTime + easing.bezierCurve: MaterialEasing.standard + } + } + + Behavior on implicitHeight { + NumberAnimation { + duration: MaterialEasing.standardTime + easing.bezierCurve: MaterialEasing.standard + } + } ColumnLayout { id: columnLayout @@ -201,31 +187,11 @@ PanelWindow { ListView { id: listLayout Layout.fillWidth: true - Layout.preferredHeight: root.height - ( root.menuStack.length > 0 ? root.entryHeight + 10 : 0 ) + Layout.preferredHeight: contentHeight spacing: 2 - model: ScriptModel { - values: [...root.menu?.children.values] - } - - add: Transition { - NumberAnimation { - property: "x" - from: listLayout.width - to: 0 - duration: 200 - easing.type: Easing.OutCubic - } - } - - move: Transition { - NumberAnimation { - property: "x" - from: 0 - to: -listLayout.width - duration: 200 - easing.type: Easing.InCubic - } - } + contentWidth: root.biggestWidth + contentHeight: contentItem.childrenRect.height + model: menuOpener.children delegate: Rectangle { id: menuItem @@ -235,7 +201,7 @@ PanelWindow { } property bool containsMouseAndEnabled: mouseArea.containsMouse && menuItem.modelData.enabled property bool containsMouseAndNotEnabled: mouseArea.containsMouse && !menuItem.modelData.enabled - width: root.implicitWidth + width: widthMetrics.width + (menuItem.modelData.icon ?? "" ? 30 : 0) + (menuItem.modelData.hasChildren ? 30 : 0) + 20 anchors.left: parent.left anchors.right: parent.right height: menuItem.modelData.isSeparator ? 1 : root.entryHeight @@ -243,6 +209,19 @@ PanelWindow { radius: 4 visible: true + 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 anchors.fill: parent @@ -254,12 +233,15 @@ PanelWindow { if ( !menuItem.modelData.hasChildren ) { if ( menuItem.modelData.enabled ) { menuItem.modelData.triggered(); - root.menuActionTriggered(); root.visible = false; } } else { - root.menuStack.push(root.menu); - root.menu = menuItem.child; + root.menuStack.push(root.trayMenu); + menuChangeAnimation.start(); + root.biggestWidth = 0; + root.trayMenu = menuItem.modelData; + listLayout.positionViewAtBeginning(); + root.menuActionTriggered(); } } } diff --git a/notification-test/Notif.qml b/notification-test/Notif.qml new file mode 100644 index 0000000..9e749b2 --- /dev/null +++ b/notification-test/Notif.qml @@ -0,0 +1,155 @@ +import Quickshell +import Quickshell.Widgets +import Quickshell.Wayland +import QtQuick.Layouts +import QtQuick +import Quickshell.Services.Notifications + +PanelWindow { + id: root + color: "transparent" + anchors { + top: true + right: true + left: true + bottom: true + } + mask: Region { item: backgroundRect } + exclusionMode: ExclusionMode.Ignore + WlrLayershell.layer: WlrLayer.Overlay + required property Notification notif + required property int centerX + required property list notifIndex + property int index: notifIndex.indexOf(notif.id) + signal notifDestroy() + + Component.onCompleted: { + openAnim.start(); + } + + Timer { + id: timeout + interval: 5000 + onTriggered: { + closeAnim.start(); + } + } + + NumberAnimation { + id: openAnim + target: backgroundRect + property: "x" + from: root.centerX + to: root.centerX - backgroundRect.implicitWidth - 20 + duration: 200 + easing.type: Easing.InOutQuad + onStopped: { timeout.start(); } + } + + NumberAnimation { + id: closeAnim + target: backgroundRect + property: "x" + from: root.centerX - backgroundRect.implicitWidth - 20 + to: root.centerX + duration: 200 + easing.type: Easing.InOutQuad + onStopped: { + root.destroy(); + root.notifDestroy(); + } + } + + Rectangle { + id: backgroundRect + implicitWidth: 400 + implicitHeight: 80 + x: root.centerX - implicitWidth - 20 + y: 34 + 20 + ( root.index * ( implicitHeight + 10 )) + color: "#801a1a1a" + border.color: "#555555" + radius: 8 + + Behavior on y { + NumberAnimation { + duration: 200 + easing.type: Easing.InOutQuad + } + } + + RowLayout { + anchors.fill: parent + anchors.margins: 8 + IconImage { + source: root.notif.image + Layout.preferredWidth: 64 + Layout.preferredHeight: 64 + Layout.alignment: Qt.AlignHCenter | Qt.AlignLeft + visible: root.notif.image !== "" + } + + ColumnLayout { + Layout.fillWidth: true + Layout.leftMargin: root.notif.image !== "" ? 0 : 16 + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + + Text { + text: root.notif.appName + color: "white" + font.bold: true + font.pointSize: 12 + elide: Text.ElideRight + wrapMode: Text.NoWrap + Layout.fillWidth: true + } + + Text { + text: root.notif.summary + color: "white" + font.pointSize: 10 + elide: Text.ElideRight + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + Text { + text: root.notif.body + color: "#dddddd" + font.pointSize: 8 + elide: Text.ElideRight + wrapMode: Text.WordWrap + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + + Rectangle { + anchors.right: parent.right + anchors.top: parent.top + anchors.rightMargin: 6 + anchors.topMargin: 6 + width: 18 + height: 18 + color: closeArea.containsMouse ? "#FF6077" : "transparent" + radius: 9 + + Text { + anchors.centerIn: parent + text: "✕" + color: closeArea.containsMouse ? "white" : "#888888" + font.pointSize: 12 + } + + MouseArea { + id: closeArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + root.notif.dismiss(); + root.visible = false; + } + } + } + } +} diff --git a/notification-test/NotificationCenter.qml b/notification-test/NotificationCenter.qml new file mode 100644 index 0000000..8f19933 --- /dev/null +++ b/notification-test/NotificationCenter.qml @@ -0,0 +1,420 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import Quickshell.Hyprland +import Quickshell.Widgets +import Quickshell.Io +import QtQuick.Layouts +import QtQuick.Controls.FluentWinUI3 +import QtQuick.Effects +import QtQuick +import Quickshell.Services.Notifications + +PanelWindow { + id: root + color: "transparent" + anchors { + top: true + right: true + left: true + bottom: true + } + required property list notifications + property bool centerShown: false + property alias posX: backgroundRect.x + property alias doNotDisturb: dndSwitch.checked + property alias groupedData: groupedData + visible: false + + IpcHandler { + id: ipcHandler + target: "root" + + function showCenter(): void { root.centerShown = !root.centerShown; } + } + + onVisibleChanged: { + if ( root.visible ) { + showAnimation.start(); + } + } + + onCenterShownChanged: { + if ( !root.centerShown ) { + closeAnimation.start(); + closeTimer.start(); + } else { + root.visible = true; + } + } + + Timer { + id: closeTimer + interval: 300 + onTriggered: { + root.visible = false; + } + } + + NumberAnimation { + id: showAnimation + target: backgroundRect + property: "x" + from: Screen.width + to: Screen.width - backgroundRect.implicitWidth - 10 + duration: 300 + easing.type: Easing.OutCubic + } + + NumberAnimation { + id: closeAnimation + target: backgroundRect + property: "x" + from: Screen.width - backgroundRect.implicitWidth - 10 + to: Screen.width + duration: 300 + easing.type: Easing.OutCubic + } + + ListModel { + id: groupedData + property int totalCount: 0 + property var groupMap: ({}) + + function mapGroups() { + groupedData.groupMap = {}; + for ( var i = 0; i < groupedData.count; i++ ) { + var name = get(i).name; + groupMap[ name ] = i; + } + } + + function updateCount() { + var count = 0; + for ( var i = 0; i < groupedData.count; i++ ) { + count += get(i).notifications.count; + } + totalCount = count; + } + + function ensureGroup(appName) { + for ( var i = 0; i < count; i++ ) { + if ( get(i).name === appName ) { + return get(i).notifications; + } + } + var model = Qt.createQmlObject('import QtQuick 2.0; ListModel {}', root); + append({ name: appName, notifications: model }); + mapGroups(); + return model; + } + + function addNotification(notif) { + var appName = notif.appName + var model = ensureGroup(appName); + model.insert(0, notif); + updateCount(); + } + + function removeNotification(notif) { + var appName = notif.appName || "Unknown"; + var group = get( groupMap[ appName ]); + if ( group.name === appName ) { + root.notifications[ idMap[ notif.id ]].dismiss(); + if ( group.notifications.count === 0 ) { + remove( groupMap[ appName ], 1 ); + mapGroups(); + } + } + updateCount(); + } + + function removeGroup(notif) { + var appName = notif.appName || "Unknown"; + var group = get(groupMap[ appName ]); + if ( group.name === appName ) { + for ( var i = 0; i < group.notifications.count; i++ ) { + var item = group.notifications.get( i ); + item.dismiss(); + } + remove( groupMap[ appName ], 1 ); + updateCount(); + mapGroups(); + } + } + + function resetGroups(notifications) { + groupedData.clear(); + for (var i = 0; i < root.notifications.length; i++) { + addNotification(root.notifications[i]); + } + updateCount(); + } + + Component.onCompleted: { + resetGroups(root.notifications) + } + } + + Connections { + target: root + function onNotificationsChanged() { + if ( root.notifications.length > groupedData.totalCount ) { + groupedData.addNotification( root.notifications[ root.notifications.length - 1 ] ); + groupedData.mapGroups(); + console.log(root.notifications) + } + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onClicked: { + if ( root.centerShown ) { + root.centerShown = false; + console.log("groups", groupedData.count); + console.log(root.notifications) + } + } + } + + Rectangle { + id: backgroundRect + y: 10 + x: Screen.width + implicitWidth: 400 + implicitHeight: root.height - 20 + color: "#801a1a1a" + radius: 8 + border.color: "#555555" + border.width: 1 + clip: true + ColumnLayout { + id: mainLayout + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + + RowLayout { + Layout.preferredHeight: 30 + Layout.fillWidth: true + Text { + text: "Notifications" + color: "white" + font.bold: true + font.pointSize: 16 + Layout.fillWidth: true + } + + Switch { + id: dndSwitch + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + text: "Do Not Disturb" + } + } + + Rectangle { + color: "#333333" + Layout.preferredHeight: 1 + Layout.fillWidth: true + } + + Column { + id: notificationColumn + Layout.fillHeight: true + Layout.fillWidth: true + // width: mainLayout.width + spacing: 10 + clip: true + + move: Transition { + NumberAnimation { + properties: "y,x" + duration: 200 + easing.type: Easing.InOutQuad + } + } + + add: Transition { + NumberAnimation { + properties: "y,x" + duration: 200 + easing.type: Easing.InOutQuad + } + } + + ListView { + id: groupListView + model: groupedData + spacing: 10 + width: 400 + height: Math.min(contentHeight, notificationColumn.height) + // Layout.fillWidth: true + // Layout.fillHeight: true + + delegate: ListView { + required property var modelData + required property int index + property bool isExpanded: false + property bool isExpandedAnim: false + id: listView + visible: true + property ListModel notificationsModel: modelData.notifications + model: notificationsModel + width: notificationColumn.width + implicitHeight: listView.isExpandedAnim ? contentHeight : 80 + clip: true + + spacing: 10 + + onIsExpandedChanged: { + if ( !isExpanded ) { + collapseAnim.start(); + } else { + expandAnim.start(); + } + } + NumberAnimation { + id: collapseAnim + target: listView + property: "implicitHeight" + to: 80 + from: listView.contentHeight + duration: 80 + easing.type: Easing.InOutQuad + onStopped: { + listView.isExpandedAnim = listView.isExpanded; + } + } + + NumberAnimation { + id: expandAnim + target: listView + property: "implicitHeight" + from: 80 + to: listView.contentHeight + duration: 80 + easing.type: Easing.InOutQuad + onStopped: { + listView.isExpandedAnim = listView.isExpanded; + } + } + + Behavior on y { + NumberAnimation { + duration: 200 + easing.type: Easing.InOutQuad + } + } + + displaced: Transition { + NumberAnimation { + properties: "y,x" + duration: 200 + easing.type: Easing.InOutQuad + } + } + + delegate: Rectangle { + id: notificationItem + required property var modelData + required property int index + width: listView.width + height: 80 + color: "#801a1a1a" + border.color: "#555555" + border.width: 1 + radius: 8 + clip: true + visible: true + opacity: 1 + + MouseArea { + anchors.fill: parent + onClicked: { + listView.isExpanded ? ( notificationItem.index === 0 ? listView.isExpanded = false : null ) : listView.isExpanded = true; + } + } + + RowLayout { + anchors.fill: parent + anchors.margins: 8 + spacing: 10 + + IconImage { + source: notificationItem.modelData.image + Layout.preferredWidth: 48 + Layout.preferredHeight: 48 + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + } + + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 4 + + Text { + text: notificationItem.modelData.appName + color: "white" + font.bold: true + font.pointSize: 12 + elide: Text.ElideRight + Layout.fillWidth: true + } + + Text { + text: notificationItem.modelData.summary + color: "white" + font.pointSize: 10 + elide: Text.ElideRight + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + } + + Text { + text: notificationItem.modelData.body + color: "#dddddd" + font.pointSize: 8 + elide: Text.ElideRight + wrapMode: Text.WordWrap + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignVCenter + } + } + } + + Rectangle { + anchors.right: parent.right + anchors.top: parent.top + anchors.rightMargin: 6 + anchors.topMargin: 6 + width: 18 + height: 18 + color: closeArea.containsMouse ? "#FF6077" : "transparent" + radius: 9 + + Text { + anchors.centerIn: parent + text: "✕" + color: closeArea.containsMouse ? "white" : "#888888" + font.pointSize: 12 + } + + MouseArea { + id: closeArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + root.notifications[modelData].dismiss(); + } + } + } + } + } + } + } + } + } +} diff --git a/notification-test/shell.qml b/notification-test/shell.qml new file mode 100644 index 0000000..c012fff --- /dev/null +++ b/notification-test/shell.qml @@ -0,0 +1,40 @@ +pragma ComponentBehavior: Bound +import Quickshell +import Quickshell.Services.Notifications +import QtQuick + +Scope { + id: root + property list notifIds: [] + NotificationServer { + id: notificationServer + imageSupported: true + actionsSupported: true + persistenceSupported: true + bodyImagesSupported: true + bodySupported: true + onNotification: { + notification.tracked = true; + notification.receivedTime = Date.now(); + root.notifIds.push(notification.id); + notificationCenter.groupedData.addNotification(notification); + notificationComponent.createObject(root, { notif: notification, visible: !notificationCenter.doNotDisturb }); + } + } + + Component { + id: notificationComponent + Notif { + centerX: notificationCenter.posX + notifIndex: root.notifIds + onNotifDestroy: { + root.notifIds.shift(); + } + } + } + + NotificationCenter { + id: notificationCenter + notifications: notificationServer.trackedNotifications.values + } +} diff --git a/popout-test/modules/GetIcons.qml b/popout-test/modules/GetIcons.qml deleted file mode 100644 index 293adc9..0000000 --- a/popout-test/modules/GetIcons.qml +++ /dev/null @@ -1,16 +0,0 @@ -pragma Singleton - -import Quickshell -import Quickshell.Services.Notifications - -Singleton { - id: root - - function getTrayIcon(id: string, icon: string): string { - if (icon.includes("?path=")) { - const [name, path] = icon.split("?path="); - icon = Qt.resolvedUrl(`${path}/${name.slice(name.lastIndexOf("/") + 1)}`); - } - return icon; - } -} diff --git a/popout-test/modules/Popout.qml b/popout-test/modules/Popout.qml deleted file mode 100644 index 8484282..0000000 --- a/popout-test/modules/Popout.qml +++ /dev/null @@ -1,50 +0,0 @@ -pragma ComponentBehavior: Bound - -import Quickshell -import QtQuick -import QtQuick.Layouts - -PopupWindow { - id: root - required property QsMenuHandle menu - property int height: menuOpener.children.values.length * entryHeight - property int entryHeight: 25 - implicitWidth: 300 - implicitHeight: height - - QsMenuOpener { - id: menuOpener - menu: root.menu - } - - ColumnLayout { - anchors.fill: parent - spacing: 0 - Repeater { - model: menuOpener.children - Rectangle { - id: menuItem - width: root.implicitWidth - height: modelData.isSeparator ? 5 : root.entryHeight - color: mouseArea.containsMouse ? "#22000000" : "transparent" - required property QsMenuEntry modelData - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.LeftButton - onClicked: { - menuItem.modelData.triggered(); - root.visible = false; - } - Text { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - text: menuItem.modelData.text - } - } - } - } - } -} diff --git a/popout-test/shell.qml b/popout-test/shell.qml deleted file mode 100644 index 3235e61..0000000 --- a/popout-test/shell.qml +++ /dev/null @@ -1,94 +0,0 @@ -import Quickshell -import Quickshell.Wayland -import Quickshell.Hyprland -import Quickshell.Services.SystemTray -import Quickshell.Widgets -import Caelestia -import QtQuick -import QtQuick.Layouts -import qs.modules - -Variants { - model: Quickshell.screens - - Scope { - id: scope - required property ShellScreen modelData - - PanelWindow { - screen: scope.modelData - color: "#99000000" - anchors { - top: true - left: true - right: true - } - - implicitHeight: 34 - - Rectangle { - anchors.centerIn: parent - implicitHeight: parent.height - implicitWidth: rowL.implicitWidth + 10 - color: "transparent" - RowLayout { - spacing: 8 - id: rowL - anchors.centerIn: parent - Repeater { - model: SystemTray.items - MouseArea { - id: trayItem - required property SystemTrayItem modelData - property SystemTrayItem item: modelData - implicitWidth: 20 - implicitHeight: 20 - - hoverEnabled: true - acceptedButtons: Qt.RightButton | Qt.LeftButton - - IconImage { - id: icon - - anchors.fill: parent - layer.enabled: true - - layer.onEnabledChanged: { - if (layer.enabled && status === Image.Ready) - analyser.requestUpdate(); - } - - onStatusChanged: { - if (layer.enabled && status === Image.Ready) - analyser.requestUpdate(); - } - - source: GetIcons.getTrayIcon(trayItem.item.id, trayItem.item.icon) - - mipmap: false - asynchronous: true - ImageAnalyser { - id: analyser - sourceItem: icon - } - } - Popout { - id: popout - menu: trayItem.item.menu - anchor.item: trayItem - anchor.edges: Edges.Bottom | Edges.Left - } - onClicked: { - if ( mouse.button === Qt.LeftButton ) { - trayItem.item.activate(); - } else if ( mouse.button === Qt.RightButton ) { - popout.visible = !popout.visible; - } - } - } - } - } - } - } - } -}