rewrite bar with quickshell

This commit is contained in:
2025-10-11 16:28:11 +02:00
parent e1a02f7994
commit abadf04aa2
49 changed files with 10246 additions and 7 deletions

View File

@@ -0,0 +1,22 @@
import QtQuick
import Quickshell.Services.UPower
import qs.Constants
import qs.Modules.Bar.Misc
import qs.Services
MonitorItem {
readonly property var battery: UPower.displayDevice
readonly property bool isReady: (battery && battery.ready && battery.isLaptopBattery && battery.isPresent)
readonly property real percent: (isReady ? (battery.percentage * 100) : 0)
readonly property bool charging: (isReady ? battery.state === UPowerDeviceState.Charging : false)
property int lowBatteryThreshold: 15
symbol: {
return charging ? Icons.charging : percent >= 80 ? Icons.battery100 : percent >= 60 ? Icons.battery75 : percent >= 40 ? Icons.battery50 : percent >= 20 ? Icons.battery25 : Icons.battery00;
}
fillColor: !isReady || charging || percent > lowBatteryThreshold ? Colors.sapphire : Colors.red
value: percent
maxValue: 100
textSuffix: "%"
pointerCursor: false
}

View File

@@ -0,0 +1,34 @@
import QtQuick
import Quickshell
import qs.Constants
import qs.Modules.Bar.Misc
import qs.Services
MonitorItem {
property ShellScreen screen: null
function getMonitor() {
return BrightnessService.getMonitorForScreen(screen) || null;
}
symbol: Icons.brightness
fillColor: Colors.blue
value: {
const monitor = getMonitor();
return monitor ? Math.round(monitor.brightness * 100) : "N/A";
}
maxValue: 100
textSuffix: "%"
onWheelUp: {
const monitor = getMonitor();
if (monitor)
monitor.increaseBrightness();
}
onWheelDown: {
const monitor = getMonitor();
if (monitor)
monitor.decreaseBrightness();
}
}

View File

@@ -0,0 +1,69 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Constants
import qs.Modules.Misc
import qs.Services
Item {
id: root
property int count: 6
property int barWidth: 5
property int barSpacing: 3
implicitWidth: root.barWidth * root.count + root.barSpacing * (root.count - 1)
implicitHeight: parent.height - 10
Cava {
id: cavaProcess
count: root.count
}
RowLayout {
anchors.fill: parent
spacing: root.barSpacing
anchors {
verticalCenter: parent.verticalCenter
}
Repeater {
model: cavaProcess.values
Rectangle {
width: root.barWidth
implicitHeight: Math.max(1, modelData * (parent.height - 10))
color: Colors.cavaList[Math.min(Math.floor(modelData * (Colors.cavaList.length - 1)), Colors.cavaList.length - 1)]
Behavior on height {
NumberAnimation {
duration: 100
easing.type: Easing.InOutCubic
}
}
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
MusicManager.playPause();
}
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0)
MusicManager.previous();
else if (wheel.angleDelta.y < 0)
MusicManager.next();
}
}
}

View File

@@ -0,0 +1,27 @@
import QtQuick
import Quickshell.Io
import qs.Constants
import qs.Modules.Bar.Misc
import qs.Services
MonitorItem {
symbol: Icons.cpuTemp > 80 ? Icons.tempHigh : Icons.cpuTemp > 50 ? Icons.tempMedium : Icons.tempLow
fillColor: Icons.cpuTemp > 80 ? Colors.red : Colors.yellow
value: Math.round(SystemStatService.cpuTemp)
maxValue: 120
textSuffix: "°C"
onClicked: {
if (action.running) {
action.signal(15);
return ;
}
action.exec(["ghostty", "-e", "btop"]);
}
Process {
id: action
running: false
}
}

View File

@@ -0,0 +1,27 @@
import QtQuick
import Quickshell.Io
import qs.Constants
import qs.Modules.Bar.Misc
import qs.Services
MonitorItem {
symbol: Icons.cpu
fillColor: Colors.teal
value: Math.round(SystemStatService.cpuUsage)
maxValue: 100
textSuffix: "%"
onClicked: {
if (action.running) {
action.signal(15);
return ;
}
action.exec(["ghostty", "-e", "btop"]);
}
Process {
id: action
running: false
}
}

View File

