Compare commits

..

6 commits

Author SHA1 Message Date
9ae22c832f
feat: multi-height delegates 2025-12-07 21:34:18 -08:00
b15bb36dae
fix: clip notif 2025-12-07 20:18:58 -08:00
6698f9648c
fix: reduce systray button margin 2025-12-07 20:08:02 -08:00
fb4da4296e
fix: notifications 2025-12-07 20:07:08 -08:00
04732abbc8
fix: make clearing work 2025-12-07 18:58:18 -08:00
c98690fa63
feat: notifications o: 2025-12-07 18:41:31 -08:00
11 changed files with 468 additions and 10 deletions

View file

@ -0,0 +1,53 @@
import Quickshell
import Quickshell.Widgets
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "root:/DataSources"
import "root:/Helpers"
import Quickshell.Services.Notifications
RowLayout {
required property Notification modelData_
Layout.minimumHeight: 0
Layout.preferredHeight: modelData_.actions.length > 0 ? 30 : 0
visible: modelData_.actions.length > 0
spacing: 5
Repeater {
model: modelData_.actions
delegate: Item {
required property NotificationAction modelData
width: 100
height: 30
Rectangle {
anchors.fill: parent
color: Stylix.base00
radius: 5
Text {
text: modelData.text
color: Stylix.base05
anchors.centerIn: parent
font.pixelSize: 12
anchors {
left: parent.left
leftMargin: 10
verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
onClicked: modelData.invoke()
}
}
}
}
}

View file

@ -0,0 +1,81 @@
import Quickshell
import Quickshell.Widgets
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "root:/DataSources"
import "root:/Helpers"
import Quickshell.Services.Notifications
Item {
id: root
Layout.alignment: Qt.AlignVCenter;
implicitWidth: 30
implicitHeight: parent.height
Rectangle {
anchors.centerIn: parent
id: rootContainer
color: "transparent"
width: 60
height: 30
radius: 50
RowLayout {
spacing: 5
anchors.centerIn: parent
Text {
verticalAlignment: Text.AlignVCenter
id: rootIcon
text: ""
color: Stylix.base05
}
Text {
verticalAlignment: Text.AlignVCenter
id: count
text: ""
font.bold: true
color: Stylix.base05
}
}
}
function updateDisplay() {
if (Notifications.list.length > 0) {
rootContainer.color = Stylix.base08
rootIcon.color = Stylix.base00
count.color = Stylix.base00
count.text = Notifications.list.length
rootContainer.width = 60
root.implicitWidth = 60
count.visible = true
} else {
rootContainer.width = 30
root.implicitWidth = 30
rootContainer.color = "transparent"
rootIcon.color = Stylix.base05
count.color = Stylix.base05
count.visible = false
}
}
Timer {
interval: 1000
running: true
repeat: true
onTriggered: root.updateDisplay()
}
MouseArea {
id: ma
anchors.fill: parent
hoverEnabled: true
onClicked: function(mouseEvent) {
var m = root.QsWindow.mapFromItem(ma, ma.width/2.0, ma.height/2.0);
var offset = notificationLoader.item.width / 2.0;
notificationLoader.item.clicky = m.x - offset;
notificationLoader.item.visible = !notificationLoader.item.visible
}
}
NotificationWindow {
id: notificationLoader
}
}

View file

@ -0,0 +1,67 @@
import Quickshell
import Quickshell.Widgets
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "root:/DataSources"
import "root:/Helpers"
import Quickshell.Services.Notifications
RowLayout {
required property Notification modelData_
IconImage {
function getIcon() {
if (modelData_.appIcon != "") {
return Quickshell.iconPath(modelData_.appIcon.replace("file://", ""))
} else {
return ThemeIcons.iconForAppId(modelData_.appName)
}
}
width: 24
height: 24
visible: source != ""
source: getIcon()
}
Text {
font.bold: true
elide: Text.ElideRight
text: modelData_.summary
color: Stylix.base05
}
Text {
id: dismiss
text: "󱏩"
color: Stylix.base08
font.pixelSize: 16
ToolTip {
id: dismissTooltip
visible: false
delay: 500
timeout: 1000
text: "Dismiss notification"
}
HoverHandler {
id: dismissHover
onHoveredChanged: {
dismissTooltip.visible = hovered
}
}
Layout.topMargin: 5
Layout.rightMargin: 10
MouseArea {
anchors.fill: parent
onClicked: {
modelData_.dismiss();
if (Notifications.list.length <= 0) {
popup.visible = false;
}
}
}
}
}

View file

@ -0,0 +1,25 @@
import Quickshell
import Quickshell.Widgets
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "root:/DataSources"
import "root:/Helpers"
import Quickshell.Services.Notifications
ClippingWrapperRectangle {
required property string image
radius: 5
Layout.preferredWidth: visible ? 80 : 0
Layout.preferredHeight: visible ? parent.height : 0
visible: image != ""
color: Stylix.base00
Image {
fillMode: Image.PreserveAspectFit
Layout.preferredWidth: 80
Layout.preferredHeight: parent.height
source: image.replace("file://", "")
}
}

View file

@ -0,0 +1,76 @@
import Quickshell
import Quickshell.Widgets
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "root:/DataSources"
import "root:/Helpers"
import Quickshell.Services.Notifications
Item {
required property Notification modelData
function calculateHeight() {
if (modelData.actions.length > 0) {
return 150
} else if (modelData.image != "") {
return 100
} else {
return 60
}
}
function calculateBodyHeight() {
if (modelData.image != "" || modelData.actions.length > 0) {
return 40
} else {
return 20
}
}
height: calculateHeight()
width: 450
Rectangle {
id: indivNotif
color: Stylix.base02
radius: 5
anchors {
fill: parent
leftMargin: 5
rightMargin: 5
}
RowLayout {
anchors {
fill: parent
}
NotificationImage {
image: modelData.image
}
ColumnLayout {
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: 5
Layout.rightMargin: 5
Layout.fillWidth: true
NotificationHeader {
modelData_: modelData
}
Text {
font.pointSize: 10
wrapMode: Text.WordWrap
Layout.fillWidth: true
Layout.preferredWidth: modelData.image != "" ? indivNotif.width - 80 : indivNotif.width
Layout.maximumHeight: calculateBodyHeight()
Layout.maximumWidth: indivNotif.width
elide: Text.ElideRight
text: modelData.body
color: Stylix.base05
}
NotificationActions {
modelData_: modelData
}
}
}
}
}

View file

@ -0,0 +1,58 @@
import Quickshell
import Quickshell.Widgets
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "root:/DataSources"
import "root:/Helpers"
import Quickshell.Services.Notifications
LazyLoader {
id: notificationLoader
loading: true
PopupWindow {
property real clicky
id: wrapperPopup
visible: false
anchor.window: root.QsWindow.window
anchor.rect.y: parentWindow?.height ?? 0
anchor.rect.x: clicky
color: "transparent"
implicitWidth: 450
implicitHeight: 600
Rectangle {
anchors.fill: parent
color: Stylix.base01
bottomLeftRadius: 5
bottomRightRadius: 5
ColumnLayout {
anchors.fill: parent
NotificationWindowHeader {
Layout.topMargin: 5
Layout.bottomMargin: 5
}
ClippingRectangle {
color: "transparent"
Layout.alignment: Qt.AlignBottom
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height - 34
ListView {
cacheBuffer: 30
anchors.fill: parent
id: notificationList
model: Notifications.list
spacing: 10
ScrollBar.vertical: ScrollBar {}
delegate: NotificationItem {}
}
}
}
}
}
}

View file

@ -0,0 +1,54 @@
import Quickshell
import Quickshell.Widgets
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "root:/DataSources"
import "root:/Helpers"
import Quickshell.Services.Notifications
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 10
Text {
font.bold: true
Layout.preferredHeight: 26
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignVCenter
text: "Notifications"
color: Stylix.base05
font.pixelSize: 16
}
Text {
Layout.preferredHeight: 26
Layout.alignment: Qt.AlignVCenter
verticalAlignment: Text.AlignBottom
id: clear
text: "󱏧"
color: Stylix.base08
font.pixelSize: 16
ToolTip {
id: clearTooltip
visible: false
delay: 500
timeout: 1000
text: "Clear notifications"
}
MouseArea {
anchors.fill: parent
onClicked: {
if (Notifications.list.length >= 0) {
Notifications.clear()
root.updateDisplay()
}
}
}
HoverHandler {
id: clearHover
onHoveredChanged: {
clearTooltip.visible = hovered
}
}
}
}

