diff --git a/Bar/Center.qml b/Bar/Center.qml index 42664c6..7c3e41a 100644 --- a/Bar/Center.qml +++ b/Bar/Center.qml @@ -1,20 +1,25 @@ +pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Hyprland +import Quickshell.Services.Notifications import "../Color.js" as Colors import "../Components/" import "../Services/" +import "./Center" Container { - id: centerPill + id: root + property Notification latestNotification: NotificationManager.getLatestNotification() || null required property HyprlandMonitor monitor + property NotificationServer notifServer: NotificationManager.notif property string previousState boxColor: Colors.mauve defaultItem: time - exclusiveMonitor: centerPill.monitor + exclusiveMonitor: root.monitor exclusiveToScreen: true state: "" @@ -23,17 +28,18 @@ Container { name: "" PropertyChanges { - centerPill.boxHeight: 28 - centerPill.boxRadius: 9 - centerPill.boxWidth: 100 + root.boxHeight: 28 + root.boxRadius: 9 + root.boxWidth: 100 + root.visibleTopMargin: 0 } StateChangeScript { script: { - if (centerPill.previousState != "hovered") { - centerPill.stack.replace(time); + if (root.previousState != "hovered") { + root.stack.replace(time); } - centerPill.previousState = ""; + root.previousState = ""; } } }, @@ -41,14 +47,15 @@ Container { name: "hovered" PropertyChanges { - centerPill.boxHeight: 35 - centerPill.boxRadius: 15 - centerPill.boxWidth: 110 + root.boxHeight: 35 + root.boxRadius: 15 + root.boxWidth: 110 + root.visibleTopMargin: 0 } StateChangeScript { script: { - centerPill.previousState = "hovered"; + root.previousState = "hovered"; } } }, @@ -56,28 +63,74 @@ Container { name: "expanded" PropertyChanges { - centerPill.boxHeight: 140 - centerPill.boxRadius: 30 - centerPill.boxWidth: 300 + root.boxHeight: 140 + root.boxRadius: 30 + root.boxWidth: 300 + root.visibleTopMargin: 0 } StateChangeScript { script: { - centerPill.stack.replace(fullTime); - centerPill.previousState = "expanded"; + if (root.state != "notifications") { + root.stack.replace(fullTime); + root.previousState = "expanded"; + } + } + } + }, + State { + extend: "expanded" + name: "notifications" + + PropertyChanges { + root.boxHeight: 320 + } + }, + State { + name: "notified" + + PropertyChanges { + root.boxHeight: 90 + root.boxRadius: 30 + root.boxWidth: 330 + root.visibleTopMargin: 20 + } + + StateChangeScript { + script: { + root.stack.replace(notification); + root.previousState = "notified"; } } } ] - mouse.onClicked: { - centerPill.state == "expanded" ? centerPill.state = "" : centerPill.state = "expanded"; + hover.onHoveredChanged: { + hover.hovered == true ? root.state == "notified" ? root.state = "notified" : root.state = "hovered" : + root.state = ""; } - mouse.onEntered: { - centerPill.state = "hovered"; + onLatestNotificationChanged: { + if (latestNotification != null && Hyprland.focusedMonitor == monitor) { + if (root.latestNotification.lastGeneration == false) { + root.state = "notified"; + notificationViewTimer.start(); + } + } } - mouse.onExited: { - centerPill.state = ""; + tap.onTapped: { + root.state == "hovered" ? root.state = "expanded" : undefined; + } + + Timer { + id: notificationViewTimer + + interval: 3000 + repeat: false + running: false || !root.hover.hovered + + onTriggered: { + root.state = ""; + } } Component { @@ -93,50 +146,42 @@ Container { Component { id: fullTime - Item { - Rectangle { - anchors.fill: parent - radius: 30 + Expanded { + NotificationList { + id: notifList - gradient: Gradient { - GradientStop { - color: "transparent" - position: 0.0 - } + animEnabled: false + radius: root.boxRadius + rootState: root.state + server: root.notifServer + state: "" - GradientStop { - color: "transparent" - position: 0.6 - } - - GradientStop { - color: Colors.red - position: 1.0 - } - } - } - - Column { - anchors.centerIn: parent - spacing: 5 - - StyledText { - anchors.horizontalCenter: parent.horizontalCenter - font.pointSize: 30 - text: Time.time - } - - StyledText { - anchors.horizontalCenter: parent.horizontalCenter - color: Colors.surface0 - text: Time.date - - font { - pointSize: 9 - weight: 400 + TapHandler { + onTapped: { + notifList.animEnabled = true; + root.state = "notifications"; + Qt.callLater(() => { + notifList.animEnabled = false; + }); } } } } } + + Component { + id: notification + + NotificationDisplay { + body: root.latestNotification.body + summary: root.latestNotification.summary + + TapHandler { + onTapped: { + root.state = "notifications"; + root.stack.replace(fullTime); + } + } + } + } } diff --git a/Bar/Center/Expanded.qml b/Bar/Center/Expanded.qml new file mode 100644 index 0000000..2908700 --- /dev/null +++ b/Bar/Center/Expanded.qml @@ -0,0 +1,55 @@ +import QtQuick +import Quickshell +import "../../Color.js" as Colors +import "../../Components/" +import "../../Services/" + +Item { + id: fullTimeRoot + + Rectangle { + anchors.fill: parent + radius: 30 + + gradient: MidpointGradient { + color: Colors.red + } + } + + Item { + id: timeColumnContainer + + anchors.fill: parent + + Column { + id: timeColumn + + spacing: 5 + + anchors { + centerIn: parent + } + + StyledText { + anchors.horizontalCenter: parent.horizontalCenter + color: Colors.surface0 + text: Time.time + + font { + pointSize: 30 + } + } + + StyledText { + anchors.horizontalCenter: parent.horizontalCenter + color: Colors.surface1 + text: Time.date + + font { + pointSize: 9 + weight: 400 + } + } + } + } +} diff --git a/Bar/Center/NotificationDisplay.qml b/Bar/Center/NotificationDisplay.qml new file mode 100644 index 0000000..8e5cc5d --- /dev/null +++ b/Bar/Center/NotificationDisplay.qml @@ -0,0 +1,47 @@ +import QtQuick +import Quickshell +import Quickshell.Services.Notifications +import "../../Color.js" as Colors +import "../../Components/" +import "../../Services/" + +Item { + id: root + + required property string body + required property string summary + + Rectangle { + anchors.fill: parent + radius: 30 + + gradient: MidpointGradient { + color: Colors.green + midpoint: 0.7 + } + } + + Column { + anchors.centerIn: parent + spacing: 5 + + StyledText { + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: 11 + text: root.summary + } + + StyledText { + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + text: root.body + verticalAlignment: Text.AlignVCenter + width: root.width - 20 + + font { + pointSize: 10 + weight: 500 + } + } + } +} diff --git a/Bar/Center/NotificationList.qml b/Bar/Center/NotificationList.qml new file mode 100644 index 0000000..41529d3 --- /dev/null +++ b/Bar/Center/NotificationList.qml @@ -0,0 +1,302 @@ +import QtQuick +import QtQuick.Effects +import Quickshell +import Quickshell.Services.Notifications +import "../../Color.js" as Colors +import "../../Components/" +import "../../Services/" + +Item { + id: root + + required property bool animEnabled + required property double radius + required property string rootState + required property NotificationServer server + + Behavior on anchors.topMargin { + enabled: root.animEnabled + + SpringAnimation { + damping: 0.35 + spring: 4 + } + } + states: [ + State { + name: "" + when: root.rootState != "notifications" + + PropertyChanges { + root.anchors.topMargin: root.parent.height - 21 + } + }, + State { + name: "expanded" + when: root.rootState == "notifications" + + PropertyChanges { + root.anchors.topMargin: 5 + } + } + ] + + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + top: parent.top + topMargin: parent.height - 21 + } + + Rectangle { + id: background + + anchors.fill: parent + color: Colors.surface0 + radius: root.radius - 3 + + states: [ + State { + name: "" + when: root.rootState != "notifications" + + PropertyChanges {} + }, + State { + name: "expanded" + when: root.rootState == "notifications" + + PropertyChanges { + background.anchors.bottomMargin: 5 + background.anchors.leftMargin: 5 + background.anchors.rightMargin: 5 + } + + StateChangeScript { + script: { + console.log("some"); + } + } + } + ] + + anchors { + bottomMargin: 15 + leftMargin: 20 + rightMargin: 20 + } + } + + Item { + id: notifListContainer + + anchors.fill: parent + clip: true + opacity: 0 + + anchors { + bottomMargin: 5 + leftMargin: 5 + rightMargin: 5 + topMargin: 5 + } + + CenteredText { + color: Colors.overlay0 + text: "No Notifications" + visible: root.server.trackedNotifications.values.length <= 0 + } + + ListView { + id: notifScrollingList + + anchors.fill: parent + boundsBehavior: Flickable.StopAtBounds + height: Math.min(contentHeight, parent.height) + implicitHeight: background.height + model: root.server.trackedNotifications.values.length + spacing: 7 + + // verticalLayoutDirection: ListView.BottomToTop + + delegate: Item { + id: notifRoot + + property Notification currentNotif: root.server.trackedNotifications.values[(notifRoot.notifLen + - 1) - index] + required property real index + property real notifLen: root.server.trackedNotifications.values.length + + height: 50 + width: ListView.view.width + + states: [ + State { + name: "" + when: notifHover.hovered == false + }, + State { + name: "hovered" + when: notifHover.hovered == false + + PropertyChanges {} + } + ] + + HoverHandler { + id: notifHover + } + + Rectangle { + id: dismissButton + + color: Colors.red + implicitWidth: 100 + radius: 10 + + Behavior on anchors.rightMargin { + NumberAnimation { + duration: 100 + } + } + states: [ + State { + name: "" + when: notifHover.hovered == false + }, + State { + name: "hovered" + when: notifHover.hovered == true + + PropertyChanges { + dismissButton.anchors.rightMargin: 5 + } + } + ] + + TapHandler { + onTapped: { + notifRoot.currentNotif.dismiss(); + } + } + + anchors { + bottom: parent.bottom + bottomMargin: 3 + right: parent.right + rightMargin: 10 + top: parent.top + topMargin: 3 + } + } + + Rectangle { + id: textContainer + + anchors.fill: parent + clip: true + color: Colors.surface2 + radius: 9 + + Behavior on anchors.rightMargin { + NumberAnimation { + duration: 100 + } + } + states: [ + State { + name: "" + when: notifHover.hovered == false + }, + State { + name: "hovered" + when: notifHover.hovered == true + + PropertyChanges { + textContainer.anchors.rightMargin: 40 + } + } + ] + + Column { + id: textColumn + + anchors { + centerIn: parent + left: parent.left + right: parent.right + } + + StyledText { + color: Colors.text + horizontalAlignment: Text.AlignHCenter + text: notifRoot.currentNotif.summary + verticalAlignment: Text.AlignVCenter + width: notifRoot.width - 10 + } + + StyledText { + color: Colors.subtext1 + horizontalAlignment: Text.AlignHCenter + text: notifRoot.currentNotif.body + verticalAlignment: Text.AlignVCenter + width: notifRoot.width - 10 + } + } + } + } + + anchors { + bottomMargin: 5 + leftMargin: 10 + rightMargin: 10 + topMargin: 5 + } + } + } + + MultiEffect { + id: notifListClipper + + anchors.fill: notifListContainer + maskEnabled: true + maskSource: clipsource + opacity: 0 + source: notifListContainer + + Behavior on opacity { + NumberAnimation { + duration: 100 + } + } + states: [ + State { + name: "" + when: root.rootState != "notifications" + + PropertyChanges { + notifListClipper.opacity: 0 + } + }, + State { + name: "expanded" + when: root.rootState == "notifications" + + PropertyChanges { + notifListClipper.opacity: 1 + } + } + ] + } + + Rectangle { + id: clipsource + + anchors.fill: background + color: "black" + layer.enabled: true + radius: background.radius + visible: false + } +} diff --git a/Components/Container.qml b/Components/Container.qml index 3d0fd78..d3fd96c 100644 --- a/Components/Container.qml +++ b/Components/Container.qml @@ -1,5 +1,6 @@ import QtQuick import QtQuick.Controls +import QtQuick.Effects import Quickshell import Quickshell.Hyprland @@ -12,26 +13,26 @@ Item { property double boxHeight: 28 property double boxRadius: 9 property double boxWidth: 100 - property Component defaultItem - required property HyprlandMonitor exclusiveMonitor - property bool exclusiveToScreen: false - property alias mouse: mouseArea - property alias rect: container - property alias stack: containerContent - - function getVisible() { + readonly property double calculatedTopMargin: { if (containerRoot.exclusiveToScreen) { if (Hyprland.focusedMonitor != containerRoot.exclusiveMonitor) { return -4 - containerRoot.boxHeight - 5; } else { - return 0; + return visibleTopMargin; } } else { - return 0; + return visibleTopMargin; } } + property Component defaultItem + required property HyprlandMonitor exclusiveMonitor + property bool exclusiveToScreen: false + property alias hover: hoverHandler + property alias rect: container + property alias stack: containerContent + property alias tap: tapHandler + property double visibleTopMargin: 0 - clip: true implicitHeight: boxHeight implicitWidth: boxWidth @@ -59,7 +60,7 @@ Item { anchors { top: parent.top - topMargin: getVisible() + topMargin: calculatedTopMargin } NumberAnimation { @@ -69,18 +70,29 @@ Item { easing: Easing.InOutBack } - MouseArea { - id: mouseArea + HoverHandler { + id: hoverHandler + } - anchors.fill: parent - hoverEnabled: true - z: 1 + TapHandler { + id: tapHandler + } + + RectangularShadow { + anchors.fill: container + blur: 30 + color: Colors.mantle + offset.x: 7 + offset.y: 3 + radius: container.radius + spread: 10 } Rectangle { id: container anchors.fill: parent + clip: true color: containerRoot.boxColor radius: containerRoot.boxRadius diff --git a/Components/MidpointGradient.qml b/Components/MidpointGradient.qml new file mode 100644 index 0000000..333c5fd --- /dev/null +++ b/Components/MidpointGradient.qml @@ -0,0 +1,23 @@ +import QtQuick + +Gradient { + id: root + + required property color color + property double midpoint: 0.6 + + GradientStop { + color: "transparent" + position: 0.0 + } + + GradientStop { + color: "transparent" + position: root.midpoint + } + + GradientStop { + color: root.color + position: 1.0 + } +} diff --git a/Components/StyledText.qml b/Components/StyledText.qml index 80bbd52..db0271b 100644 --- a/Components/StyledText.qml +++ b/Components/StyledText.qml @@ -3,6 +3,7 @@ import "../Color.js" as Colors Text { color: Colors.base + elide: Text.ElideRight font { family: "FiraMono Nerd Font" diff --git a/Services/NotificationManager.qml b/Services/NotificationManager.qml new file mode 100644 index 0000000..ece9287 --- /dev/null +++ b/Services/NotificationManager.qml @@ -0,0 +1,31 @@ +pragma Singleton +import QtQuick +import Quickshell +import Quickshell.Services.Notifications + +Singleton { + id: root + + property alias notif: server + + function getLatestNotification() { + let notificationList = server.trackedNotifications.values; + let len = notificationList.length; + if (len <= 0) { + len = 1; + } + let latestNotification = notificationList[len - 1]; + return latestNotification; + } + + NotificationServer { + id: server + + bodyMarkupSupported: true + bodySupported: true + + onNotification: notification => { + notification.tracked = true; + } + } +} diff --git a/Services/qmldir b/Services/qmldir new file mode 100644 index 0000000..ad604ed --- /dev/null +++ b/Services/qmldir @@ -0,0 +1,3 @@ +module Services +singleton Time 1.0 Time.qml +singleton NotificationManager 1.0 NotificationManager.qml