@@ -0,0 +1,144 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Io
import Quickshell.Widgets
import qs.Constants
import qs.Services
Item {
id: root
property real maxWidth: 250
property string fallbackIcon: "application-x-executable"
function getAppIcon() {
try {
const focusedWindow = Niri.getFocusedWindow();
if (focusedWindow && focusedWindow.appId) {
try {
const idValue = focusedWindow.appId;
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue);
const iconResult = ThemeIcons.iconForAppId(normalizedId.toLowerCase());
if (iconResult && iconResult !== "")
return iconResult;
} catch (iconError) {
console.warn("Error getting icon from CompositorService:", iconError);
}
}
return ThemeIcons.iconFromName(root.fallbackIcon);
} catch (e) {
console.warn("Error in getAppIcon:", e);
return ThemeIcons.iconFromName(root.fallbackIcon);
}
}
implicitHeight: parent.height
RowLayout {
id: layout
anchors.fill: parent
spacing: 10
visible: Niri.focusedWindowId !== -1
Item {
// Layout.alignment: Qt.AlignVCenter
id: iconContainer
implicitWidth: 18
implicitHeight: 18
IconImage {
id: windowIcon
anchors.fill: parent
source: getAppIcon()
asynchronous: true
smooth: true
visible: source !== ""
}
}
Item {
id: titleContainer
implicitWidth: root.maxWidth
implicitHeight: parent.height
// Layout.alignment: Qt.AlignVCenter
clip: true
Text {
id: windowTitle
text: Niri.focusedWindowTitle
anchors.verticalCenter: parent.verticalCenter
font.pointSize: Fonts.medium
font.family: Fonts.primary
color: Colors.accent
Process {
id: action
running: false
}
MouseArea {
id: mouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
onEntered: {
if (windowTitle.implicitWidth > titleContainer.width)
windowTitle.x = titleContainer.width - windowTitle.implicitWidth;
}
onExited: {
windowTitle.x = 0;
}
onClicked: function(mouse) {
if (mouse.button === Qt.MiddleButton) {
action.command = ["niri", "msg", "action", "close-window"];
action.startDetached();
} else if (mouse.button === Qt.LeftButton) {
action.command = ["niri", "msg", "action", "center-window"];
action.startDetached();
}
}
onWheel: function(wheel) {
if (wheel.angleDelta.y > 0) {
action.command = ["niri", "msg", "action", "set-column-width", "+10%"];
action.startDetached();
} else if (wheel.angleDelta.y < 0) {
action.command = ["niri", "msg", "action", "set-column-width", "-10%"];
action.startDetached();
} else if (wheel.angleDelta.x > 0) {
action.command = ["niri", "msg", "action", "focus-column-left"];
action.startDetached();
} else if (wheel.angleDelta.x < 0) {
action.command = ["niri", "msg", "action", "focus-column-right"];
action.startDetached();
}
}
}
Behavior on x {
NumberAnimation {
duration: 1000
easing.type: Easing.OutCubic
}
}
}
}
}
}

View File

