diff --git a/Bar.qml b/Bar.qml index ae6938a..ff3cd4b 100644 --- a/Bar.qml +++ b/Bar.qml @@ -4,303 +4,380 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Effects -import Quickshell.Wayland +import QtQuick.Shapes import Quickshell.Hyprland -import Quickshell.Services.UPower import Quickshell.Services.SystemTray +import Quickshell.Widgets +import Quickshell.Wayland import qs.config -Scope { - Variants { - model: Quickshell.screens +PanelWindow { + id: window - PanelWindow { - required property var modelData - screen: modelData - color: "transparent" + property color barColor: Appearance.colors.background - anchors { - top: true + color: "transparent" + exclusionMode: ExclusionMode.Ignore + mask: Region { + item: cornersArea + intersection: Intersection.Subtract + } + + anchors { + left: true + top: true + right: true + bottom: true + } + + // Inline Components + component Corner: WrapperItem { + id: root + + property int corner + property real radius: Appearance.radius + property color color + + Component.onCompleted: { + switch (corner) { + case 0: + anchors.left = parent.left; + anchors.top = parent.top; + break; + case 1: + anchors.top = parent.top; + anchors.right = parent.right; + rotation = 90; + break; + case 2: + anchors.right = parent.right; + anchors.bottom = parent.bottom; + rotation = 180; + break; + case 3: + anchors.left = parent.left; + anchors.bottom = parent.bottom; + rotation = -90; + break; + } + } + + Shape { + preferredRendererType: Shape.CurveRenderer + + ShapePath { + strokeWidth: 0 + fillColor: root.color + startX: root.radius + + PathArc { + relativeX: -root.radius + relativeY: root.radius + radiusX: root.radius + radiusY: radiusX + direction: PathArc.Counterclockwise + } + + PathLine { + relativeX: 0 + relativeY: -root.radius + } + + PathLine { + relativeX: root.radius + relativeY: 0 + } + } + } + } + component Exclusion: PanelWindow { + property string name + implicitWidth: 0 + implicitHeight: 0 + WlrLayershell.namespace: `quickshell:${name}ExclusionZone` + } + + // Exclusions + Scope { + Exclusion { + name: "left" + exclusiveZone: leftBar.implicitWidth + anchors.left: true + } + Exclusion { + name: "top" + exclusiveZone: topBar.implicitHeight + anchors.top: true + } + Exclusion { + name: "right" + exclusiveZone: rightBar.implicitWidth + anchors.right: true + } + Exclusion { + name: "bottom" + exclusiveZone: bottomBar.implicitHeight + anchors.bottom: true + } + } + + // Bars + Rectangle { + id: leftBar + implicitWidth: 10 + implicitHeight: QsWindow.window?.height ?? 0 + color: window.barColor + anchors.left: parent.left + } + Rectangle { + id: rightBar + implicitWidth: 10 + implicitHeight: QsWindow.window?.height ?? 0 + color: window.barColor + anchors.right: parent.right + } + Rectangle { + id: bottomBar + implicitWidth: QsWindow.window?.width ?? 0 + implicitHeight: 10 + color: window.barColor + anchors.bottom: parent.bottom + } + Rectangle { + id: topBar + implicitWidth: QsWindow.window?.width ?? 0 + implicitHeight: 50 + color: window.barColor + anchors.top: parent.top + + FlexboxLayout { + id: flexLayout + anchors.fill: parent + anchors.margins: Appearance.margin + + wrap: FlexboxLayout.Wrap + direction: FlexboxLayout.Row + justifyContent: FlexboxLayout.JustifySpaceBetween + + + Rectangle { + color: 'transparent' + implicitHeight: parent.height + Layout.fillWidth: true + + Row { + Layout.fillWidth: true + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + spacing: Appearance.spacing + + Button { + padding: Appearance.padding + display: AbstractButton.IconOnly + + icon.color: Appearance.colors.foreground + icon.source: Quickshell.shellPath("assets") + "/icons/nix.svg" + onClicked: { + Quickshell.execDetached(["vicinae", "toggle"]); } - implicitHeight: 50 - implicitWidth: 1150 + hoverEnabled: true + + HoverHandler { + cursorShape: Qt.PointingHandCursor + } + + background: Rectangle { + anchors.fill: parent + radius: Appearance.radius + color: parent.hovered ? Appearance.colors.inActive : Appearance.colors.background + + Behavior on color { + ColorAnimation { + duration: Appearance.duration + } + } + } + } + + Button { + padding: Appearance.padding + + text: `${Battery.percentage}% ${Battery.energyRate}W` + palette.buttonText: Appearance.colors.foreground + font.family: Appearance.font.family + font.pointSize: Appearance.font.pointSize + + icon.color: Appearance.colors.foreground + icon.source: Battery.icon + + background: Rectangle { + anchors.fill: parent + radius: Appearance.radius + color: Appearance.colors.inActive + } + } + } + } + + Rectangle { + color: 'transparent' + implicitHeight: parent.height + Layout.fillWidth: true + + Row { + Layout.fillWidth: true + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + spacing: Appearance.spacing + + Repeater { + model: 7 Rectangle { - anchors.fill: parent - anchors.topMargin: 10 - radius: Appearance.radius - color: Appearance.colors.background + property var ws: Hyprland.workspaces.values.find(w => w.id === index + 1) + property bool isActive: Hyprland.focusedWorkspace?.id === (index + 1) + Layout.alignment: Qt.AlignVCenter + radius: 1000 + implicitHeight: 15 - FlexboxLayout { - id: flexLayout - anchors.fill: parent - anchors.margins: Appearance.margin + implicitWidth: isActive ? this.implicitHeight * 2.3 : this.implicitHeight - wrap: FlexboxLayout.Wrap - direction: FlexboxLayout.Row - justifyContent: FlexboxLayout.JustifySpaceBetween - - Rectangle { - color: 'transparent' - implicitHeight: parent.height - Layout.fillWidth: true - - Row { - Layout.fillWidth: true - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - spacing: Appearance.spacing - - Button { - padding: Appearance.padding - display: AbstractButton.IconOnly - - icon.color: Appearance.colors.foreground - icon.source: Quickshell.shellPath("assets") + "/icons/nix.svg" - onClicked: { - Quickshell.execDetached(["vicinae", "toggle"]); - } - - hoverEnabled: true - - HoverHandler { - cursorShape: Qt.PointingHandCursor - } - - background: Rectangle { - anchors.fill: parent - radius: Appearance.radius - color: parent.hovered ? Appearance.colors.inActive : Appearance.colors.background - - Behavior on color { - ColorAnimation { - duration: Appearance.duration - } - } - } - } - - Button { - property real percentage: Math.round(UPower.displayDevice.percentage * 100) - property real energyRate: UPower.displayDevice.changeRate - property string iconBase: Quickshell.shellPath("assets") + "/icons/" - property string iconName: parent.percentage < 30 ? "battery-low.svg" : parent.percentage < 70 ? "battery-medium.svg" : "battery-full.svg" - - padding: Appearance.padding - - text: `${percentage}% ${energyRate}W` - palette.buttonText: Appearance.colors.foreground - font.family: Appearance.font.family - font.pointSize: Appearance.font.pointSize - - icon.color: Appearance.colors.foreground - icon.source: iconBase + iconName - - background: Rectangle { - anchors.fill: parent - radius: Appearance.radius - color: Appearance.colors.inActive - } - } - } - } - - Rectangle { - color: 'transparent' - implicitHeight: parent.height - Layout.fillWidth: true - - Row { - Layout.fillWidth: true - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - spacing: Appearance.spacing - - Repeater { - model: 7 - - Rectangle { - property var ws: Hyprland.workspaces.values.find(w => w.id === index + 1) - property bool isActive: Hyprland.focusedWorkspace?.id === (index + 1) - Layout.alignment: Qt.AlignVCenter - radius: 1000 - implicitHeight: 15 - - implicitWidth: isActive ? this.implicitHeight * 2.3 : this.implicitHeight - - color: { - if (handler.hovered) { - return Appearance.colors.accent; - } else if (isActive || ws) { - return Appearance.colors.accent; - } else { - return Appearance.colors.inActive; - } - } - - MouseArea { - hoverEnabled: true - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: Hyprland.dispatch("workspace " + (index + 1)) - } - - Behavior on color { - ColorAnimation { - duration: Appearance.duration - } - } - HoverHandler { - id: handler - } - - Behavior on implicitWidth { - NumberAnimation { - duration: Appearance.duration - easing.type: Easing.OutQuad - } - } - } - } - } - } - Rectangle { - color: 'transparent' - implicitHeight: parent.height - Layout.fillWidth: true - - Row { - Layout.fillWidth: true - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - spacing: Appearance.spacing - - Button { - padding: Appearance.padding - display: AbstractButton.IconOnly - - icon.color: Appearance.colors.foreground - icon.source: Quickshell.shellPath("assets") + "/icons/wifi.svg" - onClicked: { - Quickshell.execDetached(["vicinae", "vicinae://extensions/dagimg-dot/wifi-commander/scan-wifi"]); - } - - hoverEnabled: true - - HoverHandler { - cursorShape: Qt.PointingHandCursor - } - - background: Rectangle { - anchors.fill: parent - radius: Appearance.radius - color: parent.hovered ? Appearance.colors.inActive : Appearance.colors.background - - Behavior on color { - ColorAnimation { - duration: Appearance.duration - } - } - } - } - - Button { - padding: Appearance.padding - display: AbstractButton.IconOnly - - icon.color: Appearance.colors.foreground - icon.source: Quickshell.shellPath("assets") + "/icons/bluetooth.svg" - onClicked: { - Quickshell.execDetached(["vicinae", "vicinae://extensions/Gelei/bluetooth/devices"]); - } - - hoverEnabled: true - - HoverHandler { - cursorShape: Qt.PointingHandCursor - } - - background: Rectangle { - anchors.fill: parent - radius: Appearance.radius - color: parent.hovered ? Appearance.colors.inActive : Appearance.colors.background - - Behavior on color { - ColorAnimation { - duration: Appearance.duration - } - } - } - } - - Rectangle { - implicitWidth: sysTrayRow.implicitWidth + 10 - implicitHeight: parent.height - radius: Appearance.radius - color: Appearance.colors.inActive - - Row { - id: sysTrayRow - - anchors.centerIn: parent - spacing: Appearance.spacing - - Repeater { - model: SystemTray.items.values.length - Image { - source: SystemTray.items.values[index].icon - height: parent.parent.height - 15 - width: parent.parent.height - 15 - } - } - } - } - - Button { - padding: Appearance.padding - text: Time.time - - palette.buttonText: Appearance.colors.foreground - font.family: Appearance.font.family - font.pointSize: Appearance.font.pointSize - - background: Rectangle { - anchors.fill: parent - radius: Appearance.radius - color: Appearance.colors.inActive - } - } - - Button { - padding: Appearance.padding - display: AbstractButton.IconOnly - - icon.color: Appearance.colors.foreground - icon.source: Quickshell.shellPath("assets") + "/icons/ghost.svg" - - hoverEnabled: true - - HoverHandler { - cursorShape: Qt.PointingHandCursor - } - - background: Rectangle { - anchors.fill: parent - radius: Appearance.radius - color: parent.hovered ? Appearance.colors.inActive : Appearance.colors.background - - Behavior on color { - ColorAnimation { - duration: Appearance.duration - } - } - } - } - } - } + color: { + if (handler.hovered) { + return Appearance.colors.accent; + } else if (isActive || ws) { + return Appearance.colors.accent; + } else { + return Appearance.colors.inActive; } + } + + MouseArea { + hoverEnabled: true + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Hyprland.dispatch("workspace " + (index + 1)) + } + + Behavior on color { + ColorAnimation { + duration: Appearance.duration + } + } + HoverHandler { + id: handler + } + + Behavior on implicitWidth { + NumberAnimation { + duration: Appearance.duration + easing.type: Easing.OutQuad + } + } } + } } + } + Rectangle { + color: 'transparent' + implicitHeight: parent.height + Layout.fillWidth: true + + Row { + Layout.fillWidth: true + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + spacing: Appearance.spacing + + Rectangle { + implicitWidth: sysTrayRow.implicitWidth + 10 + implicitHeight: parent.height + radius: Appearance.radius + color: Appearance.colors.inActive + visible: SystemTray.items.values.length > 0 + + Row { + id: sysTrayRow + + anchors.centerIn: parent + spacing: Appearance.spacing + + Repeater { + model: SystemTray.items.values.length + Image { + source: SystemTray.items.values[index].icon + height: parent.parent.height - 18 + width: parent.parent.height - 18 + } + } + } + } + + Button { + padding: Appearance.padding + text: Time.time + + palette.buttonText: Appearance.colors.foreground + font.family: Appearance.font.family + font.pointSize: Appearance.font.pointSize + + background: Rectangle { + anchors.fill: parent + radius: Appearance.radius + color: Appearance.colors.inActive + } + } + + Button { + padding: Appearance.padding + display: AbstractButton.IconOnly + + icon.color: Appearance.colors.foreground + icon.source: Quickshell.shellPath("assets") + "/icons/ghost.svg" + + hoverEnabled: true + + HoverHandler { + cursorShape: Qt.PointingHandCursor + } + + background: Rectangle { + anchors.fill: parent + radius: Appearance.radius + color: parent.hovered ? Appearance.colors.inActive : Appearance.colors.background + + Behavior on color { + ColorAnimation { + duration: Appearance.duration + } + } + } + } + } + } } + + } + + Rectangle { + id: cornersArea + implicitWidth: QsWindow.window?.width - (leftBar.implicitWidth + rightBar.implicitWidth) + implicitHeight: QsWindow.window?.height - (topBar.implicitHeight + bottomBar.implicitHeight) + color: "transparent" + x: leftBar.implicitWidth + y: topBar.implicitHeight + + Repeater { + model: [0, 1, 2, 3] + + Corner { + required property int modelData + corner: modelData + color: window.barColor + } + } + } } + + diff --git a/Battery.qml b/Battery.qml new file mode 100644 index 0000000..8b68231 --- /dev/null +++ b/Battery.qml @@ -0,0 +1,13 @@ +pragma Singleton + +import Quickshell +import QtQuick +import Quickshell.Services.UPower + +Singleton { + id: root + readonly property int percentage: Math.round(UPower.displayDevice.percentage * 100) + readonly property real energyRate: UPower.displayDevice.changeRate + readonly property string iconName: root.percentage < 30 ? "battery-low.svg" : root.percentage < 70 ? "battery-medium.svg" : "battery-full.svg" + readonly property url icon: `${Quickshell.shellPath("assets")}/icons/${root.iconName}` +} diff --git a/flake.lock b/flake.lock index 25e4ad6..02bfdb3 100644 --- a/flake.lock +++ b/flake.lock @@ -2,15 +2,15 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1771848320, - "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", - "owner": "nixos", + "lastModified": 1787360063, + "narHash": "sha256-dt4WdcvsA8/RCe+VZZwqU0X+XMM3wBbGCWA0/sFWzGo=", + "owner": "NixOS", "repo": "nixpkgs", - "rev": "2fc6539b481e1d2569f25f8799236694180c0993", + "rev": "2c423e03bbafcff28bfadc6781a4a8257f205cb5", "type": "github" }, "original": { - "owner": "nixos", + "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" @@ -23,11 +23,11 @@ ] }, "locked": { - "lastModified": 1771926182, - "narHash": "sha256-QbXuSLhiSxOq6ydBL3+KGe1aiYWBW+e3J6qjJZaRMq0=", + "lastModified": 1787279335, + "narHash": "sha256-CLX2Zp5i5BuLbOxNOkwRd9YY84IOrACNxBV79o9/F9Y=", "ref": "refs/heads/master", - "rev": "cddb4f061bab495f4473ca5f2c571b6c710efef7", - "revCount": 744, + "rev": "1a4716cde794a59928d9d9fc15f2afc7a95de360", + "revCount": 846, "type": "git", "url": "https://git.outfoxxed.me/outfoxxed/quickshell" }, @@ -39,7 +39,28 @@ "root": { "inputs": { "nixpkgs": "nixpkgs", - "quickshell": "quickshell" + "quickshell": "quickshell", + "treefmt-nix": "treefmt-nix" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1786901030, + "narHash": "sha256-WSFCsDSE5ffgD2MqzkM2CYjeFiKhRF/dJUN8uedb6YE=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "27b3b12a8e6375f28ebe122f07d230ca5459bbfa", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index 198dc2f..4eeb439 100644 --- a/flake.nix +++ b/flake.nix @@ -2,30 +2,54 @@ description = "tux's widgets for wayland"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; quickshell = { url = "git+https://git.outfoxxed.me/outfoxxed/quickshell"; inputs.nixpkgs.follows = "nixpkgs"; }; + + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; - outputs = { - self, - nixpkgs, - quickshell, - }: let - system = "x86_64-linux"; - pkgs = nixpkgs.legacyPackages.${system}; - in { - devShells.${system} = { - default = pkgs.mkShell { - buildInputs = [ - quickshell.packages.${system}.default - pkgs.kdePackages.qtdeclarative - pkgs.kdePackages.qt5compat + outputs = + { + self, + nixpkgs, + quickshell, + treefmt-nix, + }: + let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + + quickshellWithModules = quickshell.packages.${system}.default.withModules [ + pkgs.qt6.qtbase + pkgs.qt6.qtdeclarative + pkgs.qt6.qtmultimedia + pkgs.qt6.qttranslations + ]; + + treefmtEval = treefmt-nix.lib.evalModule pkgs { + projectRootFile = "flake.nix"; + + programs = { + nixfmt.enable = true; + }; + }; + in + { + formatter.${system} = treefmtEval.config.build.wrapper; + + devShells.${system}.default = pkgs.mkShell { + packages = [ + quickshellWithModules + pkgs.cava + pkgs.wallust ]; }; }; - }; }