Create Styled things and finish notifications module

This commit is contained in:
Amaro Lopes
2025-10-09 22:32:48 -03:00
parent eddae605d4
commit db1ab727a7
13 changed files with 380 additions and 190 deletions

View File

@@ -2,9 +2,10 @@ import QtQuick
import Quickshell.Io
import Quickshell.Services.Pipewire
import qs.Common
import qs.Common.Styled
import qs.Services
Rectangle {
BackgroundRectangle {
id: audioWidget
property string monitor: ""
@@ -15,8 +16,7 @@ Rectangle {
implicitWidth: audioText.implicitWidth * 1.6
implicitHeight: Theme.heightGaps
color: Theme.backgroudColor
radius: 25
states: [
State {
name: "Mute"
@@ -56,17 +56,13 @@ Rectangle {
target: audioWidget.audioPipewireActive ? Pipewire.defaultAudioSink.audio : null
}
Text {
StyledText {
id: audioText
property string audioTextText: audioWidget.audioPipewireActive ? audioWidget.icon + " " + Math.round(Pipewire.defaultAudioSink.audio.volume * 100).toString() + "%" : ""
anchors.centerIn: parent
text: audioTextText
font.bold: true
font.pixelSize: Theme.pixelSize
font.family: Theme.fontFamily
color: Theme.textColor
}
Process {

View File

@@ -3,6 +3,7 @@ import Quickshell.Widgets
import Quickshell.Io
import qs.Services
import qs.Common
import qs.Common.Styled
Item {
property var monitor: ""
@@ -12,25 +13,19 @@ Item {
leftMargin: Theme.gaps
}
Rectangle {
BackgroundRectangle {
id: clock
color: Theme.backgroudColor
implicitWidth: clockText.implicitWidth * 1.6
implicitHeight: Theme.heightGaps
radius: 25
property string calendar: ""
Text {
StyledText {
id: clockText
anchors.centerIn: parent
text: Time.time
font.bold: true
font.pixelSize: Theme.pixelSize
font.family: Theme.fontFamily
color: Theme.textColor
}
Process {

View File

@@ -0,0 +1,10 @@
import QtQuick
import qs.Common
Rectangle {
color: Theme.backgroudColor
border.width: 1
border.color: Theme.color2
radius: 20
}

View File

@@ -0,0 +1,8 @@
import QtQuick
import qs.Common
Rectangle {
color: Theme.backgroudColorBright
radius: 20
}

View File

@@ -0,0 +1,19 @@
import QtQuick
import qs.Common
Text {
id: clockText
anchors.margins: 5
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
textFormat: Text.MarkdownText
font.bold: true
font.pixelSize: Theme.pixelSize
font.family: Theme.fontFamily
color: Theme.textColor
onLinkActivated: link => Qt.openUrlExternally(link)
text: ""
}

View File

@@ -3,57 +3,96 @@ import Quickshell
import Quickshell.Widgets
import qs.Services
import qs.Common
import qs.Common.Styled
import qs.Widgets
import Quickshell.Services.Notifications
Item {
id: root
property var monitor: ""
property bool createWindow: false
property string notificationIcon: ""
MarginWrapperManager {
rightMargin: Theme.gaps
leftMargin: Theme.gaps
}
states: [
State {
name: "MuteActive"
when: NotificationService.notificationsMuted && NotificationService.notificationsNumber
PropertyChanges {
root.notificationIcon : "\udb80\udc9b " + NotificationService.notificationsNumber
}
},
State {
name: "Active"
when: !NotificationService.notificationsMuted && NotificationService.notificationsNumber
PropertyChanges {
root.notificationIcon : "\udb80\udc9a " + NotificationService.notificationsNumber
}
},
State {
name: "MuteEmpty"
when: NotificationService.notificationsMuted
PropertyChanges {
root.notificationIcon : "\uec08"
}
},
State {
name: "Empty"
when: !NotificationService.notificationsMuted && !NotificationService.notificationsNumber
PropertyChanges {
root.notificationIcon : "\ueaa2"
}
}
]
Binding {
target: root
property: "createWindow"
value: NotificationService.notificationsNumber > 0 && root.createWindow
}
Rectangle {
BackgroundRectangle {
color: Theme.backgroudColor
implicitWidth: clockText.implicitWidth * 1.6
implicitWidth: 60
implicitHeight: Theme.heightGaps
radius: 25
Text {
id: clockText
StyledText {
id: notifText
anchors.centerIn: parent
text: {
NotificationService.notificationsNumber > 0 ? "\udb80\udc9a " + NotificationService.notificationsNumber : "\ueaa2";
}
font.bold: true
font.pixelSize: Theme.pixelSize
font.family: Theme.fontFamily
color: Theme.textColor
anchors.fill: parent
text: root.notificationIcon + " "
// text: {
// NotificationService.notificationsNumber > 0 ? "\udb80\udc9a " + NotificationService.notificationsNumber : "\ueaa2";
// }
}
MouseArea {
anchors.fill: parent
onClicked: {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
NotificationService.notificationsMuted = !NotificationService.notificationsMuted
return;
}
root.createWindow = !root.createWindow;
}
}
}
LazyLoader {
id: windowLoader
active: NotificationService.notificationsNumber ? createWindow : false
activeAsync: NotificationService.notificationsNumber ? createWindow : false
component: NotificationWindow {}
component: NotificationWindow {
onClear: root.createWindow = false
}
}
}

View File

@@ -8,6 +8,8 @@ Singleton {
readonly property string hyprlandSignature: Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE")
readonly property var focusedMon: Hyprland.focusedMonitor
property var sortedTopLevels: {
if (!ToplevelManager.toplevels || !ToplevelManager.toplevels.values) {
return [];

View File

@@ -5,6 +5,7 @@ import QtQuick
import Quickshell
import Quickshell.Services.Notifications
import qs.Widgets
import qs.Services
Singleton {
id: notificationRoot
@@ -12,8 +13,9 @@ Singleton {
readonly property var notificationServer: notificationServer
readonly property var trackedNotifications: notificationServer.trackedNotifications
readonly property var notificationsNumber: notificationServer.trackedNotifications.values.length
property bool shouldShowOsd: false
property var currentNotification: null
property bool notificationsMuted: false
property ListModel globalList: ListModel {}
NotificationServer {
id: notificationServer
@@ -21,54 +23,134 @@ Singleton {
}
Connections {
function onNotification(notif) {
if (notif.body == "MediaOngoingActivity") {
return;
}
if (notif.tracked) {
return;
}
notif.tracked = true;
notificationRoot.currentNotification = notif;
notificationRoot.shouldShowOsd = true;
notificationTimer.start();
}
target: notificationServer
function onNotification(notif) {
if(notif.transient) return;
if (notif.body === "MediaOngoingActivity")
return;
notif.tracked = true;
notificationRoot.addNotification(globalList, notif);
if (notif.lastGeneration)
return;
// Use the refactored helper
notificationRoot.addNotification(notificationList, notif);
}
}
Timer {
id: notificationTimer
ListModel {
id: notificationList
}
/**
* @param {ListModel} model
* @param {var} targetNotif
*/
function removeNotification(model, targetNotif) {
if (!model || typeof model.remove !== "function") {
console.warn("removeNotification(): invalid model");
return;
}
for (let i = 0; i < model.count; i++) {
if (model.get(i).notif === targetNotif) {
model.remove(i);
break;
}
}
}
/**
* @param {ListModel} model
* @param {var} notif
*/
function addNotification(model, notif) {
if (!model || typeof model.append !== "function") {
console.warn("addNotification(): invalid model");
return;
}
// Avoid duplicates
for (let i = 0; i < model.count; i++) {
if (model.get(i).notif === notif)
return;
}
model.append({
notif
});
notif.closed.connect(function (reason) {
removeNotification(model, notif);
});
}
// function notificationDismiss(notif) {
// removeNotification(notificationList, notif);
// removeNotification(globalList, notif);
// notif.dismiss();
// }
function notificationDismiss(notif) {
removeNotification(notificationList, notif);
removeNotification(globalList, notif);
if (notif && typeof notif.dismiss === "function")
notif.dismiss(); // dismiss first
interval: 5000
onTriggered: parent.shouldShowOsd = false
}
LazyLoader {
id: popupLoader
active: notificationRoot.currentNotification && notificationRoot.shouldShowOsd
active: notificationList.count && !notificationRoot.notificationsMuted
component: PanelWindow {
id: notificationWindow
screen: {
Quickshell.screens.filter(screen => screen.name == HyprlandService.focusedMon.name)[0];
}
anchors.top: true
margins.top: screen.height / 100
exclusiveZone: 0
implicitWidth: 400
implicitHeight: 905
implicitHeight: screen.height
color: "transparent"
mask: Region { item: listView }
NotificationWrapper {
id: notificationWrapper
ListView {
id: listView
model: notificationList
implicitWidth: parent.width
implicitHeight: contentHeight
orientation: ListView.Vertical
clip: true
spacing: 5
interactive: false
notification: notificationRoot.currentNotification
implicitWidth: notificationWindow.implicitWidth
delegate: NotificationWrapper {
id: notifWrapper
onDismissed: {
notificationRoot.shouldShowOsd = false;
notificationRoot.currentNotification.dismiss();
required property var modelData
notification: modelData
implicitWidth: listView.width
startTimer: NotificationUrgency.toString(notification?.urgency) === "Critical"? false: true
timerDuration: 5000
onDismissed: {
if (notification && typeof notification.dismiss === "function")
notificationRoot.notificationDismiss(notification);
}
onTimedout: {
notificationRoot.removeNotification(notificationList, notification)
}
}
Component.onCompleted: positionViewAtEnd()
}
}
}

View File

@@ -6,6 +6,7 @@ import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import qs.Common
import qs.Common.Styled
import qs.Services
Singleton {
@@ -35,18 +36,14 @@ Singleton {
Component {
id: stub
Text {
StyledText {
text: "stub"
font.bold: true
font.pixelSize: Theme.pixelSize
font.family: Theme.fontFamily
color: Theme.textColor
}
}
Component {
id: systray
Text {
StyledText {
text: {
if (!HoverMediator.component?.model)
return "";
@@ -57,35 +54,24 @@ Singleton {
else
return "";
}
font.bold: true
font.pixelSize: Theme.pixelSize
font.family: Theme.fontFamily
color: Theme.textColor
}
}
Component {
id: time
Text {
StyledText {
property string calendar: (HoverMediator.component.calendar) ? HoverMediator.component.calendar : ""
text: calendar
font.bold: true
font.pixelSize: Theme.pixelSize
font.family: Theme.fontFamilyMono
color: Theme.textColor
}
}
Component {
id: audio
Text {
StyledText {
property string sinkDescription: (HoverMediator.component.sink) ? HoverMediator.component.sink.description : ""
text: sinkDescription
font.bold: true
font.pixelSize: Theme.pixelSize
font.family: Theme.fontFamily
color: Theme.textColor
}
}
@@ -114,13 +100,15 @@ Singleton {
}
}
WrapperRectangle {
BackgroundRectangle {
id: wsPopUp
leftMargin: (Theme.gaps * 2)
rightMargin: (Theme.gaps * 2)
topMargin: Theme.gaps
bottomMargin: Theme.gaps
MarginWrapperManager {
leftMargin: (Theme.gaps * 2)
rightMargin: (Theme.gaps * 2)
topMargin: Theme.gaps
bottomMargin: Theme.gaps
}
color: Theme.backgroudColor
radius: 25
opacity: 1

View File

@@ -6,16 +6,18 @@ import Quickshell
import Quickshell.Widgets
import Quickshell.Services.SystemTray
import qs.Common
import qs.Common.Styled
import qs.Services
WrapperRectangle {
BackgroundRectangle {
id: systrayRoot
property string monitor: ""
rightMargin: Theme.gaps
leftMargin: Theme.gaps
radius: 25
color: Theme.backgroudColor
MarginWrapperManager {
rightMargin: Theme.gaps
leftMargin: Theme.gaps
}
property string monitor: ""
// color: Theme.backgroudColor
RowLayout {

View File

@@ -1,41 +1,91 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import qs.Services
import qs.Widgets
import qs.Common.Styled
import qs.Common
import QtQuick.Window
PopupWindow {
id: notificationRoot
anchor.item: root
anchor.rect.y: parentWindow?.height
implicitWidth: 400
implicitHeight: Math.min(listView.contentHeight, 900)
color: "white"
implicitWidth: screen.width
// implicitWidth: 400
implicitHeight: screen.height
color: "transparent"
visible: true
signal clear
ListView {
id: listView
mask: Region { item: listView }
MouseArea {
anchors.fill: parent
model: NotificationService.trackedNotifications.values
orientation: ListView.Vertical
verticalLayoutDirection: ListView.BottomToTop
clip: true
spacing: 5
onClicked: notificationRoot.clear()
}
delegate: NotificationWrapper {
required property var modelData
notification: modelData
width: ListView.width
onDismissed: {
if (notification && typeof notification.dismiss === "function") {
notification.dismiss();
Rectangle {
id: notifWindow
anchors{
top: parent.top
left: parent.left
topMargin: 40
}
border.width: 1
color: Theme.color2
implicitWidth: 400
implicitHeight: Math.min(listView.contentHeight, 1090) + 50
ColumnLayout {
id: windowLayout
anchors.fill: parent
spacing: 0
BackgroundRectangle {
implicitWidth: parent.width
implicitHeight: 25
radius: 0
border.width: 0
MouseArea {
anchors.fill: parent
onClicked: {
while (NotificationService.notificationsNumber != 0) {
NotificationService.trackedNotifications.values[0].dismiss();
}
}
}
StyledText {
anchors.centerIn: parent
text: "NOTIFICAÇÕES"
}
}
ListView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
model: NotificationService.globalList
clip: true
spacing: 5
leftMargin: 10
rightMargin: 10
delegate: NotificationWrapper {
required property var modelData
notification: modelData
implicitWidth: listView.width - 20
onDismissed: {
NotificationService.notificationDismiss(notification);
}
}
}
}
Component.onCompleted: positionViewAtEnd()
onModelChanged: positionViewAtEnd()
}
}

View File

@@ -2,134 +2,137 @@ import Quickshell
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import qs.Common.Styled
Rectangle {
BackgroundRectangle {
id: notificationWrapper
signal dismissed
signal timedout
property bool hasImage: notification?.image ? true : false
property bool clicked: false
property var notification: null
property bool startTimer: false
property int timerDuration: 2000
property bool firstTime: true
property string image: {
if (hasImage) {
return notification.image;
return notification?.image;
}
if (notification?.appIcon === "") {
return "";
}
return Quickshell.iconPath(notification?.appIcon);
}
property real targetHeight: notifLayout.implicitHeight + 20
implicitWidth: 400
implicitHeight: notifLayout.implicitHeight
topLeftRadius: image ? 100 : 20
bottomLeftRadius: image ? 100 : 20
topRightRadius: 20
bottomRightRadius: 20
color: "#80000000"
implicitHeight: 0
// implicitHeight: Math.max(notifLayout.implicitHeight, 100)
Component.onCompleted: {
notificationWrapper.implicitHeight = targetHeight
}
clip: true
Behavior on implicitHeight {
SequentialAnimation {
NumberAnimation {
duration: 150
easing.type: Easing.OutCubic
}
ScriptAction {
script: {
if(clicked) notificationWrapper.dismissed()
if(notificationWrapper.implicitHeight === 0) notificationWrapper.timedout()
}
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
notificationWrapper.dismissed();
notificationWrapper.clicked = true
notificationWrapper.implicitHeight = 0
}
}
Timer {
id: notifTimer
interval: timerDuration
running: notificationWrapper.startTimer
onTriggered: {
notificationWrapper.implicitHeight = 0;
}
}
RowLayout {
id: notifLayout
implicitHeight: Math.max(iconImage.implicitHeight, gridRoot.implicitHeight)
anchors {
fill: parent
}
ClippingRectangle {
IconImage {
id: iconImage
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
implicitWidth: 100
implicitHeight: 100
visible: notificationWrapper.image ? true : false
color: "grey"
radius: 100
IconImage {
anchors.centerIn: parent
implicitSize: 100
source: notificationWrapper.image
}
Layout.alignment: Qt.AlignLeft| Qt.AlignVCenter
Layout.leftMargin: 10
implicitSize: 80
visible: notificationWrapper.image? true: false
source: notificationWrapper.image
}
ColumnLayout {
id: gridRoot
Layout.leftMargin: 10
Layout.rightMargin: 10
implicitHeight: summaryRectangle.implicitHeight + bodyRectangle.implicitHeight
spacing: 5
ForegroundRectangle {
id: summaryRectangle
Rectangle {
Layout.fillWidth: true
implicitHeight: summaryText.implicitHeight + 10
radius: 20
color: "#50ffffff"
Rectangle {
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
visible: summaryText.text ? true : false
implicitWidth: parent.width
radius: parent.radius
color: "white"
StyledText {
id: summaryText
Text {
id: summaryText
anchors.fill: parent
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 12
wrapMode: Text.WordWrap
textFormat: Text.MarkdownText
text: notificationWrapper.notification.summary
}
text: notificationWrapper.notification? notificationWrapper.notification.summary : ""
}
}
Rectangle {
ForegroundRectangle {
id: bodyRectangle
Layout.fillWidth: true
Layout.columnSpan: 5
implicitHeight: bodyText.implicitHeight + 10
radius: 20
Rectangle {
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
visible: bodyText.text ? true : false
implicitWidth: parent.width
radius: parent.radius
color: "white"
StyledText {
id: bodyText
Text {
id: bodyText
anchors.fill: parent
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 14
wrapMode: Text.WordWrap
textFormat: Text.MarkdownText
text: notificationWrapper.notification.body
onLinkActivated: link => Qt.openUrlExternally(link)
}
text: notificationWrapper.notification? notificationWrapper.notification.body : ""
}
}
}

View File

@@ -7,6 +7,7 @@ import Quickshell
import Quickshell.Hyprland
import Quickshell.Widgets
import qs.Common
import qs.Common.Styled
import qs.Services
WrapperMouseArea {
@@ -41,7 +42,7 @@ WrapperMouseArea {
model: 5
delegate: Rectangle {
delegate: BackgroundRectangle {
id: workspacesRectangle
property string type: "workspace"
@@ -57,7 +58,6 @@ WrapperMouseArea {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
implicitHeight: Theme.heightGaps
radius: 25
states: [
State {
@@ -104,16 +104,12 @@ WrapperMouseArea {
}
}
Text {
StyledText {
property int workspaceName: workspacesRectangle.workspaceIndexAlign > 5 ? workspacesRectangle.workspaceIndexAlign - 5 : workspacesRectangle.workspaceIndexAlign
anchors.centerIn: parent
text: workspaceName
font.bold: true
font.pixelSize: Theme.pixelSize
font.family: Theme.fontFamily
color: Theme.textColor
}
MouseArea {