@@ -0,0 +1,85 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Constants
import qs.Services
Item {
// Text {
// id: ipText
// anchors.verticalCenter: parent.verticalCenter
// text: Icons.global + " " + (showCountryCode ? IpService.countryCode : IpService.ip)
// font.pixelSize: Fonts.medium
// color: Colors.peach
// }
id: root
property bool showCountryCode: true
implicitHeight: parent.height
implicitWidth: layout.width + 10
RowLayout {
id: layout
anchors.top: parent.top
anchors.bottom: parent.bottom
spacing: 0
Text {
text: Icons.global
font.pointSize: Fonts.icon + 5
color: Colors.peach
}
Item {
id: expander
implicitWidth: mouseArea.containsMouse ? ipText.implicitWidth + 10 : 0
implicitHeight: parent.height
clip: true
Text {
id: ipText
text: showCountryCode ? IpService.countryCode : IpService.ip
font.pointSize: showCountryCode ? Fonts.medium : Fonts.small
font.family: Fonts.primary
color: Colors.peach
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 5
}
Behavior on implicitWidth {
NumberAnimation {
duration: 200
easing.type: Easing.InOutCubic
}
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: (mouse) => {
if (mouse.button === Qt.LeftButton) {
WriteClipboard.write(showCountryCode ? IpService.countryCode : IpService.ip);
SendNotification.show("Copied to clipboard", showCountryCode ? IpService.countryCode : IpService.ip);
} else if (mouse.button === Qt.RightButton)
showCountryCode = !showCountryCode;
else if (mouse.button === Qt.MiddleButton)
IpService.refresh();
}
}
}

View File

@@ -0,0 +1,28 @@
import QtQuick
import Quickshell.Io
import qs.Constants
import qs.Modules.Bar.Misc
import qs.Services
MonitorItem {
symbol: Icons.memory
fillColor: Colors.green
value: Math.round(SystemStatService.memPercent)
maxValue: 100
textValue: SystemStatService.memGb
textSuffix: "G"
onClicked: {
if (action.running) {
action.signal(15);
return ;
}
action.exec(["ghostty", "-e", "btop"]);
}
Process {
id: action
running: false
}
}

View File

@@ -0,0 +1,52 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Constants
import qs.Services
Item {
implicitHeight: parent.height
implicitWidth: layout.width + 10
RowLayout {
id: layout
anchors.top: parent.top
anchors.bottom: parent.bottom
spacing: 5
Text {
text: Icons.download
font.pointSize: Fonts.icon - 3
color: Colors.accent
Layout.leftMargin: 10
}
Text {
text: SystemStatService.formatSpeed(SystemStatService.txSpeed)
font.pointSize: Fonts.medium
font.family: Fonts.primary
color: Colors.accent
}
Item {
width: 5
}
Text {
text: Icons.upload
font.pointSize: Fonts.icon - 3
color: Colors.accent
}
Text {
text: SystemStatService.formatSpeed(SystemStatService.rxSpeed)
font.pointSize: Fonts.medium
font.family: Fonts.primary
color: Colors.accent
}
}
}

View File

@@ -0,0 +1,18 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Constants
Item {
id: root
implicitHeight: parent.height
Rectangle {
anchors.centerIn: parent
width: 1.5
height: parent.height * 0.32
color: Colors.text
}
}

View File

@@ -0,0 +1,10 @@
import QtQuick
import qs.Constants
import qs.Services
Text {
text: TimeService.time + " | " + TimeService.dateString
font.pointSize: Fonts.medium
font.family: Fonts.primary
color: Colors.accent
}

View File

@@ -0,0 +1,60 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Constants
import qs.Modules.Bar.Misc
Item {
id: root
property ShellScreen screen
implicitHeight: parent.height
implicitWidth: layout.implicitWidth
RowLayout {
id: layout
anchors.top: parent.top
anchors.bottom: parent.bottom
SymbolButton {
symbol: Icons.tray
buttonColor: Colors.green
}
Item {
id: trayContainer
implicitHeight: parent.height
implicitWidth: mouseArea.containsMouse ? expandedTray.implicitWidth : 0
clip: true
SystemTray {
id: expandedTray
screen: root.screen
}
Behavior on implicitWidth {
NumberAnimation {
duration: 200
easing.type: Easing.InOutCubic
}
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
}
}

View File

@@ -0,0 +1,21 @@
import QtQuick
import qs.Constants
import qs.Modules.Bar.Misc
import qs.Services
MonitorItem {
symbol: AudioService.muted ? Icons.volumeMuted : (AudioService.volume >= 0.66 ? Icons.volumeHigh : (AudioService.volume >= 0.33 ? Icons.volumeMedium : Icons.volumeLow))
fillColor: Colors.lavender
value: Math.round(AudioService.volume * 100)
maxValue: 100
textSuffix: "%"
onWheelUp: {
AudioService.increaseVolume();
}
onWheelDown: {
AudioService.decreaseVolume();
}
onClicked: {
AudioService.toggleMute();
}
}

View File

@@ -0,0 +1,299 @@
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import Quickshell
import Quickshell.Io
import qs.Constants
import qs.Services
Item {
id: root
required property ShellScreen screen
property bool hovered: false
property ListModel localWorkspaces
property real masterProgress: 0
property bool effectsActive: false
property color effectColor: Colors.accent
property int horizontalPadding: 16
property int spacingBetweenPills: 8
property bool isDestroying: false
signal workspaceChanged(int workspaceId, color accentColor)
function triggerUnifiedWave() {
effectColor = Colors.accent;
masterAnimation.restart();
}
function updateWorkspaceFocus() {
for (let i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i);
if (ws.isFocused === true) {
root.triggerUnifiedWave();
root.workspaceChanged(ws.id, Colors.accent);
break;
}
}
}
implicitWidth: {
let total = 0;
for (let i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i);
if (ws.isFocused)
total += 44;
else if (ws.isActive)
total += 28;
else
total += 16;
}
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills;
total += horizontalPadding * 2;
return total;
}
height: parent.height
Component.onCompleted: {
localWorkspaces.clear();
for (let i = 0; i < WorkspaceManager.workspaces.count; i++) {
const ws = WorkspaceManager.workspaces.get(i);
if (ws.output.toLowerCase() === screen.name.toLowerCase())
localWorkspaces.append(ws);
}
workspaceRepeater.model = localWorkspaces;
updateWorkspaceFocus();
}
Component.onDestruction: {
root.isDestroying = true;
}
Connections {
function onWorkspacesChanged() {
localWorkspaces.clear();
for (let i = 0; i < WorkspaceManager.workspaces.count; i++) {
const ws = WorkspaceManager.workspaces.get(i);
if (ws.output.toLowerCase() === screen.name.toLowerCase())
localWorkspaces.append(ws);
}
workspaceRepeater.model = localWorkspaces;
updateWorkspaceFocus();
}
target: WorkspaceManager
}
SequentialAnimation {
id: masterAnimation
PropertyAction {
target: root
property: "effectsActive"
value: true
}
NumberAnimation {
target: root
property: "masterProgress"
from: 0
to: 1
duration: 1000
easing.type: Easing.OutQuint
}
PropertyAction {
target: root
property: "effectsActive"
value: false
}
PropertyAction {
target: root
property: "masterProgress"
value: 0
}
}
Rectangle {
id: workspaceBackground
width: parent.width - 15
height: 26
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
radius: 12
color: Colors.transparent
layer.enabled: true
layer.effect: DropShadow {
color: "black"
radius: 12
samples: 24
verticalOffset: 0
horizontalOffset: 0
opacity: 0.1
}
}
Row {
id: pillRow
spacing: spacingBetweenPills
anchors.verticalCenter: workspaceBackground.verticalCenter
width: root.width - horizontalPadding * 2
x: horizontalPadding
Repeater {
id: workspaceRepeater
model: localWorkspaces
Item {
id: workspacePillContainer
height: 12
width: {
if (model.isFocused)
return 44;
else if (model.isActive)
return 28;
else
return 16;
}
Rectangle {
// half of focused height (if you want to animate this too)
id: workspacePill
anchors.fill: parent
radius: {
if (model.isFocused)
return 12;
else
return 6;
}
color: {
if (model.isFocused)
return Colors.accent;
if (model.isActive)
return Colors.accent.lighter(130);
if (model.isUrgent)
return Theme.error;
return Colors.surface2;
}
scale: model.isFocused ? 1 : 0.9
z: 0
MouseArea {
id: pillMouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
WorkspaceManager.switchToWorkspace(model.idx);
}
z: 20
hoverEnabled: true
}
// Material 3-inspired smooth animation for width, height, scale, color, opacity, and radius
Behavior on width {
NumberAnimation {
duration: 350
easing.type: Easing.OutBack
}
}
Behavior on height {
NumberAnimation {
duration: 350
easing.type: Easing.OutBack
}
}
Behavior on scale {
NumberAnimation {
duration: 300
easing.type: Easing.OutBack
}
}
Behavior on color {
ColorAnimation {
duration: 200
easing.type: Easing.InOutCubic
}
}
Behavior on opacity {
NumberAnimation {
duration: 200
easing.type: Easing.InOutCubic
}
}
Behavior on radius {
NumberAnimation {
duration: 350
easing.type: Easing.OutBack
}
}
}
Rectangle {
id: pillBurst
anchors.centerIn: workspacePillContainer
width: workspacePillContainer.width + 18 * root.masterProgress
height: workspacePillContainer.height + 18 * root.masterProgress
radius: width / 2
color: "transparent"
border.color: root.effectColor
border.width: 2 + 6 * (1 - root.masterProgress)
opacity: root.effectsActive && model.isFocused ? (1 - root.masterProgress) * 0.7 : 0
visible: root.effectsActive && model.isFocused
z: 1
}
Behavior on width {
NumberAnimation {
duration: 350
easing.type: Easing.OutBack
}
}
Behavior on height {
NumberAnimation {
duration: 350
easing.type: Easing.OutBack
}
}
}
}
}
localWorkspaces: ListModel {
}
}