refactor(bar): restructure panel window and corners

This commit is contained in:
tux
2026-08-23 02:02:38 +05:30
parent ab5587fd90
commit a1533e73c7
4 changed files with 444 additions and 309 deletions

643
Bar.qml
View File

@@ -4,303 +4,380 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import Quickshell.Wayland import QtQuick.Shapes
import Quickshell.Hyprland import Quickshell.Hyprland
import Quickshell.Services.UPower
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
import Quickshell.Widgets
import Quickshell.Wayland
import qs.config import qs.config
Scope { PanelWindow {
Variants { id: window
model: Quickshell.screens
PanelWindow { property color barColor: Appearance.colors.background
required property var modelData
screen: modelData
color: "transparent"
anchors { color: "transparent"
top: true 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 hoverEnabled: true
implicitWidth: 1150
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 { Rectangle {
anchors.fill: parent property var ws: Hyprland.workspaces.values.find(w => w.id === index + 1)
anchors.topMargin: 10 property bool isActive: Hyprland.focusedWorkspace?.id === (index + 1)
radius: Appearance.radius Layout.alignment: Qt.AlignVCenter
color: Appearance.colors.background radius: 1000
implicitHeight: 15
FlexboxLayout { implicitWidth: isActive ? this.implicitHeight * 2.3 : this.implicitHeight
id: flexLayout
anchors.fill: parent
anchors.margins: Appearance.margin
wrap: FlexboxLayout.Wrap color: {
direction: FlexboxLayout.Row if (handler.hovered) {
justifyContent: FlexboxLayout.JustifySpaceBetween return Appearance.colors.accent;
} else if (isActive || ws) {
Rectangle { return Appearance.colors.accent;
color: 'transparent' } else {
implicitHeight: parent.height return Appearance.colors.inActive;
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
}
}
}
}
}
}
} }
}
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
}
}
}
} }

13
Battery.qml Normal file
View File

@@ -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}`
}

41
flake.lock generated
View File

@@ -2,15 +2,15 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1771848320, "lastModified": 1787360063,
"narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", "narHash": "sha256-dt4WdcvsA8/RCe+VZZwqU0X+XMM3wBbGCWA0/sFWzGo=",
"owner": "nixos", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "2fc6539b481e1d2569f25f8799236694180c0993", "rev": "2c423e03bbafcff28bfadc6781a4a8257f205cb5",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "NixOS",
"ref": "nixos-unstable", "ref": "nixos-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
@@ -23,11 +23,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1771926182, "lastModified": 1787279335,
"narHash": "sha256-QbXuSLhiSxOq6ydBL3+KGe1aiYWBW+e3J6qjJZaRMq0=", "narHash": "sha256-CLX2Zp5i5BuLbOxNOkwRd9YY84IOrACNxBV79o9/F9Y=",
"ref": "refs/heads/master", "ref": "refs/heads/master",
"rev": "cddb4f061bab495f4473ca5f2c571b6c710efef7", "rev": "1a4716cde794a59928d9d9fc15f2afc7a95de360",
"revCount": 744, "revCount": 846,
"type": "git", "type": "git",
"url": "https://git.outfoxxed.me/outfoxxed/quickshell" "url": "https://git.outfoxxed.me/outfoxxed/quickshell"
}, },
@@ -39,7 +39,28 @@
"root": { "root": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs", "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"
} }
} }
}, },

View File

@@ -2,30 +2,54 @@
description = "tux's widgets for wayland"; description = "tux's widgets for wayland";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
quickshell = { quickshell = {
url = "git+https://git.outfoxxed.me/outfoxxed/quickshell"; url = "git+https://git.outfoxxed.me/outfoxxed/quickshell";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
treefmt-nix = {
url = "github:numtide/treefmt-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = { outputs =
self, {
nixpkgs, self,
quickshell, nixpkgs,
}: let quickshell,
system = "x86_64-linux"; treefmt-nix,
pkgs = nixpkgs.legacyPackages.${system}; }:
in { let
devShells.${system} = { system = "x86_64-linux";
default = pkgs.mkShell { pkgs = nixpkgs.legacyPackages.${system};
buildInputs = [
quickshell.packages.${system}.default quickshellWithModules = quickshell.packages.${system}.default.withModules [
pkgs.kdePackages.qtdeclarative pkgs.qt6.qtbase
pkgs.kdePackages.qt5compat 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
]; ];
}; };
}; };
};
} }