mirror of
https://github.com/danbulant/dots-hyprland
synced 2026-05-24 12:22:09 +00:00
notification popups
This commit is contained in:
parent
5dc0dc133d
commit
54fdf043c9
12 changed files with 240 additions and 46 deletions
8
.config/quickshell/GlobalStates.qml
Normal file
8
.config/quickshell/GlobalStates.qml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
Singleton {
|
||||
property int sidebarRightOpenCount: 0
|
||||
}
|
||||
|
|
@ -163,32 +163,31 @@ Item {
|
|||
Layout.fillHeight: true
|
||||
onPressed: Hyprland.dispatch(`workspace ${index+1}`)
|
||||
width: workspaceButtonWidth
|
||||
|
||||
contentItem: StyledText {
|
||||
z: 3
|
||||
property int workspaceValue: workspaceGroup * ConfigOptions.bar.workspacesShown + index + 1
|
||||
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: Appearance.font.pixelSize.small
|
||||
text: `${workspaceValue}`
|
||||
elide: Text.ElideRight
|
||||
color: (monitor.activeWorkspace?.id == workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : Appearance.colors.colOnLayer1Inactive)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Appearance.animation.elementDecel.duration
|
||||
easing.type: Appearance.animation.elementDecel.type
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Item {
|
||||
implicitWidth: workspaceButtonWidth
|
||||
implicitHeight: workspaceButtonWidth
|
||||
StyledText {
|
||||
z: 3
|
||||
property int workspaceValue: workspaceGroup * ConfigOptions.bar.workspacesShown + index + 1
|
||||
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10"))
|
||||
text: `${workspaceValue}`
|
||||
elide: Text.ElideRight
|
||||
color: (monitor.activeWorkspace?.id == workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : Appearance.colors.colOnLayer1Inactive)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Appearance.animation.elementDecel.duration
|
||||
easing.type: Appearance.animation.elementDecel.type
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ Singleton {
|
|||
property int barCenterSideModuleWidth: 360
|
||||
property int barPreferredSideSectionWidth: 400
|
||||
property int sidebarWidth: 450
|
||||
|
||||
property int hyprlandGapsOut: 5
|
||||
property int elevationMargin: 7
|
||||
property int fabShadowRadius: 5
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// From https://github.com/rafzby/circular-progressbar
|
||||
// License: LGPL-3.0 - A copy can be found in `licenses` folder of repo
|
||||
// Modified so it looks like in Material 3: https://m3.material.io/components/progress-indicators/specs
|
||||
import QtQuick 2.9
|
||||
import QtQuick
|
||||
import "root:/modules/common"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
|
@ -9,15 +10,19 @@ Item {
|
|||
property int size: 30
|
||||
property int lineWidth: 2
|
||||
property real value: 0
|
||||
property color primaryColor: "#70585D"
|
||||
property color secondaryColor: "#FFF8F7"
|
||||
property color primaryColor: Appearance.m3colors.m3onSecondaryContainer
|
||||
property color secondaryColor: Appearance.m3colors.m3secondaryContainer
|
||||
property real gapAngle: Math.PI / 10
|
||||
property bool fill: false
|
||||
property int fillOverflow: 2
|
||||
property int animationDuration: 1000
|
||||
property var easingType: Easing.OutCubic
|
||||
|
||||
width: size
|
||||
height: size
|
||||
|
||||
signal animationFinished();
|
||||
|
||||
onValueChanged: {
|
||||
canvas.degree = value * 360;
|
||||
}
|
||||
|
|
@ -62,12 +67,12 @@ Item {
|
|||
|
||||
// Secondary
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, progressAngle + gapAngle, startAngle - gapAngle);
|
||||
ctx.arc(x, y, radius, progressAngle + gapAngle, fullAngle - gapAngle);
|
||||
ctx.strokeStyle = root.secondaryColor;
|
||||
ctx.stroke();
|
||||
|
||||
// Primary (value indication)
|
||||
var endAngle = (progressAngle === startAngle) ? startAngle + epsilon : progressAngle;
|
||||
var endAngle = progressAngle + (value > 0 ? 0 : epsilon);
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, startAngle, endAngle);
|
||||
ctx.strokeStyle = root.primaryColor;
|
||||
|
|
@ -77,7 +82,7 @@ Item {
|
|||
Behavior on degree {
|
||||
NumberAnimation {
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.OutCubic
|
||||
easing.type: root.easingType
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,15 @@ import "./notification_utils.js" as NotificationUtils
|
|||
Item {
|
||||
id: root
|
||||
property var notificationObject
|
||||
property bool popup: false
|
||||
property bool expanded: false
|
||||
property bool enableAnimation: true
|
||||
property int notificationListSpacing: 5
|
||||
property bool ready: false
|
||||
property int defaultTimeoutValue: 5000
|
||||
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
clip: !popup
|
||||
|
||||
Process {
|
||||
id: closeSidebarProcess
|
||||
|
|
@ -42,6 +44,16 @@ Item {
|
|||
|
||||
Component.onCompleted: {
|
||||
root.ready = true
|
||||
if (popup) timeoutTimer.start()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timeoutTimer
|
||||
interval: notificationObject.expireTimeout ?? root.defaultTimeoutValue
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
Notifications.timeoutNotification(notificationObject.id);
|
||||
}
|
||||
}
|
||||
|
||||
function destroyWithAnimation(delay = 0) {
|
||||
|
|
@ -104,7 +116,7 @@ Item {
|
|||
root.toggleExpanded()
|
||||
}
|
||||
|
||||
// Flick right to dismiss
|
||||
// Flick right to dismiss/discard
|
||||
property real startX: 0
|
||||
property real dragStartThreshold: 10
|
||||
property real dragConfirmThreshold: 70
|
||||
|
|
@ -124,7 +136,7 @@ Item {
|
|||
}
|
||||
onDragStartedChanged: () => {
|
||||
// Prevent drag focus being shifted to parent flickable
|
||||
root.parent.parent.parent.interactive = !dragStarted
|
||||
if (root.parent.parent.parent.interactive !== undefined) root.parent.parent.parent.interactive = !dragStarted
|
||||
root.enableAnimation = !dragStarted
|
||||
}
|
||||
onReleased: (mouse) => {
|
||||
|
|
@ -196,6 +208,18 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
visible: popup
|
||||
id: notificationShadow
|
||||
anchors.fill: notificationBackground
|
||||
source: notificationBackground
|
||||
radius: 5
|
||||
samples: radius * 2 + 1
|
||||
color: Appearance.colors.colShadow
|
||||
verticalOffset: 2
|
||||
horizontalOffset: 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -334,6 +358,21 @@ Item {
|
|||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
CircularProgress {
|
||||
id: notificationProgress
|
||||
visible: popup
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
lineWidth: 2
|
||||
value: popup ? 1 : 0
|
||||
size: 20
|
||||
animationDuration: notificationObject.expireTimeout ?? root.defaultTimeoutValue
|
||||
easingType: Easing.Linear
|
||||
|
||||
Component.onCompleted: {
|
||||
value = 0
|
||||
}
|
||||
}
|
||||
|
||||
StyledText { // Time
|
||||
id: notificationTimeText
|
||||
Layout.fillWidth: false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
import "root:/"
|
||||
import "root:/modules/common"
|
||||
import "root:/modules/common/widgets"
|
||||
import "root:/services"
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
|
||||
Scope {
|
||||
id: screenCorners
|
||||
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
visible: true
|
||||
|
||||
property var modelData
|
||||
property Component notifComponent: NotificationWidget {}
|
||||
property list<NotificationWidget> notificationWidgetList: []
|
||||
|
||||
screen: modelData
|
||||
WlrLayershell.namespace: "quickshell:notificationPopup"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
exclusiveZone: 0
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: columnLayout
|
||||
}
|
||||
|
||||
color: "transparent"
|
||||
width: Appearance.sizes.notificationPopupWidth
|
||||
height: columnLayout.implicitHeight
|
||||
|
||||
// Signal handlers to add/remove notifications
|
||||
Connections {
|
||||
target: Notifications
|
||||
function onNotify(notification) {
|
||||
if (GlobalStates.sidebarRightOpenCount > 0) {
|
||||
return
|
||||
}
|
||||
// notificationRepeater.model = [notification, ...notificationRepeater.model]
|
||||
const notif = root.notifComponent.createObject(columnLayout, {
|
||||
notificationObject: notification,
|
||||
popup: true
|
||||
});
|
||||
notificationWidgetList.unshift(notif)
|
||||
|
||||
// Remove stuff from t he column, add back
|
||||
for (let i = 0; i < notificationWidgetList.length; i++) {
|
||||
if (notificationWidgetList[i].parent === columnLayout) {
|
||||
notificationWidgetList[i].parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Add notification widgets to the column
|
||||
for (let i = 0; i < notificationWidgetList.length; i++) {
|
||||
if (notificationWidgetList[i].parent === null) {
|
||||
notificationWidgetList[i].parent = columnLayout;
|
||||
}
|
||||
}
|
||||
}
|
||||
function onDiscard(id) {
|
||||
for (let i = notificationWidgetList.length - 1; i >= 0; i--) {
|
||||
const widget = notificationWidgetList[i];
|
||||
if (widget && widget.notificationObject && widget.notificationObject.id === id) {
|
||||
widget.destroyWithAnimation();
|
||||
notificationWidgetList.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
function onTimeout(id) {
|
||||
for (let i = notificationWidgetList.length - 1; i >= 0; i--) {
|
||||
const widget = notificationWidgetList[i];
|
||||
if (widget && widget.notificationObject && widget.notificationObject.id === id) {
|
||||
widget.destroyWithAnimation();
|
||||
notificationWidgetList.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
function onDiscardAll() {
|
||||
for (let i = notificationWidgetList.length - 1; i >= 0; i--) {
|
||||
const widget = notificationWidgetList[i];
|
||||
if (widget && widget.notificationObject) {
|
||||
widget.destroyWithAnimation();
|
||||
}
|
||||
}
|
||||
notificationWidgetList = [];
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout { // Scrollable window content
|
||||
id: columnLayout
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width - Appearance.sizes.hyprlandGapsOut * 2
|
||||
spacing: 0 // The widgets themselves have margins for spacing
|
||||
|
||||
Item {
|
||||
implicitHeight: 1
|
||||
implicitWidth: 1
|
||||
}
|
||||
|
||||
// Notifications are added by the above signal handlers
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -57,8 +57,6 @@ Scope {
|
|||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
mask: Region {
|
||||
item: osdValuesWrapper
|
||||
|
|
|
|||
|
|
@ -57,8 +57,6 @@ Scope {
|
|||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
mask: Region {
|
||||
item: osdValuesWrapper
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import "root:/"
|
||||
import "root:/services"
|
||||
import "root:/modules/common"
|
||||
import "root:/modules/common/widgets"
|
||||
import "root:/services"
|
||||
import "./quickToggles/"
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
|
@ -25,6 +26,10 @@ Scope {
|
|||
visible: false
|
||||
focusable: true
|
||||
|
||||
onVisibleChanged: {
|
||||
GlobalStates.sidebarRightOpenCount += visible ? 1 : -1
|
||||
}
|
||||
|
||||
property var modelData
|
||||
|
||||
screen: modelData
|
||||
|
|
@ -191,6 +196,7 @@ Scope {
|
|||
let panelWindow = sidebarVariants.instances[i];
|
||||
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
|
||||
panelWindow.visible = !panelWindow.visible;
|
||||
if(panelWindow.visible) Notifications.timeoutAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -209,6 +215,7 @@ Scope {
|
|||
let panelWindow = sidebarVariants.instances[i];
|
||||
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
|
||||
panelWindow.visible = true;
|
||||
if(panelWindow.visible) Notifications.timeoutAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ Item {
|
|||
notificationWidgetList.push(notif)
|
||||
})
|
||||
}
|
||||
|
||||
function onNotify(notification) {
|
||||
// notificationRepeater.model = [notification, ...notificationRepeater.model]
|
||||
const notif = root.notifComponent.createObject(columnLayout, { notificationObject: notification });
|
||||
|
|
@ -41,6 +42,7 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onDiscard(id) {
|
||||
for (let i = notificationWidgetList.length - 1; i >= 0; i--) {
|
||||
const widget = notificationWidgetList[i];
|
||||
|
|
@ -50,6 +52,16 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onDiscardAll() {
|
||||
for (let i = notificationWidgetList.length - 1; i >= 0; i--) {
|
||||
const widget = notificationWidgetList[i];
|
||||
if (widget && widget.notificationObject) {
|
||||
widget.destroyWithAnimation();
|
||||
}
|
||||
}
|
||||
notificationWidgetList = [];
|
||||
}
|
||||
}
|
||||
|
||||
Flickable { // Scrollable window
|
||||
|
|
@ -145,15 +157,7 @@ Item {
|
|||
buttonIcon: "clear_all"
|
||||
buttonText: "Clear"
|
||||
onClicked: () => {
|
||||
for (let i = notificationWidgetList.length - 1; i >= 0; i--) {
|
||||
// for (let i = 0; i < notificationWidgetList.length; i++) {
|
||||
const widget = notificationWidgetList[i];
|
||||
if (widget && widget.notificationObject) {
|
||||
widget.destroyWithAnimation();
|
||||
}
|
||||
}
|
||||
notificationWidgetList = [];
|
||||
Notifications.discardAll()
|
||||
Notifications.discardAllNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ Singleton {
|
|||
signal initDone();
|
||||
signal notify(notification: var);
|
||||
signal discard(id: var);
|
||||
signal discardAll();
|
||||
signal timeout(id: var);
|
||||
|
||||
NotificationServer {
|
||||
id: notifServer
|
||||
|
|
@ -69,13 +71,24 @@ Singleton {
|
|||
root.discard(id);
|
||||
}
|
||||
|
||||
function discardAll() {
|
||||
function discardAllNotifications() {
|
||||
root.list = []
|
||||
triggerListChange()
|
||||
notifFileView.setText(JSON.stringify(root.list, null, 2))
|
||||
notifServer.trackedNotifications.values.forEach((notif) => {
|
||||
notif.dismiss()
|
||||
})
|
||||
root.discardAll();
|
||||
}
|
||||
|
||||
function timeoutNotification(id) {
|
||||
root.timeout(id);
|
||||
}
|
||||
|
||||
function timeoutAll() {
|
||||
root.list.forEach((notif) => {
|
||||
root.timeout(notif.id);
|
||||
})
|
||||
}
|
||||
|
||||
function attemptInvokeAction(id, notifIdentifier) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//@ pragma UseQApplication
|
||||
|
||||
import "./modules/bar/"
|
||||
import "./modules/notificationPopup/"
|
||||
import "./modules/onScreenDisplay/"
|
||||
import "./modules/screenCorners/"
|
||||
import "./modules/session/"
|
||||
|
|
@ -13,6 +14,7 @@ import Quickshell
|
|||
|
||||
ShellRoot {
|
||||
Bar {}
|
||||
NotificationPopup {}
|
||||
OnScreenDisplayBrightness {}
|
||||
OnScreenDisplayVolume {}
|
||||
ReloadPopup {}
|
||||
|
|
|
|||
Loading…
Reference in a new issue