diff --git a/Bar.qml b/Bar.qml index 45a8464..70f972d 100644 --- a/Bar.qml +++ b/Bar.qml @@ -3,96 +3,131 @@ import QtQuick.Effects import QtQuick.Layouts import Quickshell import Quickshell.Hyprland -import "Bar" +import "./Bar" import "Color.js" as Colors Scope { Variants { + id: panelDelegate + + delegate: panelWindow model: Quickshell.screens + } - delegate: Component { - PanelWindow { - id: win + Component { + id: panelWindow - property double barExclusionZone: 15 - property double barMaxHeight: 1000 - required property var modelData - property HyprlandMonitor monitor: Hyprland.monitorFor(modelData) + PanelWindow { + id: win - color: "transparent" - exclusionMode: ExclusionMode.Normal - exclusiveZone: barExclusionZone - implicitHeight: barMaxHeight - screen: modelData + property double barExclusionZone: 15 + property double barMaxHeight: 1000 + required property var modelData + property HyprlandMonitor monitor: Hyprland.monitorFor(modelData) - mask: Region { - Region { - item: left - } + color: "transparent" + exclusionMode: ExclusionMode.Normal + exclusiveZone: barExclusionZone + implicitHeight: barMaxHeight + mask: maskRegion + screen: modelData - Region { - item: center - } + Region { + id: maskRegion - Region { - item: right - } + Region { + item: left + } + + Region { + item: center + } + + Region { + item: right + } + } + + anchors { + left: true + right: true + top: true + } + + MultiEffect { + anchors.fill: rowContainer + shadowBlur: 1.5 + shadowColor: Colors.mantle + shadowEnabled: true + shadowHorizontalOffset: 5 + shadowOpacity: 1 + shadowVerticalOffset: 3 + source: rowContainer + } + + Item { + id: rowContainer + + anchors { + fill: parent + topMargin: 3 } anchors { - left: true - right: true - top: true + left: parent.left + leftMargin: 3 + right: parent.right + rightMargin: 3 + top: parent.top } - Item { + Row { + id: left + anchors { - fill: parent - topMargin: 3 + left: parent.left + top: parent.top } - Row { - id: left + WorkspaceSelector { + monitor: win.monitor + } + } - height: childrenRect.height + Row { + id: center - anchors { - left: parent.left - leftMargin: 7 - } - - Left { - monitor: win.monitor - } + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top } - Row { - id: center + Time { + monitor: win.monitor + } + } - height: childrenRect.height + Row { + id: right - anchors { - horizontalCenter: parent.horizontalCenter - } + Layout.alignment: Qt.AlignTop + spacing: 5 - Center { - monitor: win.monitor - } + anchors { + right: parent.right + top: parent.top } - Row { - id: right + Network { + monitor: win.monitor + } - height: childrenRect.height + Bluetooth { + monitor: win.monitor + } - anchors { - right: parent.right - rightMargin: 7 - } - - Right { - monitor: win.monitor - } + Battery { + monitor: win.monitor } } } diff --git a/Bar/Battery.qml b/Bar/Battery.qml new file mode 100644 index 0000000..99b4a6e --- /dev/null +++ b/Bar/Battery.qml @@ -0,0 +1,189 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland + +import "../Color.js" as Colors +import "../Components/" +import "../Services/" + +Container { + id: root + + property double batteryDisplayHeight: 100 + required property HyprlandMonitor monitor + property bool opened: false + property double radius: 9 + + animOffset: 150 + boxColor: Colors.green + boxRadius: radius + defaultItem: icon + exclusiveMonitor: root.monitor + exclusiveToScreen: true + forceHidden: !BatteryManager.hasLaptopBattery + + states: [ + State { + name: "closed" + when: root.hovered == false && root.opened == false + + PropertyChanges { + root.boxHeight: 25 + root.boxRadius: root.radius + root.boxWidth: 60 + } + }, + State { + name: "hovered" + when: root.hovered == true && root.opened == false + + PropertyChanges { + root.boxHeight: 28 + root.boxWidth: 64 + } + }, + State { + name: "opened" + when: root.opened == true && root.hovered == true + + PropertyChanges { + root.boxHeight: root.batteryDisplayHeight + root.boxRadius: 12 + root.boxWidth: 450 + } + + StateChangeScript { + script: { + stack.replace(batteryDisplay); + } + } + } + ] + + hover.onHoveredChanged: { + if (hover.hovered == false) { + root.opened = false; + if (root.state != "icon") { + root.stack.replace(icon); + } + } + } + tap.onTapped: { + root.opened = true; + } + + Component { + id: icon + + Item { + BatteryGradient {} + + CenteredText { + property string component: "icon" + + propo: true + text: BatteryManager.getBatteryText() + } + } + } + + Component { + id: batteryDisplay + + Item { + id: batteryDisplayRoot + + property double innerMargin: 5 + property double outerMargin: 6 + + BatteryGradient {} + + Rectangle { + id: batteryBackground + + anchors.fill: parent + anchors.margins: batteryDisplayRoot.outerMargin + color: Colors.surface1 + radius: 8 + + RowLayout { + anchors.fill: parent + + Rectangle { + id: percentBox + + property double percentBoxRadius: 5 + + Layout.fillHeight: true + Layout.margins: batteryDisplayRoot.innerMargin + color: Colors.text + implicitWidth: root.batteryDisplayHeight - (batteryDisplayRoot.innerMargin * 2) - ( + batteryDisplayRoot.outerMargin * 2) + radius: 5 + + Rectangle { + color: Colors.green + implicitHeight: (root.batteryDisplayHeight - (batteryDisplayRoot.innerMargin * 2) - ( + batteryDisplayRoot.outerMargin * 2)) * BatteryManager.mainBattery.percentage + radius: percentBox.percentBoxRadius + + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + } + + CenteredText { + fontSize: 15 + text: BatteryManager.getBatteryPercentage(BatteryManager.mainBattery) + "%" + } + } + + Column { + id: batColumn + + property var batInfo: BatteryManager.getMainBatteryInfo() + + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + + StyledText { + color: Colors.text + fontSize: 14 + text: "Battery - " + batColumn.batInfo["name"] + } + + StyledText { + color: Colors.subtext1 + fontSize: 12 + fontWeight: 5 + text: batColumn.batInfo["energy"] + "/" + batColumn.batInfo["capacity"] + "Wh - " + + BatteryManager.getChargingRateFormat() + } + + StyledText { + property var timeInfo: batColumn.batInfo["time"] + + color: Colors.subtext1 + fontSize: 12 + fontWeight: 5 + text: BatteryManager.getTimeFormat() + } + } + } + } + } + } + + component BatteryGradient: MidpointGradient { + anchors.fill: parent + angle: -45 + blur: 0.6 + color: BatteryManager.getBatteryColor() + midpoint: 0.55 + radius: radius + } +} diff --git a/Bar/Bluetooth.qml b/Bar/Bluetooth.qml new file mode 100644 index 0000000..0687db5 --- /dev/null +++ b/Bar/Bluetooth.qml @@ -0,0 +1,213 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland + +import "../Color.js" as Colors +import "../Components/" +import "../Services/" + +Container { + id: root + + property double devicePlaqueHeight: 60 + property double devicePlaqueSpacing: 4 + required property HyprlandMonitor monitor + property bool opened: false + + animOffset: 200 + boxColor: Colors.peach + boxHeight: 25 + boxWidth: 33 + defaultItem: icon + exclusiveMonitor: root.monitor + exclusiveToScreen: true + forceHidden: !BluetoothManager.anyConnected + + states: [ + State { + name: "closed" + when: root.hovered == false && root.opened == false + + PropertyChanges { + root.boxHeight: 25 + root.boxRadius: 9 + root.boxWidth: 33 + } + }, + State { + name: "hovered" + when: root.hovered == true && root.opened == false + + PropertyChanges { + root.boxHeight: 28 + root.boxWidth: 35 + } + }, + State { + name: "opened" + when: root.opened == true && root.hovered == true + + PropertyChanges { + root.boxHeight: (root.devicePlaqueHeight * BluetoothManager.getConnectedDevicesList().length) + ( + root.devicePlaqueSpacing * (BluetoothManager.getConnectedDevicesList().length + 1)) + 2 + root.boxRadius: 12 + root.boxWidth: 350 + } + + StateChangeScript { + script: { + stack.replace(deviceList); + } + } + } + ] + + hover.onHoveredChanged: { + if (hover.hovered == false) { + root.opened = false; + if (root.state != "icon") { + root.stack.replace(icon); + } + } + } + tap.onTapped: { + root.opened = true; + } + + Component { + id: icon + + StyledText { + property string component: "icon" + + horizontalAlignment: Qt.AlignCenter + propo: true + text: "󰂱" + verticalAlignment: Qt.AlignVCenter + } + } + + Component { + id: deviceList + + Column { + id: deviceListRoot + + property string component: "deviceList" + property double plaqueHeight: root.devicePlaqueHeight + property double plaqueSpacing: root.devicePlaqueSpacing + + spacing: plaqueSpacing + + Item { + implicitHeight: 1 + implicitWidth: 10 + } + + Repeater { + delegate: devicePlaque + model: BluetoothManager.getConnectedDevicesList() + } + + Component { + id: devicePlaque + + Rectangle { + id: devicePlaqueRoot + + readonly property variant deviceText: BluetoothManager.getDeviceText(modelData) + readonly property double margin: 5 + required property var modelData + + color: Colors.surface1 + implicitHeight: deviceListRoot.plaqueHeight + radius: 10 + + anchors { + left: parent.left + leftMargin: devicePlaqueRoot.margin + right: parent.right + rightMargin: devicePlaqueRoot.margin + } + + RowLayout { + id: deviceInfoRow + + property double margin: 7 + + spacing: margin * 2 + + anchors { + fill: parent + leftMargin: margin + } + + Rectangle { + id: iconContainer + + property double boxRadius: 4 + + color: Colors.text + implicitHeight: deviceListRoot.plaqueHeight - (deviceInfoRow.margin * 2) + implicitWidth: deviceListRoot.plaqueHeight - (deviceInfoRow.margin * 2) + radius: boxRadius + + Rectangle { + id: batteryIndicator + + color: Colors.green + implicitHeight: (deviceListRoot.plaqueHeight - (deviceInfoRow.margin * 2)) + * devicePlaqueRoot.deviceText["batteryRaw"] + radius: iconContainer.boxRadius + + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + } + + HoverHandler { + id: batteryHoverHandler + + onHoveredChanged: { + if (batteryHoverHandler.hovered == true) { + batteryText.text = devicePlaqueRoot.deviceText["battery"]; + } else { + batteryText.text = devicePlaqueRoot.deviceText["icon"]; + } + } + } + + CenteredText { + id: batteryText + + fontSize: 17 + text: devicePlaqueRoot.deviceText["icon"] + } + } + + Column { + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + + StyledText { + color: Colors.text + fontSize: 14 + text: devicePlaqueRoot.deviceText["name"] + } + + StyledText { + color: Colors.subtext1 + fontSize: 10 + text: devicePlaqueRoot.deviceText["mac"] + } + } + } + } + } + } + } +} diff --git a/Bar/Center.qml b/Bar/Center.qml deleted file mode 100644 index 103cd75..0000000 --- a/Bar/Center.qml +++ /dev/null @@ -1,263 +0,0 @@ -pragma ComponentBehavior: Bound -import QtQuick -import QtQuick.Layouts -import Quickshell -import Quickshell.Hyprland -import Quickshell.Services.Mpris -import Quickshell.Services.Notifications -import "../Color.js" as Colors -import "../Components/" -import "../Services/" -import "./Center" - -Container { - 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: root.monitor - exclusiveToScreen: true - - states: [ - State { - name: "" - - PropertyChanges { - root.boxHeight: 28 - root.boxRadius: 9 - root.boxWidth: 100 - root.visibleTopMargin: 0 - } - - StateChangeScript { - script: { - if (root.previousState != "hovered") { - root.stack.replace(time); - } - root.previousState = ""; - } - } - }, - State { - name: "hovered" - - PropertyChanges { - root.boxHeight: 35 - root.boxRadius: 15 - root.boxWidth: 110 - root.visibleTopMargin: 0 - } - - StateChangeScript { - script: { - root.previousState = "hovered"; - } - } - }, - State { - name: "mprisToast" - - PropertyChanges { - root.boxHeight: 35 - root.boxRadius: 15 - root.boxWidth: 350 - root.visibleTopMargin: 0 - } - - StateChangeScript { - script: { - root.previousState = "mpris"; - root.stack.replace(mpris); - } - } - }, - State { - name: "expanded" - - PropertyChanges { - root.boxHeight: 140 - root.boxRadius: 30 - root.boxWidth: 300 - root.visibleTopMargin: 0 - } - - StateChangeScript { - script: { - 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"; - } - } - } - ] - - hover.onHoveredChanged: { - if (hover.hovered == true) { - if (root.state == "notified") { - root.state = "notified"; - notificationViewTimer.stop(); - } else if (MprisManager.getPlaying() == true) { - root.state = "mprisToast"; - } else { - root.state = "hovered"; - } - } else { - root.state = ""; - } - } - onLatestNotificationChanged: { - if (latestNotification != null && Hyprland.focusedMonitor == monitor) { - if (root.latestNotification.lastGeneration == false) { - root.state = "notified"; - notificationViewTimer.restart(); - } - } - } - tap.onTapped: { - root.state == "hovered" ? root.state = "expanded" : undefined; - } - - Connections { - function onTrackChanged() { - if (Hyprland.focusedMonitor == root.monitor) { - musicToastTimer.restart(); - if (root.state == "") { - root.state = "mprisToast"; - } - } - } - - target: MprisManager.defaultPlayer - } - - Timer { - id: notificationViewTimer - - interval: 3000 - repeat: false - running: false - - onTriggered: { - root.state = ""; - } - } - - Timer { - id: musicToastTimer - - interval: 3000 - repeat: false - running: false - - onTriggered: { - if (!root.hover.hovered) { - root.state = ""; - } - } - } - - Component { - id: time - - Item { - CenteredText { - text: Time.time - } - } - } - - Component { - id: mpris - - Mpris { - TapHandler { - onTapped: root.state = "expanded" - } - - TapHandler { - acceptedButtons: Qt.RightButton - - onTapped: MprisManager.skip() - } - - TapHandler { - acceptedButtons: Qt.MiddleButton - - onTapped: MprisManager.prev() - } - } - } - - Component { - id: fullTime - - Expanded { - NotificationList { - id: notifList - - animEnabled: false - radius: root.boxRadius - rootState: root.state - server: root.notifServer - state: "" - - 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 deleted file mode 100644 index 2908700..0000000 --- a/Bar/Center/Expanded.qml +++ /dev/null @@ -1,55 +0,0 @@ -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/Mpris.qml b/Bar/Center/Mpris.qml deleted file mode 100644 index 51adfe2..0000000 --- a/Bar/Center/Mpris.qml +++ /dev/null @@ -1,62 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import Quickshell.Services.Mpris -import "../../Color.js" as Colors -import "../../Components/" -import "../../Services/" - -Item { - id: root - - property MprisPlayer player: MprisManager.defaultPlayer - - Rectangle { - color: Colors.red - radius: 5 - - anchors { - bottomMargin: 15 - fill: parent - leftMargin: 10 - rightMargin: parent.width - ((parent.width * MprisManager.pos) - 10) - topMargin: 15 - } - } - - RowLayout { - anchors { - bottomMargin: 2 - fill: parent - leftMargin: 7 - rightMargin: 7 - topMargin: 2 - } - - Item { - Layout.fillHeight: true - Layout.preferredWidth: height - - Image { - anchors.fill: parent - fillMode: Image.PreserveAspectFit - source: MprisManager.defaultPlayer.trackArtUrl - } - } - - Item { - Layout.fillHeight: true - Layout.fillWidth: true - - StyledText { - elide: Qt.ElideRight - horizontalAlignment: Qt.AlignHCenter - text: MprisManager.defaultPlayer.trackArtist + " - " + MprisManager.defaultPlayer.trackTitle - verticalAlignment: Qt.AlignVCenter - - anchors { - fill: parent - } - } - } - } -} diff --git a/Bar/Center/NotificationDisplay.qml b/Bar/Center/NotificationDisplay.qml deleted file mode 100644 index 65a27e3..0000000 --- a/Bar/Center/NotificationDisplay.qml +++ /dev/null @@ -1,49 +0,0 @@ -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 - maximumLineCount: 2 - text: root.body - verticalAlignment: Text.AlignVCenter - width: root.width - 20 - wrapMode: Text.WordWrap - - font { - pointSize: 10 - weight: 500 - } - } - } -} diff --git a/Bar/Center/NotificationList.qml b/Bar/Center/NotificationList.qml deleted file mode 100644 index c6372c9..0000000 --- a/Bar/Center/NotificationList.qml +++ /dev/null @@ -1,307 +0,0 @@ -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 - } - } - ] - - 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 - - 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 - } - } - ] - - StyledText { - color: Colors.surface0 - font.pointSize: 15 - horizontalAlignment: Qt.AlignRight - text: "󰩹" - verticalAlignment: Qt.AlignVCenter - - anchors { - fill: parent - rightMargin: 14 - } - } - - 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/Bar/Left.qml b/Bar/Left.qml deleted file mode 100644 index 99ff888..0000000 --- a/Bar/Left.qml +++ /dev/null @@ -1,70 +0,0 @@ -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" -import "./Left/" - -Container { - id: root - - property double extraHoveredWidth: 0 - required property HyprlandMonitor monitor - property double rowLeftMargin: 4 - property double rowSpacing: 3 - property double rowYmargin: 3 - property double selectorFocusedExtra: 15 - property double selectorHeight: 20 - property double selectorWidth: 20 - property double spacing: 3 - property double workspaceNumber: WorkspaceManager.getNumberOfWorkspaces(monitor) - - boxHeight: 25 - boxWidth: (selectorWidth * workspaceNumber) + ((2 * rowLeftMargin) + (rowSpacing * ( - workspaceNumber - 1))) + extraHoveredWidth + (hoverHandler.hovered == true - ? selectorFocusedExtra : 0) - exclusiveMonitor: monitor - - defaultItem: WorkspaceContainer { - extraHoveredWidth: root.extraHoveredWidth - monitor: root.monitor - rowLeftMargin: root.rowLeftMargin - rowSpacing: root.rowSpacing - rowYmargin: root.rowYmargin - selectorFocusedExtra: root.selectorFocusedExtra - selectorHeight: root.selectorHeight - selectorWidth: root.selectorWidth - } - Behavior on rowYmargin { - SpringAnimation { - damping: 0.3 - spring: 4 - } - } - states: [ - State { - name: "" - when: !hoverHandler.hovered - }, - State { - name: "hovered" - when: hoverHandler.hovered - - PropertyChanges { - root.boxHeight: 40 - root.extraHoveredWidth: 25 - root.rowYmargin: 5 - root.selectorWidth: 40 - } - } - ] - - HoverHandler { - id: hoverHandler - } -} diff --git a/Bar/Left/WorkspaceContainer.qml b/Bar/Left/WorkspaceContainer.qml deleted file mode 100644 index 65ccc38..0000000 --- a/Bar/Left/WorkspaceContainer.qml +++ /dev/null @@ -1,86 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import Quickshell -import Quickshell.Hyprland -import "../../Color.js" as Colors -import "../../Components/" -import "../../Services/" - -Item { - id: root - - required property double extraHoveredWidth - required property HyprlandMonitor monitor - required property double rowLeftMargin - required property double rowSpacing - required property double rowYmargin - required property double selectorFocusedExtra - required property double selectorHeight - required property double selectorWidth - - Item { - id: newWorkspaceButton - - implicitWidth: 20 - opacity: root.extraHoveredWidth > 0 ? 1 : 0 - - Behavior on opacity { - NumberAnimation { - duration: 100 - } - } - - TapHandler { - id: tapHandler - - onTapped: WorkspaceManager.activateNextFreeWorkspace() - } - - anchors { - bottom: parent.bottom - bottomMargin: 5 + root.rowYmargin - right: parent.right - rightMargin: 4 - top: parent.top - topMargin: 5 + root.rowYmargin - } - - Rectangle { - anchors.fill: parent - color: Colors.green - radius: 4 - - StyledText { - anchors.centerIn: parent - text: "+" - } - } - } - - Row { - spacing: root.rowSpacing - - anchors { - bottom: parent.bottom - bottomMargin: root.rowYmargin - left: parent.left - leftMargin: root.rowLeftMargin - top: parent.top - topMargin: root.rowYmargin - } - - Repeater { - model: WorkspaceManager.getWorkspacesForMonitor(root.monitor) - - WorkspaceSelector { - required property HyprlandWorkspace modelData - - extraHoveredWidth: root.extraHoveredWidth - selectorFocusedExtra: root.selectorFocusedExtra - selectorHeight: root.selectorHeight - selectorWidth: root.selectorWidth - workspace: modelData - } - } - } -} diff --git a/Bar/Left/WorkspaceSelector.qml b/Bar/Left/WorkspaceSelector.qml deleted file mode 100644 index 3111d51..0000000 --- a/Bar/Left/WorkspaceSelector.qml +++ /dev/null @@ -1,87 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Quickshell -import Quickshell.Hyprland -import "../../Color.js" as Colors -import "../../Components/" -import "../../Services/" - -Item { - id: root - - required property double extraHoveredWidth - property bool hovered: extraHoveredWidth > 0 ? true : false - property color selectorColor: Colors.surface2 - required property double selectorFocusedExtra - required property double selectorHeight - required property double selectorWidth - required property HyprlandWorkspace workspace - property string workspaceState: WorkspaceManager.getWorkspaceState(workspace) - - implicitWidth: selectorWidth + (root.hovered == true ? (root.workspaceState == "focused" - ? selectorFocusedExtra : 0) : 0) - - Behavior on implicitWidth { - SpringAnimation { - damping: 0.3 - spring: 4 - } - } - states: [ - State { - name: "" - when: root.workspaceState == "inactive" - - PropertyChanges { - root.selectorColor: Colors.surface2 - } - }, - State { - name: "active" - when: root.workspaceState == "active" - - PropertyChanges { - root.selectorColor: Colors.overlay2 - } - }, - State { - name: "focused" - when: root.workspaceState == "focused" - - PropertyChanges { - root.selectorColor: Colors.lavender - } - } - ] - - TapHandler { - id: tapHandler - - onTapped: root.workspace.activate() - } - - anchors { - bottom: parent.bottom - top: parent.top - } - - Rectangle { - color: root.selectorColor - radius: 6 - - Behavior on color { - ColorAnimation { - duration: 100 - } - } - - anchors { - fill: parent - } - - CenteredText { - font.pointSize: 10.5 - text: root.workspace.id - } - } -} diff --git a/Bar/Network.qml b/Bar/Network.qml new file mode 100644 index 0000000..8bfc485 --- /dev/null +++ b/Bar/Network.qml @@ -0,0 +1,161 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland + +import "../Color.js" as Colors +import "../Components/" +import "../Services/" + +Container { + id: root + + required property HyprlandMonitor monitor + property double networkDisplayHeight: 80 + property bool opened: false + + animOffset: 250 + boxColor: Colors.red + boxHeight: 25 + boxWidth: 33 + defaultItem: icon + exclusiveMonitor: root.monitor + exclusiveToScreen: true + forceHidden: !NetworkManager.connectedWifi + + states: [ + State { + name: "closed" + when: root.hovered == false && root.opened == false + + PropertyChanges { + root.boxHeight: 25 + root.boxRadius: 9 + root.boxWidth: 33 + } + }, + State { + name: "hovered" + when: root.hovered == true && root.opened == false + + PropertyChanges { + root.boxHeight: 28 + root.boxWidth: 35 + } + }, + State { + name: "opened" + when: root.opened == true && root.hovered == true + + PropertyChanges { + root.boxHeight: root.networkDisplayHeight + root.boxRadius: 12 + root.boxWidth: 350 + } + + StateChangeScript { + script: { + stack.replace(wifiInfo); + } + } + } + ] + + hover.onHoveredChanged: { + if (hover.hovered == false) { + root.opened = false; + if (root.state != "icon") { + root.stack.replace(icon); + } + } + } + tap.onTapped: { + root.opened = true; + } + + Component { + id: icon + + StyledText { + horizontalAlignment: Qt.AlignCenter + propo: true + text: NetworkManager.getNetworkDetails(NetworkManager.defaultAdapter)["icon"] || "󰤭" + verticalAlignment: Qt.AlignVCenter + } + } + + Component { + id: wifiInfo + + Item { + id: networkDisplayRoot + + property double innerMargin: 5 + property double outerMargin: 6 + + Rectangle { + id: networkDisplayBg + + anchors.fill: parent + anchors.margins: networkDisplayRoot.outerMargin + color: Colors.surface1 + radius: 8 + + RowLayout { + anchors.fill: parent + + Rectangle { + id: iconBox + + property double iconBoxRadius: 5 + + Layout.fillHeight: true + Layout.margins: networkDisplayRoot.innerMargin + color: Colors.red + implicitWidth: root.networkDisplayHeight - (networkDisplayRoot.innerMargin * 2) - ( + networkDisplayRoot.outerMargin * 2) + radius: 5 + + CenteredText { + fontSize: 23 + propo: true + text: NetworkManager.getNetworkDetails(NetworkManager.defaultAdapter)["icon"] + } + } + + Column { + id: networkColumn + + property var networkInfo: NetworkManager.getNetworkDetails(NetworkManager.defaultAdapter) + + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + + StyledText { + color: Colors.text + fontSize: 14 + text: networkColumn.networkInfo["networkName"] + } + + StyledText { + color: Colors.subtext1 + fontSize: 12 + fontWeight: 5 + text: "Connected on: " + networkColumn.networkInfo["adapterName"] + } + + StyledText { + property var timeInfo: networkColumn.networkInfo["time"] + + color: Colors.subtext1 + fontSize: 12 + fontWeight: 5 + text: "Network Details " + (networkColumn.networkInfo["saved"] == true ? "Saved" : "Not Saved") + } + } + } + } + } + } +} diff --git a/Bar/Right.qml b/Bar/Right.qml deleted file mode 100644 index 093d19d..0000000 --- a/Bar/Right.qml +++ /dev/null @@ -1,27 +0,0 @@ -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" -import "./Right/" - -Row { - id: root - - required property HyprlandMonitor monitor - - spacing: 10 - - Battery { - exclusiveMonitor: root.monitor - } - - Bluetooth { - exclusiveMonitor: root.monitor - } -} diff --git a/Bar/Right/Battery.qml b/Bar/Right/Battery.qml deleted file mode 100644 index 3b5f605..0000000 --- a/Bar/Right/Battery.qml +++ /dev/null @@ -1,28 +0,0 @@ -pragma ComponentBehavior: Bound -import QtQuick -import Quickshell -import Quickshell.Services.UPower -import "../../Color.js" as Colors -import "../../Components/" -import "../../Services/" -import "Bluetooth" - -Container { - id: root - - animOffset: 160 - boxColor: Colors.green - boxHeight: 25 - boxWidth: 40 - defaultItem: icon - exclusiveToScreen: true - forceHidden: BatteryManager.battery.isLaptopBattery - - Component { - id: icon - - CenteredText { - text: BatteryManager.batteryPercentage - } - } -} diff --git a/Bar/Right/Bluetooth.qml b/Bar/Right/Bluetooth.qml deleted file mode 100644 index a3df4db..0000000 --- a/Bar/Right/Bluetooth.qml +++ /dev/null @@ -1,98 +0,0 @@ -pragma ComponentBehavior: Bound -import QtQuick -import Quickshell -import Quickshell.Bluetooth -import "../../Color.js" as Colors -import "../../Components/" -import "../../Services/" -import "Bluetooth" - -Container { - id: root - - property bool shown: false - - animOffset: 200 - boxColor: Colors.peach - boxHeight: 25 - boxWidth: 30 - defaultItem: icon - exclusiveToScreen: true - forceHidden: !(root.shown || BluetoothManager.getConnected()) - hoverableWhenHidden: true - state: "" - - states: [ - State { - name: "" - - PropertyChanges { - root.boxHeight: 25 - root.boxWidth: 30 - } - - StateChangeScript { - script: { - root.stack.replace(icon); - } - } - }, - State { - name: "expanded" - - PropertyChanges { - root.boxHeight: 250 - root.boxRadius: 20 - root.boxWidth: 350 - } - - StateChangeScript { - script: { - root.stack.replace(expanded); - root.shown = true; - } - } - } - ] - - hover.onHoveredChanged: { - if (hover.hovered == false) { - root.state = ""; - hoverIntervalTimer.start(); - } else { - root.shown = true; - hoverIntervalTimer.start(); - } - } - tap.onTapped: state = "expanded" - - Timer { - id: hoverIntervalTimer - - interval: 500 - repeat: false - running: false - - onTriggered: { - if (root.hover.hovered == false) { - root.shown = false; - } - } - } - - Component { - id: icon - - Item { - CenteredText { - text: BluetoothManager.getConnected() == true ? "󰂱" : "󰂲" - } - } - } - - Component { - id: expanded - - Expanded {} - } -} diff --git a/Bar/Right/Bluetooth/Expanded.qml b/Bar/Right/Bluetooth/Expanded.qml deleted file mode 100644 index 71b4f61..0000000 --- a/Bar/Right/Bluetooth/Expanded.qml +++ /dev/null @@ -1,384 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import Quickshell.Bluetooth -import "../../../Color.js" as Colors -import "../../../Components/" -import "../../../Services/" - -Item { - id: root - - property double topBarHeight: 35 - - Rectangle { - color: Colors.surface0 - radius: 17 - - anchors { - bottomMargin: 5 - fill: parent - leftMargin: 5 - rightMargin: 5 - topMargin: 5 - } - - Rectangle { - id: topBar - - property double topBarHeight: root.topBarHeight - - color: Colors.peach - implicitHeight: topBarHeight - radius: 9 - - anchors { - left: parent.left - leftMargin: 5 - right: parent.right - rightMargin: 5 - top: parent.top - topMargin: 5 - } - - RowLayout { - id: topBarRow - - property double topBarRowPadding: 4 - - spacing: 3 - - anchors { - left: parent.left - leftMargin: topBarRowPadding - right: parent.right - rightMargin: topBarRowPadding - top: parent.top - topMargin: topBarRowPadding - } - - Rectangle { - Layout.fillWidth: true - // color: Qt.lighter(Colors.peach, 1.2) - color: Colors.surface1 - implicitHeight: topBar.topBarHeight - (topBarRow.topBarRowPadding * 2) - radius: 6 - - StyledText { - anchors.verticalCenter: parent.verticalCenter - color: Colors.text - horizontalAlignment: Qt.AlignLeft - text: " " + BluetoothManager.defaultAdapterName - } - } - - Rectangle { - id: scanButton - - color: Qt.lighter(Colors.peach, 1.15) - implicitHeight: topBar.topBarHeight - (topBarRow.topBarRowPadding * 2) - implicitWidth: topBar.topBarHeight - (topBarRow.topBarRowPadding * 2) - radius: 6 - - Behavior on color { - ColorAnimation { - duration: 100 - } - } - states: [ - State { - name: "hovered" - - PropertyChanges { - scanButton.color: Colors.green - } - } - ] - - border { - color: Colors.surface0 - width: 2 - } - - HoverHandler { - onHoveredChanged: hovered == true ? scanButton.state = "hovered" : scanButton.state = "" - } - - TapHandler { - onTapped: { - BluetoothManager.toggleDiscover(); - } - } - - CenteredText { - id: scanButtonText - - text: "" - - NumberAnimation { - id: animateRotation - - duration: 1000 - from: 0 - loops: Animation.Infinite - properties: "rotation" - running: BluetoothManager.defaultAdapter.discovering - target: scanButtonText - to: 360 - - onStopped: { - scanButton.rotation = 0; - } - } - } - } - } - } - - ListView { - boundsBehavior: Flickable.StopAtBounds - clip: true - model: BluetoothManager.getDevicesList() - spacing: 5 - - delegate: Item { - id: deviceRoot - - required property real index - required property BluetoothDevice modelData - - height: 55 - width: ListView.view.width - - Rectangle { - id: containerBox - - anchors.fill: parent - clip: true - color: Colors.peach - radius: 7 - - Rectangle { - anchors.centerIn: parent - color: Colors.green - implicitHeight: parent.height - 10 - implicitWidth: parent.width * (deviceRoot.modelData.batteryAvailable == true - ? deviceRoot.modelData.battery : 0) - 10 - opacity: 0.7 - radius: 7 - visible: deviceRoot.modelData.batteryAvailable == true - } - - RowLayout { - anchors { - bottomMargin: 4 - fill: parent - leftMargin: 4 - rightMargin: 4 - topMargin: 4 - } - - Rectangle { - Layout.fillHeight: true - Layout.preferredWidth: height - color: deviceRoot.modelData.connected ? Colors.green : Qt.lighter(Colors.peach, 1.25) - radius: 5 - - CenteredText { - color: Colors.surface0 - font.pointSize: 30 - text: BluetoothManager.getIcon(deviceRoot.modelData.icon) - } - } - - ColumnLayout { - Layout.fillWidth: true - - Rectangle { - Layout.fillHeight: true - Layout.fillWidth: true - color: Qt.lighter(Colors.peach, 1.15) - radius: 4 - - StyledText { - anchors.fill: parent - anchors.verticalCenter: parent.verticalCenter - elide: Qt.ElideRight - text: deviceRoot.modelData.name - } - } - - Rectangle { - Layout.fillHeight: true - Layout.fillWidth: true - color: "transparent" - - StyledText { - anchors.fill: parent - anchors.verticalCenter: parent.verticalCenter - color: Colors.surface2 - font.weight: 300 - text: deviceRoot.modelData.address - } - } - } - - Item { - Layout.fillHeight: true - Layout.preferredWidth: height - - RowLayout { - anchors.fill: parent - - Rectangle { - id: itemEntry - - Layout.fillHeight: true - Layout.fillWidth: true - color: Colors.red - radius: 3 - - Loader { - id: deleteButtonLoader - - anchors.fill: parent - sourceComponent: forgetButton - - states: [ - State { - name: "" - when: deviceRoot.modelData.connected == false - - PropertyChanges { - deleteButtonLoader.sourceComponent: forgetButton - } - }, - State { - name: "connected" - when: deviceRoot.modelData.connected == true - - PropertyChanges { - deleteButtonLoader.sourceComponent: disconnectButton - } - } - ] - } - - Component { - id: disconnectButton - - Item { - anchors.fill: parent - - TapHandler { - onTapped: deviceRoot.modelData.disconnect() - } - - CenteredText { - text: "" - } - } - } - - Component { - id: forgetButton - - Item { - anchors.fill: parent - - TapHandler { - onTapped: deviceRoot.modelData.forget() - } - - CenteredText { - text: "󰩹" - } - } - } - } - - Rectangle { - id: greenButton - - property Component loadedItem: connectButton - - Layout.fillHeight: true - Layout.fillWidth: true - color: Colors.green - radius: 3 - - states: [ - State { - name: "" - when: deviceRoot.modelData.paired == true - - PropertyChanges { - greenButton.color: Colors.green - greenButton.loadedItem: connectButton - } - }, - State { - name: "unpaired" - when: deviceRoot.modelData.paired == false - - PropertyChanges { - greenButton.color: Colors.blue - greenButton.loadedItem: pairButton - } - } - ] - - Loader { - anchors.fill: parent - sourceComponent: greenButton.loadedItem - } - - Component { - id: connectButton - - Item { - anchors.fill: parent - - CenteredText { - text: "󱘖" - } - - TapHandler { - onTapped: deviceRoot.modelData.connect() - } - } - } - - Component { - id: pairButton - - Item { - anchors.fill: parent - - CenteredText { - text: "" - } - - TapHandler { - onTapped: { - deviceRoot.modelData.pair(); - } - } - } - } - } - } - } - } - } - } - - anchors { - bottomMargin: 5 - fill: parent - leftMargin: 5 - rightMargin: 5 - topMargin: 10 + root.topBarHeight - } - } - } -} diff --git a/Bar/Time.qml b/Bar/Time.qml new file mode 100644 index 0000000..1768ea9 --- /dev/null +++ b/Bar/Time.qml @@ -0,0 +1,337 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland +import Quickshell.Services.Notifications +import Quickshell.Widgets + +import "../Color.js" as Colors +import "../Components/" +import "../Services/" + +Container { + id: root + + property Notification latestNotif + required property HyprlandMonitor monitor + property bool mpris: false + property double mprisHeight: 60 + property double mprisWidth: 350 + property double notificationHeight: 100 + property double notificationWidth: 350 + property bool notified: false + property bool overriden: false + + boxColor: Colors.mauve + defaultItem: time + exclusiveMonitor: root.monitor + exclusiveToScreen: true + + states: [ + State { + name: "closed" + when: root.hovered == false && root.overriden == false + + PropertyChanges { + root.boxHeight: 28 + root.boxRadius: 9 + root.boxWidth: 100 + } + + StateChangeScript { + script: { + if (!root.stack.currentItem) { + return; + } + if (root.stack.currentItem.component != "time") { + root.stack.replace(time); + } + } + } + }, + State { + name: "hovered" + when: root.hovered == true && root.overriden == false + + PropertyChanges { + root.boxHeight: MprisManager.getPlaying() == false ? 32 : root.mprisHeight + root.boxRadius: 12 + root.boxWidth: MprisManager.getPlaying() == false ? 107 : root.mprisWidth + } + + StateChangeScript { + script: { + root.stack.replace(MprisManager.getPlaying() == true ? mprisToast : time); + } + } + }, + State { + name: "notified" + when: root.notified == true + + PropertyChanges { + root.boxHeight: root.notificationHeight + root.boxRadius: 12 + root.boxWidth: root.notificationWidth + root.visibleTopMargin: 5 + } + + StateChangeScript { + script: { + root.stack.replace(notification); + notificationTimer.restart(); + } + } + }, + State { + name: "mpris" + when: root.mpris == true + + PropertyChanges { + root.boxHeight: root.mprisHeight + root.boxRadius: 12 + root.boxWidth: root.mprisWidth + root.visibleTopMargin: 5 + } + + StateChangeScript { + script: { + root.stack.replace(mprisToast); + mprisTimer.restart(); + } + } + } + ] + + Connections { + function onNewNotification() { + notification => { + if (notification.lastGeneration == false) { + root.latestNotif = notification; + root.overriden = true; + root.notified = true; + } + }; + } + + target: NotificationManager + } + + Connections { + function onTrackChanged() { + root.overriden = true; + root.mpris = true; + console.log("track changed"); + } + + target: MprisManager.defaultPlayer + } + + Timer { + id: notificationTimer + + interval: 2000 + repeat: false + running: false + + onTriggered: { + root.notified = false; + root.overriden = false; + } + } + + Timer { + id: mprisTimer + + interval: 3000 + repeat: false + running: false + + onTriggered: { + root.mpris = false; + root.overriden = false; + } + } + + Component { + id: time + + StyledText { + property string component: "time" + + horizontalAlignment: Qt.AlignCenter + text: Time.time + verticalAlignment: Qt.AlignVCenter + } + } + + Component { + id: notification + + Item { + id: notificationRoot + + property double innerMargin: 5 + property double outerMargin: 6 + + function getInnerHeight() { + let fullHeight = root.notificationHeight; + let fullMargin = notificationRoot.innerMargin + notificationRoot.outerMargin; + + return fullHeight - (fullMargin * 2); + } + + Rectangle { + color: Colors.surface1 + radius: 9 + + anchors { + fill: parent + margins: notificationRoot.outerMargin + } + + RowLayout { + Rectangle { + Layout.margins: notificationRoot.innerMargin + color: Colors.mauve + implicitHeight: notificationRoot.getInnerHeight() + implicitWidth: notificationRoot.getInnerHeight() + radius: 5 + + IconImage { + anchors.fill: parent + anchors.margins: 10 + source: root.latestNotif.image + } + } + + ColumnLayout { + property double textMargin: 4 + + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + Layout.leftMargin: 0 + Layout.margins: notificationRoot.innerMargin + textMargin + + StyledText { + color: Colors.text + fontSize: 14 + text: root.latestNotif.summary + } + + StyledText { + Layout.fillHeight: true + Layout.maximumWidth: root.notificationWidth - ((notificationRoot.outerMargin * 2) + ( + notificationRoot.innerMargin * 3) + notificationRoot.getInnerHeight()) + color: Colors.subtext0 + elide: Qt.ElideRight + fontWeight: 5 + maximumLineCount: 2 + text: root.latestNotif.body + wrapMode: Text.WordWrap + } + } + } + } + } + } + + Component { + id: mprisToast + + Item { + id: mprisToastRoot + + property double innerMargin: 4 + property double outerMargin: 4 + property var trackInfo: MprisManager.getTrackInfo() + + function getInnerHeight() { + let fullHeight = root.mprisHeight; + let fullMargin = mprisToastRoot.innerMargin + mprisToastRoot.outerMargin; + + return fullHeight - (fullMargin * 2); + } + + Rectangle { + color: Colors.surface1 + radius: 9 + + anchors { + fill: parent + margins: mprisToastRoot.outerMargin + } + + RowLayout { + Rectangle { + Layout.margins: mprisToastRoot.innerMargin + color: Colors.mauve + implicitHeight: mprisToastRoot.getInnerHeight() + implicitWidth: mprisToastRoot.getInnerHeight() + radius: 5 + + IconImage { + anchors.fill: parent + source: mprisToastRoot.trackInfo["albumArt"] + } + } + + ColumnLayout { + property double textMargin: 1 + + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + Layout.leftMargin: 0 + Layout.margins: mprisToastRoot.innerMargin + textMargin + + StyledText { + Layout.maximumWidth: root.mprisWidth - ((mprisToastRoot.outerMargin * 2) + ( + mprisToastRoot.innerMargin * 5) + mprisToastRoot.getInnerHeight()) + color: Colors.text + fontSize: 14 + text: mprisToastRoot.trackInfo["name"] + " - " + mprisToastRoot.trackInfo["artist"] + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + Layout.maximumWidth: root.mprisWidth - ((mprisToastRoot.outerMargin * 2) + ( + mprisToastRoot.innerMargin * 5) + mprisToastRoot.getInnerHeight()) + Layout.preferredWidth: root.mprisWidth - ((mprisToastRoot.outerMargin * 2) + ( + mprisToastRoot.innerMargin * 5) + mprisToastRoot.getInnerHeight()) + + Rectangle { + anchors.fill: parent + color: Colors.overlay0 + radius: 100 + + Rectangle { + color: Colors.mauve + implicitWidth: parent.width * mprisToastRoot.trackInfo["lengthPercent"] + radius: 100 + + anchors { + bottom: parent.bottom + left: parent.left + top: parent.top + } + } + + StyledText { + text: MprisManager.getTimeString() + + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + } + } + } + } + } + } + } + } +} diff --git a/Bar/WorkspaceSelector.qml b/Bar/WorkspaceSelector.qml new file mode 100644 index 0000000..042e365 --- /dev/null +++ b/Bar/WorkspaceSelector.qml @@ -0,0 +1,108 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland + +import "../Color.js" as Colors +import "../Components/" +import "../Services/" + +Container { + id: root + + property double barHeight: 25 + required property HyprlandMonitor monitor + property double rowLeftMargin: 5 + property double workspaceButtonSpacing: 4 + property double workspaceCount: WorkspaceManager.getNumberOfWorkspaces(monitor) + property double workspaceWidth: 17 + + boxHeight: barHeight + boxRadius: 8 + boxWidth: (2 * rowLeftMargin) + (workspaceCount * workspaceWidth) + ((workspaceCount - 1) + * workspaceButtonSpacing) + defaultItem: placeholder + exclusiveMonitor: monitor + + states: [ + State { + name: "hovered" + when: root.hovered + + PropertyChanges { + root.barHeight: 30 + root.workspaceWidth: 20 + } + } + ] + + Component { + id: placeholder + + Item {} + } + + RowLayout { + spacing: root.workspaceButtonSpacing + + anchors { + bottom: parent.bottom + left: parent.left + leftMargin: root.rowLeftMargin + top: parent.top + } + + Repeater { + delegate: workspaceButton + delegateModelAccess: DelegateModel.ReadOnly + model: WorkspaceManager.getWorkspacesForMonitor(root.exclusiveMonitor) + } + + Component { + id: workspaceButton + + Item { + id: buttonRoot + + required property var modelData + property double stateInt: WorkspaceManager.getWorkspaceStateIndex(modelData) + property variant workspaceColors: [Colors.lavender, Colors.overlay2, Colors.surface2] + property string workspaceId: modelData.id + + Layout.alignment: Qt.AlignLeft + implicitHeight: root.barHeight - 7 + implicitWidth: root.workspaceWidth + + Behavior on implicitHeight { + SpringAnimation { + damping: 0.3 + spring: 4 + } + } + Behavior on implicitWidth { + SpringAnimation { + damping: 0.3 + spring: 4 + } + } + + TapHandler { + id: tapHandler + + onTapped: WorkspaceManager.activateWorkspaceById(buttonRoot.modelData.id) + } + + Rectangle { + anchors.fill: parent + color: buttonRoot.workspaceColors[buttonRoot.stateInt] + radius: 4 + + CenteredText { + text: buttonRoot.modelData.id + } + } + } + } + } +} diff --git a/Components/Container.qml b/Components/Container.qml index dfde525..611cd8e 100644 --- a/Components/Container.qml +++ b/Components/Container.qml @@ -9,6 +9,8 @@ import "../Color.js" as Colors Item { id: root + property bool _fullyHidden: false + property bool _widthCollapsed: false property double animOffset: 0 property color boxColor: Colors.surface0 property double boxHeight: 28 @@ -35,13 +37,15 @@ Item { property bool forceHidden: false property alias hover: hoverHandler property bool hoverableWhenHidden: false + property bool hovered: false property alias rect: container property alias stack: containerContent property alias tap: tapHandler property double visibleTopMargin: 0 implicitHeight: boxHeight - implicitWidth: boxWidth + implicitWidth: root._widthCollapsed ? 0 : root.boxWidth + visible: !root._fullyHidden Behavior on anchors.topMargin { animation: defaultCurve @@ -58,13 +62,49 @@ Item { spring: 4 } } - Behavior on boxWidth { + Behavior on implicitWidth { SpringAnimation { damping: 0.3 spring: 4 } } + onForceHiddenChanged: { + if (root.forceHidden == true) { + hideTimer.restart(); + } else { + hideTimer.stop(); + subHideTimer.stop(); + root._fullyHidden = false; + root._widthCollapsed = false; + } + } + + Timer { + id: hideTimer + + interval: 250 + repeat: false + running: false + + onTriggered: { + root._widthCollapsed = true; + subHideTimer.restart(); + } + } + + Timer { + id: subHideTimer + + interval: 200 + repeat: false + running: false + + onTriggered: { + root._fullyHidden = true; + } + } + anchors { top: parent.top topMargin: calculatedTopMargin @@ -79,22 +119,20 @@ Item { HoverHandler { id: hoverHandler + + onHoveredChanged: { + if (hoverHandler.hovered == true) { + root.hovered = true; + } else { + root.hovered = false; + } + } } 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 diff --git a/Components/MidpointGradient.qml b/Components/MidpointGradient.qml index 333c5fd..43fdb76 100644 --- a/Components/MidpointGradient.qml +++ b/Components/MidpointGradient.qml @@ -1,23 +1,75 @@ import QtQuick +import QtQuick.Shapes -Gradient { +Item { id: root - required property color color - property double midpoint: 0.6 + property real angle: 0 // Degrees; 0 = transparent left, colour right + readonly property real angleRadians: angle * Math.PI / 180.0 + property real blur: 0.0 // 0 = sharp, 1 = very soft - GradientStop { - color: "transparent" - position: 0.0 + // Public API + property color color: "#4da3ffff" + + // Half the diagonal ensures the gradient covers the entire item. + readonly property real gradientRadius: Math.sqrt(width * width + height * height) / 2.0 + property real midpoint: 0.5 // 0..1 + property double radius: 10 + readonly property real safeBlur: Math.max(blur, 0.00001) + readonly property real safeMidpoint: clamp01(midpoint) + readonly property real transitionEnd: clamp01(safeMidpoint + safeBlur / 2.0) + readonly property real transitionStart: clamp01(safeMidpoint - safeBlur / 2.0) + readonly property color transparentColor: Qt.rgba(color.r, color.g, color.b, 0.0) + + function clamp01(value) { + return Math.max(0.0, Math.min(1.0, value)); } - GradientStop { - color: "transparent" - position: root.midpoint + Behavior on color { + ColorAnimation { + duration: 100 + } } - GradientStop { - color: root.color - position: 1.0 + Shape { + anchors.fill: parent + preferredRendererType: Shape.CurveRenderer + + ShapePath { + strokeWidth: -1 + + fillGradient: LinearGradient { + x1: root.width / 2.0 - root.gradientRadius * Math.cos(root.angleRadians) + x2: root.width / 2.0 + root.gradientRadius * Math.cos(root.angleRadians) + y1: root.height / 2.0 - root.gradientRadius * Math.sin(root.angleRadians) + y2: root.height / 2.0 + root.gradientRadius * Math.sin(root.angleRadians) + + GradientStop { + color: root.transparentColor + position: 0.0 + } + + GradientStop { + color: root.transparentColor + position: root.transitionStart + } + + GradientStop { + color: root.color + position: root.transitionEnd + } + + GradientStop { + color: root.color + position: 1.0 + } + } + + PathRectangle { + height: root.height + radius: Math.min(root.radius, Math.min(width, height) / 2.0) + width: root.width + } + } } } diff --git a/Components/StyledText.qml b/Components/StyledText.qml index 4da4ce9..3618bb4 100644 --- a/Components/StyledText.qml +++ b/Components/StyledText.qml @@ -2,12 +2,18 @@ import QtQuick import "../Color.js" as Colors Text { + id: root + + property double fontSize: 12 + property double fontWeight: 8 + property bool propo: false + color: Colors.base elide: Text.ElideRight font { - family: "FiraMono Nerd Font" - pointSize: 12 - weight: 800 + family: root.propo == true ? "FiraMono Nerd Font Propo" : "FiraMono Nerd Font" + pointSize: fontSize + weight: root.fontWeight * 100 } } diff --git a/Services/BatteryManager.qml b/Services/BatteryManager.qml index c99c28c..c8cc381 100644 --- a/Services/BatteryManager.qml +++ b/Services/BatteryManager.qml @@ -3,21 +3,160 @@ import QtQuick import Quickshell import Quickshell.Services.UPower +import "../Color.js" as Colors + Singleton { id: root - property UPowerDevice battery: Upower.displayDevice - property bool batteryInUse: root.batteryPresent && root.batteryLaptop - property bool batteryLaptop: root.battery.isLaptopBattery - property double batteryPercentage: root.battery.percentage - property bool batteryPresent: root.battery.ready + property double batteryPercentage: root.mainBattery.percentage || 0 + property bool batteryPresent: root.mainBattery.ready || false + property UPowerDevice mainBattery: UPower.displayDevice || undefined + property bool hasLaptopBattery: root.mainBattery && root.mainBattery.isLaptopBattery - function getBatteryIcon() { - let battery = root.battery; - if (battery.state = UpowerDeviceState.Charging) { - return "󰂄"; - } - if (battery.state = UpowerDeviceState.Discharging) { + + + function getBatteryColor() { + let criticalLevel = 20; + let batteryPercentage = getBatteryPercentage(mainBattery); + + if (mainBattery.state == UPowerDeviceState.FullyCharged) { + return Colors.sky; + } else if (mainBattery.state == UPowerDeviceState.Charging) { + return Colors.mauve; + } else if (mainBattery.state == UPowerDeviceState.PendingCharge | mainBattery.state + == UPowerDeviceState.PendingDischarge) { + return Colors.peach; + } else if (batteryPercentage <= criticalLevel) { + return Colors.red; + } else { + return Colors.green; } } + + function getBatteryIcon(battery) { + if (battery.state == UPowerDeviceState.Charging) { + return "󰂄"; + } else if (battery.state == UPowerDeviceState.PendingCharge | battery.state + == UPowerDeviceState.PendingDischarge) { + return "󰚥"; + } else if (battery.state == UPowerDeviceState.Discharging | battery.state + == UPowerDeviceState.FullyCharged) { + let iconArr = ["󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"]; + let batteryCharge = battery.percentage; + let chargeIndex = Math.ceil(batteryCharge * iconArr.length) - 1; + return iconArr[chargeIndex]; + } + } + + function getBatteryName(battery) { + let pathsplit = battery.nativePath.split("/"); + let batteryName = pathsplit[pathsplit.length]; + return pathsplit; + } + + function getBatteryPercentage(battery) { + return battery.percentage * 100; + } + + function getBatteryText() { + let icon = getBatteryIcon(mainBattery); + let percentage = getBatteryPercentage(mainBattery); + return icon + " " + percentage + "%"; + } + + function getChargingRateFormat() { + let rate = mainBattery.changeRate.toFixed(1); + + let finalString = "harging at " + rate + "W"; + if (rate == 0) { + finalString = "Battery Idle"; + } else if (rate <= 0 || mainBattery.state == UPowerDeviceState.Charging) { + finalString = "C" + finalString; + } else { + finalString = "Disc" + finalString; + } + return finalString; + } + + function getMainBatteryInfo() { + let battery = UPower.devices.values[0]; + let batteryState = battery.state; + let batteryName = getBatteryName(battery); + let time = getTimeFormat(); + return { + "name": batteryName, + "charge": battery.percentage, + "energy": battery.energy.toFixed(1), + "capacity": battery.energyCapacity.toFixed(1), + "health": battery.healthPercentage + }; + } + + function getTimeFormat() { + let state = mainBattery.state; + let finalString = ""; + if (state == UPowerDeviceState.FullyCharged) { + finalString += "Battery Full"; + } else if (state == UPowerDeviceState.Charging) { + let time = mainBattery.timeToFull; + if (time == 0) { + return "Getting Time..."; + } else { + let timeArr = secondsToTime(time); + // If more than 1 hour + if (timeArr.hours >= 1 || timeArr.hourNeedsIncrement == 1) { + finalString += timeArr.Hours + " Hours "; + // If minutes not 0 + if (timeArr.minutesRounded > 1) { + finalString += timeArr.minutesRounded + " Minutes"; + } + } else { + finalString += timeArr.minutes + " Minutes "; + finalString += timeArr.seconds + " Seconds"; + } + } + finalString += " Until Full"; + } else if (state == UPowerDeviceState.Discharging) { + let time = mainBattery.timeToEmpty; + if (time == 0) { + return "Getting Time..."; + } else { + let timeArr = secondsToTime(time); + // If more than 1 hour + if (timeArr.hours >= 1 || timeArr.hourNeedsIncrement == 1) { + finalString += timeArr.hours + " Hours "; + // If minutes not 0 + if (timeArr.minutesRounded > 1) { + finalString += timeArr.minutesRounded + " Minutes"; + } + } else { + finalString += timeArr.minutes + " Minutes "; + finalString += timeArr.seconds + " Seconds"; + } + } + finalString += " Remaining"; + } else { + finalString += "Getting Time..."; + } + return finalString; + } + + function secondsToTime(totalSeconds) { + totalSeconds = Math.max(0, Number(totalSeconds)); + + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = Math.floor(totalSeconds % 60); + + const roundedMinutes = minutes + (seconds >= 30 ? 1 : 0); + const hourNeedsIncrement = roundedMinutes === 60; + + return { + hours, + minutes, + seconds, + minutesRounded: hourNeedsIncrement ? 0 : roundedMinutes, + hourNeedsIncrement + }; + } } diff --git a/Services/BluetoothManager.qml b/Services/BluetoothManager.qml index 41f8a1d..92bdd20 100644 --- a/Services/BluetoothManager.qml +++ b/Services/BluetoothManager.qml @@ -6,21 +6,59 @@ import Quickshell.Bluetooth Singleton { id: root - property BluetoothAdapter defaultAdapter: Bluetooth.defaultAdapter - property string defaultAdapterName: Bluetooth.defaultAdapter.adapterId + property BluetoothAdapter defaultAdapter: Bluetooth.defaultAdapter || null + property string defaultAdapterName: defaultAdapter ? defaultAdapter.adapterId : "" + + property bool anyConnected: root.defaultAdapter != null + && root.defaultAdapter.devices.values.some(device => device.connected) function getConnected() { - let deviceList = getDevicesList(); - for (let device in deviceList) { - let currentDevice = deviceList[device]; - if (currentDevice.connected == true) { - return true; + return root.anyConnected; + } + + function getConnectedDevicesList() { + let devicesList = getDevicesList(); + if (!devicesList) { + return []; + } + let connectedList = []; + for (let device of devicesList) { + if (device.connected == true) { + connectedList.push(device); } } - return false; + return connectedList; + } + + function getDeviceText(device) { + if (!device) { + return undefined; + } + let deviceName = device.deviceName; + let deviceIcon = getIcon(device.icon); + let deviceMac = device.address; + let hasBattery = device.batteryAvailable; + let deviceBattery = deviceIcon; + let deviceBatteryRaw = 0; + if (hasBattery) { + deviceBattery = device.battery * 100; + deviceBatteryRaw = device.battery; + } + + return { + "icon": deviceIcon, + "name": deviceName, + "mac": deviceMac, + "hasBattery": hasBattery, + "battery": deviceBattery, + "batteryRaw": deviceBatteryRaw + }; } function getDevicesList() { + if (!root.defaultAdapter) { + return []; + } return root.defaultAdapter.devices.values; } @@ -41,29 +79,4 @@ Singleton { return icons[name] || "󰾰"; } - - function pairTrustConnect(device) { - device.pair(); - device.trusted = true; - device.connect(); - } - - function toggleDiscover() { - if (defaultAdapter.discovering == true) { - defaultAdapter.discovering = false; - } else { - defaultAdapter.discovering = true; - discoveringTimeout.running = true; - } - } - - Timer { - id: discoveringTimeout - - interval: 15000 - repeat: false - running: false - - onTriggered: defaultAdapter.discovering = false - } } diff --git a/Services/MprisManager.qml b/Services/MprisManager.qml index 95cf458..089bb50 100644 --- a/Services/MprisManager.qml +++ b/Services/MprisManager.qml @@ -6,8 +6,7 @@ import Quickshell.Services.Mpris Singleton { id: root - property MprisPlayer defaultPlayer: Mpris.players.values[0] - property real pos: defaultPlayer.position / defaultPlayer.length + property MprisPlayer defaultPlayer: Mpris.players.values[0] || null function getPlaying() { if (!defaultPlayer) { @@ -17,6 +16,55 @@ Singleton { } } + function getTimeString() { + let trackInfo = getTrackInfo(); + let position = trackInfo["position"]; + let length = trackInfo["length"]; + if (length.hours > 0) { + return position.hours + ":" + position.minutes + ":" + position.seconds + "/" + length.hours + + ":" + length.minutes + ":" + length.seconds; + } else { + return position.minutes + ":" + position.seconds + "/" + length.minutes + ":" + length.seconds; + } + } + + function getTrackInfo() { + let trackName = root.defaultPlayer.trackTitle; + let trackArtist = root.defaultPlayer.trackArtist; + let trackAlbum = root.defaultPlayer.trackAlbum; + let trackPosition = root.defaultPlayer.position; + let trackLength = root.defaultPlayer.length; + let trackArt = root.defaultPlayer.trackArtUrl; + return { + "name": trackName, + "artist": trackArtist, + "album": trackAlbum, + "position": msToArray(trackPosition), + "length": msToArray(trackLength), + "lengthRemaining": msToArray(trackLength - trackPosition), + "lengthPercent": trackPosition / trackLength, + "albumArt": trackArt + }; + } + + function msToArray(time) { + time = Math.max(0, Number(time)); + + const inthours = Math.floor(time / 3600000); + const intminutes = Math.floor((time % 60000) / 60); + const intseconds = Math.floor((time % 1000) % 60); + + const hours = String(inthours).padStart(2, "0"); + const minutes = String(intminutes).padStart(2, "0"); + const seconds = String(intseconds).padStart(2, "0"); + + return { + hours, + minutes, + seconds + }; + } + function prev() { defaultPlayer.previous(); } diff --git a/Services/NetworkManager.qml b/Services/NetworkManager.qml new file mode 100644 index 0000000..f7ee42c --- /dev/null +++ b/Services/NetworkManager.qml @@ -0,0 +1,99 @@ +pragma Singleton +import QtQuick +import Quickshell +import Quickshell.Networking + +Singleton { + id: root + + property bool connectedWifi: root.defaultAdapter != null && root.defaultAdapter.connected + && root.defaultAdapter.type == DeviceType.Wifi + property NetworkDevice defaultAdapter: Networking.devices.values[0] || null + + function getConnectedNetworks(networksArr) { + let connectedNetworks = []; + + for (let network of networksArr) { + if (network.state == ConnectionState.Connected) { + connectedNetworks.push(network); + } + } + return connectedNetworks; + } + + function getNetworkDetails(adapter) { + if (!adapter) { + return { + "adapterName": "", + "adapterConnected": false, + "adapterIsWifi": false, + "networkName": "", + "networkStrength": 0, + "icon": "󰤭" + }; + } + + let networksArr = adapter.networks.values; + let connectedNetworks = getConnectedNetworks(networksArr); + let deviceWifi = isDeviceWifi(adapter); + + let primaryNetwork = connectedNetworks[0]; + if (!primaryNetwork) { + return { + "adapterName": adapter.name, + "adapterConnected": adapter.connected, + "adapterIsWifi": deviceWifi, + "networkName": "No Network", + "networkStrength": 0, + "icon": "󰤭" + }; + } + + let networkName = primaryNetwork.name; + let networkStrength = primaryNetwork.signalStrength; + + let networkIcon = getWifiIcon(networkStrength); + + let outputDict = { + "adapterName": adapter.name, + "adapterConnected": adapter.connected, + "adapterIsWifi": deviceWifi, + "networkName": networkName, + "networkStrength": networkStrength, + "icon": networkIcon, + "saved": primaryNetwork.known + }; + return outputDict; + } + + function getWifiIcon(strength) { + let iconArr = ["󰤟", "󰤢", "󰤥", "󰤨"]; + let strengthIndex = Math.ceil(strength * iconArr.length); + return iconArr[strengthIndex - 1]; + } + + function getWifiText() { + let info = getNetworkDetails(defaultAdapter); + + let adapter = info["adapterName"]; + let name = info["networkName"]; + + let finalString = adapter + " - " + name; + return finalString; + } + + function isConnectedAndWifi() { + return root.connectedWifi; + } + + function isDeviceWifi(adapter) { + if (adapter.type == DeviceType.Wifi) { + return true; + } else if (adapter.type == DeviceType.Wired) { + return false; + } else { + console.log(adapter.type); + return false; + } + } +} diff --git a/Services/NotificationManager.qml b/Services/NotificationManager.qml index 02981e3..edbb714 100644 --- a/Services/NotificationManager.qml +++ b/Services/NotificationManager.qml @@ -2,12 +2,15 @@ pragma Singleton import QtQuick import Quickshell import Quickshell.Services.Notifications +import "../Color.js" as Colors Singleton { id: root property alias notif: server + signal newNotification(notificiation: Notification) + function getLatestNotification() { let notificationList = server.trackedNotifications.values; let len = notificationList.length; @@ -24,9 +27,11 @@ Singleton { actionsSupported: true bodyMarkupSupported: true bodySupported: true + imageSupported: true onNotification: notification => { notification.tracked = true; + root.newNotification(notification); } } } diff --git a/Services/WorkspaceManager.qml b/Services/WorkspaceManager.qml index d238bc9..8b1c685 100644 --- a/Services/WorkspaceManager.qml +++ b/Services/WorkspaceManager.qml @@ -62,6 +62,17 @@ Singleton { } } + function getWorkspaceStateIndex(workspace) { + let state = getWorkspaceState(workspace); + if (state == "focused") { + return 0; + } else if (state == "active") { + return 1; + } else if (state == "inactive") { + return 2; + } + } + function getWorkspacesForMonitor(monitor) { let allWorkspaces = getAllNumberedWorkspaces(); let monitorWorkspaces = []; diff --git a/Services/qmldir b/Services/qmldir index 6e2dbe3..54e051b 100644 --- a/Services/qmldir +++ b/Services/qmldir @@ -5,3 +5,4 @@ singleton WorkspaceManager 1.0 WorkspaceManager.qml singleton BluetoothManager 1.0 BluetoothManager.qml singleton MprisManager 1.0 MprisManager.qml singleton BatteryManager 1.0 BatteryManager.qml +singleton NetworkManager 1.0 NetworkManager.qml