diff --git a/.gitignore b/.gitignore index 1f8011a..36a8c6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,12 @@ .session + +# Devenv +.devenv* +devenv.local.nix +devenv.local.yaml + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml diff --git a/Bar.qml b/Bar.qml index dd10837..45a8464 100644 --- a/Bar.qml +++ b/Bar.qml @@ -1,10 +1,10 @@ import QtQuick import QtQuick.Effects +import QtQuick.Layouts import Quickshell import Quickshell.Hyprland -import "Components/Color.js" as Colors - -import "Components" +import "Bar" +import "Color.js" as Colors Scope { Variants { @@ -14,32 +14,28 @@ Scope { PanelWindow { id: win + property double barExclusionZone: 15 + property double barMaxHeight: 1000 required property var modelData property HyprlandMonitor monitor: Hyprland.monitorFor(modelData) color: "transparent" exclusionMode: ExclusionMode.Normal - exclusiveZone: 20 - implicitHeight: 1000 + exclusiveZone: barExclusionZone + implicitHeight: barMaxHeight screen: modelData - Behavior on implicitHeight { - NumberAnimation { - duration: 0 - easing.type: Easing.InOutQuad - } - } mask: Region { Region { - item: workspaceSwitcher + item: left } Region { - item: pillItem + item: center } Region { - item: utilsItem + item: right } } @@ -49,35 +45,54 @@ Scope { top: true } - Workspace { - id: workspaceSwitcher - - monitor: win.monitor - } - - RectangularShadow { - anchors.fill: pillItem - blur: 20 - color: Colors.crust - offset.x: 3 - offset.y: 3 - radius: pillItem.radius - spread: 7 - } - - Pill { - id: pillItem - - monitor: win.monitor - } - Item { - anchors.fill: parent + anchors { + fill: parent + topMargin: 3 + } - Utils { - id: utilsItem + Row { + id: left - monitor: win.monitor + height: childrenRect.height + + anchors { + left: parent.left + leftMargin: 7 + } + + Left { + monitor: win.monitor + } + } + + Row { + id: center + + height: childrenRect.height + + anchors { + horizontalCenter: parent.horizontalCenter + } + + Center { + monitor: win.monitor + } + } + + Row { + id: right + + height: childrenRect.height + + anchors { + right: parent.right + rightMargin: 7 + } + + Right { + monitor: win.monitor + } } } } diff --git a/Bar/Center.qml b/Bar/Center.qml new file mode 100644 index 0000000..0e94c30 --- /dev/null +++ b/Bar/Center.qml @@ -0,0 +1,264 @@ +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.start(); + } + } + } + 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) { + console.log("some"); + 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 new file mode 100644 index 0000000..2908700 --- /dev/null +++ b/Bar/Center/Expanded.qml @@ -0,0 +1,55 @@ +import QtQuick +import Quickshell +import "../../Color.js" as Colors +import "../../Components/" +import "../../Services/" + +Item { + id: fullTimeRoot + + Rectangle { + anchors.fill: parent + radius: 30 + + gradient: MidpointGradient { + color: Colors.red + } + } + + Item { + id: timeColumnContainer + + anchors.fill: parent + + Column { + id: timeColumn + + spacing: 5 + + anchors { + centerIn: parent + } + + StyledText { + anchors.horizontalCenter: parent.horizontalCenter + color: Colors.surface0 + text: Time.time + + font { + pointSize: 30 + } + } + + StyledText { + anchors.horizontalCenter: parent.horizontalCenter + color: Colors.surface1 + text: Time.date + + font { + pointSize: 9 + weight: 400 + } + } + } + } +} diff --git a/Bar/Center/Mpris.qml b/Bar/Center/Mpris.qml new file mode 100644 index 0000000..51adfe2 --- /dev/null +++ b/Bar/Center/Mpris.qml @@ -0,0 +1,62 @@ +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 new file mode 100644 index 0000000..8e5cc5d --- /dev/null +++ b/Bar/Center/NotificationDisplay.qml @@ -0,0 +1,47 @@ +import QtQuick +import Quickshell +import Quickshell.Services.Notifications +import "../../Color.js" as Colors +import "../../Components/" +import "../../Services/" + +Item { + id: root + + required property string body + required property string summary + + Rectangle { + anchors.fill: parent + radius: 30 + + gradient: MidpointGradient { + color: Colors.green + midpoint: 0.7 + } + } + + Column { + anchors.centerIn: parent + spacing: 5 + + StyledText { + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: 11 + text: root.summary + } + + StyledText { + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + text: root.body + verticalAlignment: Text.AlignVCenter + width: root.width - 20 + + font { + pointSize: 10 + weight: 500 + } + } + } +} diff --git a/Bar/Center/NotificationList.qml b/Bar/Center/NotificationList.qml new file mode 100644 index 0000000..c6372c9 --- /dev/null +++ b/Bar/Center/NotificationList.qml @@ -0,0 +1,307 @@ +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 new file mode 100644 index 0000000..99ff888 --- /dev/null +++ b/Bar/Left.qml @@ -0,0 +1,70 @@ +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 new file mode 100644 index 0000000..65ccc38 --- /dev/null +++ b/Bar/Left/WorkspaceContainer.qml @@ -0,0 +1,86 @@ +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 new file mode 100644 index 0000000..3111d51 --- /dev/null +++ b/Bar/Left/WorkspaceSelector.qml @@ -0,0 +1,87 @@ +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/Right.qml b/Bar/Right.qml new file mode 100644 index 0000000..a4608d4 --- /dev/null +++ b/Bar/Right.qml @@ -0,0 +1,21 @@ +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 + + Bluetooth { + exclusiveMonitor: root.monitor + } +} diff --git a/Bar/Right/Bluetooth.qml b/Bar/Right/Bluetooth.qml new file mode 100644 index 0000000..875949c --- /dev/null +++ b/Bar/Right/Bluetooth.qml @@ -0,0 +1,72 @@ +pragma ComponentBehavior: Bound +import QtQuick +import Quickshell +import Quickshell.Bluetooth +import "../../Color.js" as Colors +import "../../Components/" +import "../../Services/" +import "Bluetooth" + +Container { + id: root + + animOffset: 200 + boxColor: Colors.peach + boxHeight: 25 + boxWidth: 30 + defaultItem: icon + exclusiveToScreen: true + forceHidden: BluetoothManager.getConnected() + 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); + } + } + } + ] + + hover.onHoveredChanged: hover.hovered == false ? state = "" : undefined + tap.onTapped: state = "expanded" + + Component { + id: icon + + Item { + CenteredText { + text: "󰂱" + } + } + } + + Component { + id: expanded + + Expanded {} + } +} diff --git a/Bar/Right/Bluetooth/Expanded.qml b/Bar/Right/Bluetooth/Expanded.qml new file mode 100644 index 0000000..71b4f61 --- /dev/null +++ b/Bar/Right/Bluetooth/Expanded.qml @@ -0,0 +1,384 @@ +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/Components/Color.js b/Color.js similarity index 100% rename from Components/Color.js rename to Color.js diff --git a/Components/CenteredText.qml b/Components/CenteredText.qml new file mode 100644 index 0000000..1599b64 --- /dev/null +++ b/Components/CenteredText.qml @@ -0,0 +1,7 @@ +import QtQuick +import "../Color.js" as Colors + +StyledText { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter +} diff --git a/Components/Clock.qml b/Components/Clock.qml deleted file mode 100644 index b14621d..0000000 --- a/Components/Clock.qml +++ /dev/null @@ -1,42 +0,0 @@ -// Time.qml - -// with this line our type becomes a Singleton -pragma Singleton - -import Quickshell -import Quickshell.Io -import QtQuick - -// your singletons should always have Singleton as the type -Singleton { - id: root - property string time - property string day - - Process { - id: timeProc - command: ["date", "+%T"] - running: true - - stdout: StdioCollector { - onStreamFinished: root.time = this.text - } - } - - Process { - id: dateProc - command: ["date", "+%A %B %d %Y"] - running: true - - stdout: StdioCollector { - onStreamFinished: root.day = this.text - } - } - - Timer { - interval: 500 - running: true - repeat: true - onTriggered: timeProc.running = true, dateProc.running = true - } -} diff --git a/Components/Container.qml b/Components/Container.qml new file mode 100644 index 0000000..d906226 --- /dev/null +++ b/Components/Container.qml @@ -0,0 +1,140 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import Quickshell +import Quickshell.Hyprland + +import "../Color.js" as Colors + +Item { + id: root + + property double animOffset: 0 + property color boxColor: Colors.surface0 + property double boxHeight: 28 + property double boxRadius: 9 + property double boxWidth: 100 + readonly property double calculatedTopMargin: { + if (root.exclusiveToScreen) { + if (Hyprland.focusedMonitor != root.exclusiveMonitor || forceHidden == true) { + return -4 - root.boxHeight - 5; + } else { + return visibleTopMargin; + } + } else { + return visibleTopMargin; + } + } + property Component defaultItem + required property HyprlandMonitor exclusiveMonitor + property bool exclusiveToScreen: false + property bool forceHidden: false + property alias hover: hoverHandler + property alias rect: container + property alias stack: containerContent + property alias tap: tapHandler + property double visibleTopMargin: 0 + + implicitHeight: boxHeight + implicitWidth: boxWidth + + Behavior on anchors.topMargin { + animation: defaultCurve + } + Behavior on boxHeight { + SpringAnimation { + damping: 0.3 + spring: 4 + } + } + Behavior on boxRadius { + SpringAnimation { + damping: 0.3 + spring: 4 + } + } + Behavior on boxWidth { + SpringAnimation { + damping: 0.3 + spring: 4 + } + } + + anchors { + top: parent.top + topMargin: calculatedTopMargin + } + + NumberAnimation { + id: defaultCurve + + duration: 200 + root.animOffset + easing: Easing.InOutBack + } + + HoverHandler { + id: hoverHandler + } + + TapHandler { + id: tapHandler + } + + RectangularShadow { + anchors.fill: container + blur: 30 + color: Colors.mantle + offset.x: 7 + offset.y: 3 + radius: container.radius + spread: 10 + } + + Rectangle { + id: container + + anchors.fill: parent + clip: true + color: root.boxColor + radius: root.boxRadius + + StackView { + id: containerContent + + anchors.fill: parent + + replaceEnter: Transition { + PropertyAnimation { + duration: 100 + from: 0 + property: "opacity" + to: 1 + } + + PropertyAnimation { + duration: 100 + from: -5 + property: "anchors.topMargin" + to: 0 + } + } + replaceExit: Transition { + PropertyAnimation { + duration: 100 + from: 1 + property: "opacity" + to: 0 + } + } + + Component.onCompleted: containerContent.push(root.defaultItem) + onCurrentItemChanged: { + if (currentItem) { + // Dynamically center the incoming child to the StackView + currentItem.anchors.horizontalCenter = containerContent.horizontalCenter; + currentItem.anchors.verticalCenter = containerContent.verticalCenter; + } + } + } + } +} diff --git a/Components/MidpointGradient.qml b/Components/MidpointGradient.qml new file mode 100644 index 0000000..333c5fd --- /dev/null +++ b/Components/MidpointGradient.qml @@ -0,0 +1,23 @@ +import QtQuick + +Gradient { + id: root + + required property color color + property double midpoint: 0.6 + + GradientStop { + color: "transparent" + position: 0.0 + } + + GradientStop { + color: "transparent" + position: root.midpoint + } + + GradientStop { + color: root.color + position: 1.0 + } +} diff --git a/Components/Pill.qml b/Components/Pill.qml deleted file mode 100644 index 5af3d2e..0000000 --- a/Components/Pill.qml +++ /dev/null @@ -1,140 +0,0 @@ -import Quickshell -import Quickshell.Hyprland -import QtQuick -import QtQuick.Controls -import QtQuick.Effects -import "Color.js" as Colors - -import "./Pill" - -Rectangle { - required property HyprlandMonitor monitor - id: container - state: "" - visible: true - - property bool shown: monitor !== null && monitor.focused - - Behavior on anchors.topMargin { - SmoothedAnimation { duration: 70; easing.type: Easing.OutBounce } - } - - opacity: shown ? 1 : 0 - - Behavior on opacity { - NumberAnimation {duration: 50} - } - - anchors { - top: parent.top - topMargin: shown ? 5 : -implicitHeight - horizontalCenter: parent.horizontalCenter - } - - MouseArea { - id: mouseArea - anchors.fill: parent - - onClicked: container.state == 'expanded' ? container.state = "" : container.state = 'expanded'; - - } - - implicitWidth: 130 - implicitHeight: 30 - - radius: 30 - - property real gradientMidpoint: 0.4 - - gradient: Gradient { - GradientStop { position: 0.0; color: Colors.surface0 } - GradientStop { position: container.gradientMidpoint; color: Colors.surface0 } - GradientStop { position: 1.0; color: Colors.surface2 } - } - - border {color: Colors.lavender; width: 2} - - component SmoothAnim: SpringAnimation { - spring: 3 - mass: 0.5 - damping: 0.18 - } - - Behavior on implicitHeight { - SmoothAnim {} - } - - Behavior on implicitWidth { - SmoothAnim {} - } - - Behavior on gradientMidpoint { - NumberAnimation { - duration: 100 - } - } - - states: [ - State { - name: "" - StateChangeScript { - script: mainLoader.replace(defaultComponent) - } - }, - State { - name: "expanded" - StateChangeScript { - script: mainLoader.replace(expandedComponent) - } - PropertyChanges { container.gradientMidpoint: 0.7} - PropertyChanges { container.implicitHeight: 130} - PropertyChanges { container.implicitWidth: 300} - } - ] - - StackView { - id: mainLoader - anchors.fill: parent - - MouseArea { - id: mouseAreaStack - anchors.fill: parent - hoverEnabled: true - - // FIX part 1: Prevent internal StackView items from changing cursor grab - propagateComposedEvents: true - - onClicked: { - container.state == 'expanded' ? container.state = "" : container.state = 'expanded'; - } - - onExited: { - container.state = "" - } - } - - enabled:false - - initialItem: defaultComponent - - // Custom animations for switching components - replaceEnter: Transition { - PropertyAnimation { property: "opacity"; from: 0; to: 1; duration: 100 } - PropertyAnimation { property: "scale"; from: 0.9; to: 1.0; duration: 100 } - } - replaceExit: Transition { - PropertyAnimation { property: "opacity"; from: 1; to: 0; duration: 100 } - } - } - - Component { - id: defaultComponent - Default {} - } - - Component { - id: expandedComponent - Expanded {} - } - -} diff --git a/Components/Pill/Default.qml b/Components/Pill/Default.qml deleted file mode 100644 index 7ffa2d9..0000000 --- a/Components/Pill/Default.qml +++ /dev/null @@ -1,16 +0,0 @@ -import Quickshell -import Quickshell.Hyprland -import QtQuick -import QtQuick.Controls -import "../Color.js" as Colors -import "../" - -Item { - Text { - font.family: "FiraMono Nerd Font" - anchors.centerIn: parent - font.pixelSize: 15 - color: Colors.text - text: Clock.time - } -} diff --git a/Components/Pill/Expanded.qml b/Components/Pill/Expanded.qml deleted file mode 100644 index 7450533..0000000 --- a/Components/Pill/Expanded.qml +++ /dev/null @@ -1,29 +0,0 @@ -import Quickshell -import Quickshell.Hyprland -import QtQuick -import QtQuick.Layouts -import "../Color.js" as Colors -import "../" - -Item { - anchors.fill: parent - - Column { - spacing: 5 - anchors.centerIn: parent - - Text { - font.family: "FiraMono Nerd Font" - font.pixelSize: 40 - color: Colors.text - anchors.horizontalCenter: parent.horizontalCenter - text: Clock.time - } - Text { - font.family: "FiraMono Nerd Font" - color: Colors.subtext0 - anchors.horizontalCenter: parent.horizontalCenter - text: Clock.day - } - } -} diff --git a/Components/StyledText.qml b/Components/StyledText.qml new file mode 100644 index 0000000..db0271b --- /dev/null +++ b/Components/StyledText.qml @@ -0,0 +1,13 @@ +import QtQuick +import "../Color.js" as Colors + +Text { + color: Colors.base + elide: Text.ElideRight + + font { + family: "FiraMono Nerd Font" + pointSize: 12 + weight: 600 + } +} diff --git a/Components/Utils.qml b/Components/Utils.qml deleted file mode 100644 index 0002578..0000000 --- a/Components/Utils.qml +++ /dev/null @@ -1,45 +0,0 @@ -pragma ComponentBehavior: Bound -import QtQuick -import QtQuick.Layouts - -import Quickshell -import Quickshell.Hyprland - -import "./Utils" - -RowLayout { - id: utilsContainer - - required property HyprlandMonitor monitor - property bool shown: monitor !== null && monitor.focused - - opacity: shown ? 1 : 0 - state: "" - - Behavior on anchors.topMargin { - SmoothedAnimation { - duration: 300 - } - } - Behavior on opacity { - NumberAnimation { - duration: 50 - } - } - states: [ - State { - name: "" - } - ] - - anchors { - right: parent.right - rightMargin: 7 - top: parent.top - topMargin: shown ? -33 : -40 - } - - Battery {} - // Mpris {} - -} diff --git a/Components/Utils/Battery.qml b/Components/Utils/Battery.qml deleted file mode 100644 index ed377b5..0000000 --- a/Components/Utils/Battery.qml +++ /dev/null @@ -1,46 +0,0 @@ -import QtQuick -import Quickshell -import Quickshell.Services.UPower - -import "../Color.js" as Colors - -import "./" - -Container { - id: batteryRoot - - boxColor: Colors.green - boxState: "" - sourceComponent: batteryClosed - visible: UPower.devices[0] ? true : false - - MouseArea { - anchors.fill: parent - enabled: true - hoverEnabled: true - propagateComposedEvents: true - - onClicked: batteryRoot.boxState == "" ? batteryRoot.boxState = "open" : batteryRoot.boxState = "" - onExited: batteryRoot.boxState = "" - } - - Component { - id: batteryClosed - - Item { - id: batteryRoot - - anchors.fill: parent - - Text { - color: Colors.base - text: "something" - - anchors { - bottom: parent.bottom - horizontalCenter: parent.horizontalCenter - } - } - } - } -} diff --git a/Components/Utils/Container.qml b/Components/Utils/Container.qml deleted file mode 100644 index c10edf1..0000000 --- a/Components/Utils/Container.qml +++ /dev/null @@ -1,64 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import Quickshell - -Item { - id: container - - required property color boxColor - property double boxHeight - property double boxRadius - required property string boxState - property double boxWidth - property double openHeight - property double openWidth - required property Component sourceComponent - - Layout.topMargin: 0 - implicitHeight: boxHeight || 50 - implicitWidth: boxWidth || 30 - state: boxState - - Behavior on anchors.topMargin { - SmoothAnim {} - } - Behavior on implicitHeight { - SmoothAnim {} - } - Behavior on implicitWidth { - SmoothAnim {} - } - states: [ - State { - name: "" - }, - State { - name: "open" - - PropertyChanges { - container.Layout.topMargin: 35 - container.implicitHeight: openHeight || 150 - container.implicitWidth: openWidth || 200 - } - } - ] - - Rectangle { - id: mainBackground - - anchors.fill: parent - color: container.boxColor - radius: container.boxRadius || 10 - - Loader { - anchors.fill: parent - sourceComponent: container.sourceComponent - } - } - - component SmoothAnim: SpringAnimation { - damping: 0.18 - mass: 0.5 - spring: 3 - } -} diff --git a/Components/Utils/Mpris.qml b/Components/Utils/Mpris.qml deleted file mode 100644 index bc1c4ec..0000000 --- a/Components/Utils/Mpris.qml +++ /dev/null @@ -1,4 +0,0 @@ -import QtQuick -import Quickshell - -Item {} diff --git a/Components/Workspace.qml b/Components/Workspace.qml deleted file mode 100644 index d718117..0000000 --- a/Components/Workspace.qml +++ /dev/null @@ -1,290 +0,0 @@ -pragma ComponentBehavior: Bound -import QtQuick -import QtQuick.Effects -import QtQuick.Layouts - -import Quickshell -import Quickshell.Hyprland - -import "Color.js" as Colors - -Item { - id: workspaceRowContainer - - required property HyprlandMonitor monitor - property int textBottomMargin: 0 - - function getNextEmptyWorkspaceId() { - let maxId = 0; - let workspaces = Hyprland.workspaces.values; - - for (let i = 0; i < workspaces.length; i++) { - let ws = workspaces[i]; - // Look at all numerical workspaces globally (ignoring special workspaces like scratchpads) - if (ws.id > maxId && ws.id < 99) { - maxId = ws.id; - } - } - - // Returns the next global workspace ID (e.g., if max is 3, returns 4) - console.log(maxId + 1); - return maxId === 0 ? 1 : maxId + 1; - } - - implicitHeight: 65 - implicitWidth: 200 - state: "" - - Behavior on anchors.topMargin { - NumberAnimation { - duration: 100 - } - } - states: [ - State { - name: "" - // When not hovered, stay at default - when: !workspaceHoverHandler.hovered - - PropertyChanges { - workspaceRowContainer.anchors.topMargin: -33 - } - }, - State { - name: "hovered" - // Keep the workspace dropped whenever hovered is true - when: workspaceHoverHandler.hovered - - PropertyChanges { - workspaceRowContainer.anchors.topMargin: -20 - } - - PropertyChanges { - workspaceRowContainer.textBottomMargin: 7 - } - } - ] - Behavior on textBottomMargin { - NumberAnimation { - duration: 80 - } - } - - anchors { - left: parent.left - leftMargin: 15 - top: parent.top - topMargin: -33 - } - - HoverHandler { - id: workspaceHoverHandler - } - - Row { - id: workspaceRow - - spacing: 5 - - Repeater { - model: Hyprland.workspaces.values - - Item { - id: selectorContainer - - property HyprlandWorkspace activeWorkspace: workspaceRowContainer.monitor.activeWorkspace - property HyprlandWorkspace currentWorkspace: Hyprland.workspaces.values[index] - property HyprlandWorkspace globalFocusedWorkspace: Hyprland.focusedWorkspace - required property int index - - function getImplicitWidth() { - let implicitWidth = 25; - - if (workspaceRowContainer.state == "hovered") { - implicitWidth += 5; - if (currentWorkspace.focused) { - implicitWidth += 20; - } - } - return implicitWidth; - } - - function shouldBeVisible() { - let isSpecialWorkspace = !selectorContainer.currentWorkspace.name.includes("special:"); - let isCurrentMonitor = Hyprland.workspaces.values[index].monitor - == workspaceRowContainer.monitor; - return isSpecialWorkspace && isCurrentMonitor; - } - - implicitHeight: 50 - implicitWidth: getImplicitWidth() - visible: shouldBeVisible() - - Behavior on anchors.topMargin { - NumberAnimation { - duration: 100 - } - } - Behavior on implicitWidth { - NumberAnimation { - duration: 100 - } - } - states: [ - State { - name: "" - when: !selectorHoverHandler.hovered - - PropertyChanges { - selectorContainer.anchors.topMargin: 0 - } - }, - State { - name: "individualHovered" - when: selectorHoverHandler.hovered - - PropertyChanges { - selectorContainer.anchors.topMargin: 10 - } - }, - State { - name: "workspaceSwitched" - - PropertyChanges { - selectorContainer.anchors.topMargin: 5 - } - } - ] - - // onActiveWorkspaceChanged: { - // if (workspaceRowContainer.monitor.activeWorkspace.id == "1") {} - // if (workspaceRowContainer.monitor.activeWorkspace.id == "4") {} - // } - onGlobalFocusedWorkspaceChanged: { - if (globalFocusedWorkspace.name === currentWorkspace.name && globalFocusedWorkspace.monitor - === workspaceRowContainer.monitor && workspaceRowContainer.monitor.activeWorkspace.name - === currentWorkspace.name) { - selectorContainer.state = "workspaceSwitched"; - switchFeedbackTimer.restart(); - } - } - - Timer { - id: switchFeedbackTimer - - interval: 200 - repeat: false - - onTriggered: selectorContainer.state = "" - } - - anchors { - top: parent.top - topMargin: 0 - } - - HoverHandler { - id: selectorHoverHandler - } - - MouseArea { - id: selectorMouseArea - - anchors.fill: parent - hoverEnabled: true - propagateComposedEvents: true - - onClicked: selectorContainer.currentWorkspace.activate() - } - - RectangularShadow { - anchors.fill: selector - blur: 20 - color: Colors.crust - offset.x: 3 - offset.y: 3 - radius: selector.radius - spread: 7 - z: -1 - } - - Rectangle { - id: selector - - anchors.fill: parent - color: selectorContainer.currentWorkspace.focused ? Colors.mauve : - workspaceRowContainer.monitor.activeWorkspace - == selectorContainer.currentWorkspace ? Colors.lavender : Colors.overlay1 - radius: 8 - state: "" - z: 1 - - Behavior on color { - ColorAnimation { - duration: 100 - } - } - - Text { - id: workspaceIdentifier - - font.family: "FiraMono Nerd Font" - font.pixelSize: workspaceRowContainer.state == "hovered" ? 15 : 13 - text: selectorContainer.currentWorkspace.name - - anchors { - bottom: parent.bottom - bottomMargin: workspaceRowContainer.textBottomMargin - horizontalCenter: parent.horizontalCenter - } - } - } - } - } - - Item { - implicitHeight: 40 - implicitWidth: 20 - - Behavior on anchors.topMargin { - NumberAnimation { - duration: 100 - } - } - - anchors { - top: parent.top - topMargin: workspaceRowContainer.state == "hovered" ? 0 : -10 - } - - MouseArea { - id: newWorkspaceButton - - anchors.fill: parent - - onClicked: Hyprland.dispatch(`hl.dsp.focus({workspace = -${workspaceRowContainer.getNextEmptyWorkspaceId()}})`) - } - - Rectangle { - anchors.fill: parent - border.color: Colors.text - border.width: 2 - color: Colors.surface2 - radius: 10 - - Text { - color: Colors.text - font.family: "FiraMono Nerd Font" - font.pixelSize: 20 - text: "+" - - anchors { - bottom: parent.bottom - horizontalCenter: parent.horizontalCenter - } - } - } - } - } -} diff --git a/Services/BluetoothManager.qml b/Services/BluetoothManager.qml new file mode 100644 index 0000000..86f3611 --- /dev/null +++ b/Services/BluetoothManager.qml @@ -0,0 +1,69 @@ +pragma Singleton +import QtQuick +import Quickshell +import Quickshell.Bluetooth + +Singleton { + id: root + + property BluetoothAdapter defaultAdapter: Bluetooth.defaultAdapter + property string defaultAdapterName: Bluetooth.defaultAdapter.adapterId + + function getConnected() { + let deviceList = getDevicesList(); + for (let device in deviceList) { + let currentDevice = deviceList[device]; + if (currentDevice.connected == true) { + return false; + } + } + return true; + } + + function getDevicesList() { + return root.defaultAdapter.devices.values; + } + + function getIcon(name) { + const icons = { + "audio-card": "󰓃", + "audio-input-microphone": "", + "audio-headphones": "󰋋", + "audio-headset": "󰋋", + "battery": "󰂀", + "camera-photo": "󰻛", + "computer": "", + "input-keyboard": "󰌌", + "input-mouse": "󰍽", + "input-gaming": "󰊴", + "phone": "󰏲" + }; + + 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 new file mode 100644 index 0000000..95cf458 --- /dev/null +++ b/Services/MprisManager.qml @@ -0,0 +1,27 @@ +pragma Singleton +import QtQuick +import Quickshell +import Quickshell.Services.Mpris + +Singleton { + id: root + + property MprisPlayer defaultPlayer: Mpris.players.values[0] + property real pos: defaultPlayer.position / defaultPlayer.length + + function getPlaying() { + if (!defaultPlayer) { + return false; + } else { + return defaultPlayer.playbackState == MprisPlaybackState.Playing; + } + } + + function prev() { + defaultPlayer.previous(); + } + + function skip() { + defaultPlayer.next(); + } +} diff --git a/Services/NotificationManager.qml b/Services/NotificationManager.qml new file mode 100644 index 0000000..02981e3 --- /dev/null +++ b/Services/NotificationManager.qml @@ -0,0 +1,32 @@ +pragma Singleton +import QtQuick +import Quickshell +import Quickshell.Services.Notifications + +Singleton { + id: root + + property alias notif: server + + function getLatestNotification() { + let notificationList = server.trackedNotifications.values; + let len = notificationList.length; + if (len <= 0) { + len = 1; + } + let latestNotification = notificationList[len - 1]; + return latestNotification; + } + + NotificationServer { + id: server + + actionsSupported: true + bodyMarkupSupported: true + bodySupported: true + + onNotification: notification => { + notification.tracked = true; + } + } +} diff --git a/Services/Time.qml b/Services/Time.qml new file mode 100644 index 0000000..527dbba --- /dev/null +++ b/Services/Time.qml @@ -0,0 +1,49 @@ +// Time.qml + +// with this line our type becomes a Singleton +pragma Singleton +import QtQuick + +import Quickshell +import Quickshell.Io + +// your singletons should always have Singleton as the type +Singleton { + id: root + + property string date + property string time + + Process { + id: timeProc + + command: ["date", "+%T"] + running: true + + stdout: StdioCollector { + onStreamFinished: root.time = this.text + } + } + + Process { + id: dateProc + + command: ["date", "+%A %B %d %Y"] + running: true + + stdout: StdioCollector { + onStreamFinished: root.date = this.text + } + } + + Timer { + interval: 1000 + repeat: true + running: true + + onTriggered: { + timeProc.running = true; + dateProc.running = true; + } + } +} diff --git a/Services/WorkspaceManager.qml b/Services/WorkspaceManager.qml new file mode 100644 index 0000000..d238bc9 --- /dev/null +++ b/Services/WorkspaceManager.qml @@ -0,0 +1,79 @@ +pragma Singleton +import QtQuick +import Quickshell +import Quickshell.Hyprland + +Singleton { + id: root + + function activateNextFreeWorkspace() { + let nextFree = getNextFreeWorkspace(); + activateWorkspaceById(nextFree); + } + + function activateWorkspaceById(id) { + Hyprland.dispatch(`hl.dsp.focus({workspace = ${id}})`); + } + + function getAllNumberedWorkspaces() { + let allWorkspaces = Hyprland.workspaces.values; + let filteredWorkspaces = []; + for (let workspace in allWorkspaces) { + let currentWorkspace = allWorkspaces[workspace]; + if (!currentWorkspace.name.includes("special")) { + if (currentWorkspace) { + filteredWorkspaces.push(currentWorkspace); + } + } + } + return filteredWorkspaces; + } + + function getNextFreeWorkspace() { + let workspaceList = getAllNumberedWorkspaces(); + + if (workspaceList[workspaceList.length - 1].id == workspaceList.length) { + return workspaceList.length + 1; + } else { + let prevWorkspace = 0; + for (let workspace in workspaceList) { + let currentWorkspace = workspaceList[workspace]; + if (currentWorkspace.id != (prevWorkspace + 1)) { + return currentWorkspace.id - 1; + } else { + prevWorkspace = currentWorkspace.id; + } + } + } + } + + function getNumberOfWorkspaces(monitor) { + let workspaceList = getWorkspacesForMonitor(monitor); + return workspaceList.length; + } + + function getWorkspaceState(workspace) { + if (workspace.focused == true) { + return "focused"; + } else if (workspace.active == true) { + return "active"; + } else { + return "inactive"; + } + } + + function getWorkspacesForMonitor(monitor) { + let allWorkspaces = getAllNumberedWorkspaces(); + let monitorWorkspaces = []; + + for (let workspace in allWorkspaces) { + let currentWorkspace = allWorkspaces[workspace]; + if (currentWorkspace.monitor == monitor) { + if (currentWorkspace) { + monitorWorkspaces.push(currentWorkspace); + } + } + } + return monitorWorkspaces; + } +} diff --git a/Services/qmldir b/Services/qmldir new file mode 100644 index 0000000..b6984ea --- /dev/null +++ b/Services/qmldir @@ -0,0 +1,6 @@ +module Services +singleton Time 1.0 Time.qml +singleton NotificationManager 1.0 NotificationManager.qml +singleton WorkspaceManager 1.0 WorkspaceManager.qml +singleton BluetoothManager 1.0 BluetoothManager.qml +singleton MprisManager 1.0 MprisManager.qml diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..e34c720 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,65 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1782153911, + "narHash": "sha256-Z7BG4cSJX1ybJeZztEMV8WlE6GrXJsSd34x9eHvpzU8=", + "owner": "cachix", + "repo": "devenv", + "rev": "d894e487bb23ed87e0555d2158710844bde65322", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "nixpkgs": { + "inputs": { + "nixpkgs-src": "nixpkgs-src" + }, + "locked": { + "lastModified": 1782132010, + "narHash": "sha256-ZnAVHdVrotp80iIMm5CSR1fdxPlw7Uwmwxb+O/wsgZ8=", + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "12866ae2dddbc0ab8b329915f8072bb9c75bde89", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "nixpkgs-src": { + "flake": false, + "locked": { + "lastModified": 1781607440, + "narHash": "sha256-rxO+uc/KFbSJp+pgyXRuAX6QlG9hJdnt0BXpEQRXY+U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3e41b24abd260e8f71dbe2f5737d24122f972158", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} \ No newline at end of file diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..c0112ee --- /dev/null +++ b/devenv.nix @@ -0,0 +1,17 @@ +{ + pkgs, + ... +}: + +{ + env.DEVSHELL_NAME = "󰏖 devenv/#fab387| quickshell/green"; + + packages = with pkgs; [ + quickshell + kdePackages.qtdeclarative + ]; + + processes = { + qs.exec = "quickshell -p ."; + }; +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..68616a4 --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,4 @@ +# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json +inputs: + nixpkgs: + url: github:cachix/devenv-nixpkgs/rolling diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 0d3e526..0000000 --- a/flake.lock +++ /dev/null @@ -1,61 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1780243769, - "narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "331800de5053fcebacf6813adb5db9c9dca22a0c", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs", - "utils": "utils" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index 962ccf1..0000000 --- a/flake.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ - description = "A simple Rust development environment"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - utils.url = "github:numtide/flake-utils"; - }; - - outputs = - { - self, - nixpkgs, - utils, - }: - utils.lib.eachDefaultSystem ( - system: - let - pkgs = import nixpkgs { inherit system; }; - in - { - devShells.default = pkgs.mkShell { - - name = "flake"; - - buildInputs = with pkgs; [ - quickshell - kdePackages.qtdeclarative - ]; - - # Environment variables - shellHook = '' - export DEVSHELL_NAME="󱄅 flake/#89dceb| quickshell/green" - - # Trigger zsh - if [[ -z "$ZSH_VERSION" ]]; then - exec zsh - fi - ''; - }; - } - ); -} diff --git a/shell.qml b/shell.qml index fc6f2ae..ec9eb18 100644 --- a/shell.qml +++ b/shell.qml @@ -1,10 +1,7 @@ +import QtQuick import Quickshell import Quickshell.Hyprland -import QtQuick -import "Components/Color.js" as Colors - -import "Components" Scope { - Bar {} + Bar {} }