View file

@ -12,14 +12,11 @@ Item {
width: parent.width
height: 30
property real text_point_size: 12
property real length: width / text_point_size
Rectangle {
anchors {
fill: parent
leftMargin: 10
rightMargin: 10
leftMargin: 5
rightMargin: 5
}
color: Stylix.base01
@ -33,7 +30,7 @@ Item {
horizontalAlignment: Text.AlignHCenter
text: modelData?.text ?? ""
color: Stylix.base05
font.pointSize: text_point_size
font.pointSize: 12
elide: Text.ElideRight
}

View file

@ -8,10 +8,11 @@ Item {
Layout.alignment: Qt.AlignVCenter;
implicitWidth: 25
implicitHeight: parent.height
property list<string> textStates: ["", ""]
Text {
id: texty
anchors.centerIn: parent
text: ""
text: textStates[0]
color: Stylix.base05
}
MouseArea {
@ -23,7 +24,9 @@ Item {
var m = root.QsWindow.mapFromItem(ma, ma.width/2.0, ma.height/2.0);
var offset = wrapperPopup.width / 2.0;
wrapperPopup.clicky = m.x - offset;
wrapperPopup.visible = !wrapperPopup.visible
wrapperPopup.visible = !wrapperPopup.visible;
texty.text = root.textStates[wrapperPopup.visible ? 1 : 0];
}
}
PopupWindow {
@ -32,11 +35,14 @@ Item {
anchor.window: root.QsWindow.window
anchor.rect.y: parentWindow.height
anchor.rect.x: clicky
width: systray.width + 10
height: systray.height + 10
implicitWidth: systray.width + 10
implicitHeight: systray.height + 10
color: "transparent"
Rectangle {
anchors.fill: parent
color: Stylix.base01
bottomLeftRadius: 5
bottomRightRadius: 5
SystemTray {
id: systray
}

View file

@ -0,0 +1,39 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import Quickshell.Services.Notifications
Singleton {
id: root
NotificationServer {
id: notificationServer
imageSupported: true
bodySupported: true
bodyMarkupSupported: false
bodyImagesSupported: false
actionsSupported: true
onNotification: (notification) => {
notification.tracked = true;
root.notification(notification);
}
}
function clear(): void {
for (const notification of notificationServer.trackedNotifications.values) {
notification.tracked = false;
}
list.length = 0;
}
// TODO: use signal
property list<Notification> list: notificationServer.trackedNotifications.values.filter(notification => notification.tracked).reverse()
signal notification(Notification notification)
IpcHandler {
target: "notifications"
function clear() {
root.clear()
}
}
}

View file

@ -5,6 +5,7 @@ import QtQuick.Layouts
import QtQuick.Controls
import "root:/DataSources"
import "root:/Components"
import "root:/Components/NotificationSystem"
Scope {
id: root
@ -72,6 +73,7 @@ Scope {
SystemTrayWrapper {}
Clock {}
NotificationDisplay {}
DistroIcon {}
}
}