Inital commit checkpoint

This commit is contained in:
2025-08-31 23:39:53 -04:00
commit f5dbdea627
39 changed files with 2637 additions and 0 deletions

80
bar/Bar.qml Normal file
View File

@@ -0,0 +1,80 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import Quickshell.Wayland
import qs.common
import qs.common.widgets
Scope {
Variants {
model: Quickshell.screens;
PanelWindow {
required property var modelData
screen: modelData
WlrLayershell.layer: WlrLayer.Bottom
color: "transparent"
Rectangle {
id: barBackground
anchors {
fill: parent
margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0
}
color: Config.options.bar.showBackground ? Appearance.colors.colLayer1 : "transparent"
}
anchors {
top: true
left: true
right: true
}
implicitHeight: 45
RowLayout { // Left Section
id: leftSection
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: 5
}
spacing: Config.options?.bar.borderless ? 4 : 8
Workspaces {}
}
RowLayout { // Middle section
id: middleSection
anchors.centerIn: parent
spacing: Config.options?.bar.borderless ? 4 : 8
Media {
visible: true
Layout.fillWidth: true
}
}
RowLayout { // Right Section
id: rightSection
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: 5
}
spacing: Config.options?.bar.borderless ? 4 : 8
SysTray {
Layout.fillWidth: false
Layout.fillHeight: true
}
NotificationIcon {
Layout.fillWidth: false
Layout.fillHeight: true
}
ClockWidget {
id: clock
Layout.fillHeight: true
Layout.fillWidth: false
}
}
}
}
}

9
bar/BarGroup.qml Normal file
View File

@@ -0,0 +1,9 @@
import qs.modules.common
import QtQuick
import QtQuick.Layouts
Item {
id: root
property real padding: 5
implicitHeight: Appearance.sizes.baseBarHeight
}

8
bar/ClockWidget.qml Normal file
View File

@@ -0,0 +1,8 @@
import QtQuick
import QtQuick.Layouts
import qs.common
import qs.common.widgets
StyledText {
text: Time.time
}

81
bar/Media.qml Normal file
View File

@@ -0,0 +1,81 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Hyprland
import Quickshell.Services.Mpris
import qs
import qs.common
import qs.common.widgets
Item {
id: root
property bool borderless: Config.options.bar.borderless
Layout.fillHeight: true
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: Appearance.sizes.barHeight
Timer {
running: MprisController.hasPlayers && MprisController.activePlayer().isPlaying && MprisController.activePlayer().lengthSupported
interval: 1000
repeat: true
onTriggered: MprisController.activePlayer().positionChanged()
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton | Qt.RightButton | Qt.LeftButton
onPressed: (event) => {
if (event.button === Qt.MiddleButton) {
MprisController.activePlayer().togglePlaying();
} else if (event.button === Qt.BackButton) {
MprisController.shiftPlayer(-1);
} else if (event.button === Qt.ForwardButton) {
MprisController.shiftPlayer(1);
} else if (event.button === Qt.LeftButton) {
GlobalStates.mediaControlsOpen = !GlobalStates.mediaControlsOpen
}
}
}
RowLayout {
id: rowLayout
spacing: 4
anchors.fill: parent
visible: MprisController.hasPlayers
CircularProgress {
id: circularProgress
visible: MprisController.hasPlayers && MprisController.activePlayer().lengthSupported
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: rowLayout.spacing
lineWidth: 2
value: MprisController.activePlayer().lengthSupported ? MprisController.activePlayer()?.position / MprisController.activePlayer()?.length : 0
implicitSize: 26
colSecondary: Appearance.colors.colSecondaryContainer
colPrimary: Appearance.m3colors.m3onSecondaryContainer
enableAnimation: false
MaterialSymbol {
visible: MprisController.hasPlayers && MprisController.activePlayer().lengthSupported
anchors.centerIn: parent
fill: 1
text: MprisController.activePlayer()?.isPlaying ? "music_note" : "pause"
iconSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSecondaryContainer
}
}
StyledText {
visible: Config.options.bar.verbose
width: rowLayout.width - (circularProgress.size + rowLayout.spacing * 2)
Layout.alignment: Qt.AlignCenter
Layout.fillWidth: true
Layout.rightMargin: rowLayout.spacing
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
color: Appearance.colors.colOnLayer1
text: `${MprisController.activePlayer()?.trackTitle}${MprisController.activePlayer()?.trackArtist ? ' • ' + MprisController.activePlayer().trackArtist : ''}`
}
}
}

45
bar/NotificationIcon.qml Normal file
View File

@@ -0,0 +1,45 @@
import qs.common
import qs.common.widgets
import QtQuick
import QtQuick.Layouts
Item {
id: root
height: parent.height
implicitWidth: rowLayout.implicitWidth
Layout.leftMargin: Appearance.rounding.screenRounding
RowLayout {
id: rowLayout
anchors.fill: parent
spacing: 15
StyledText {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: Appearance.font.pixelSize.larger
color: Appearance.m3colors.m3error
text: NotificationServer.amountNotifications
visible: {
NotificationServer.amountNotifications > 0
}
}
MaterialSymbol {
visible: true
anchors.centerIn: parent
text: NotificationServer.amountNotifications > 0 ? "notifications_unread" : "notifications"
iconSize: Appearance.font.pixelSize.larger
color: NotificationServer.amountNotifications > 0 ? Appearance.m3colors.m3error : Appearance.m3colors.m3onSecondaryContainer
}
StyledText {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colSubtext
text: "•"
visible: true
}
}
}

39
bar/SysTray.qml Normal file
View File

@@ -0,0 +1,39 @@
import qs.common
import qs.common.widgets
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.SystemTray
Item {
id: root
height: parent.height
implicitWidth: rowLayout.implicitWidth
Layout.leftMargin: Appearance.rounding.screenRounding
RowLayout {
id: rowLayout
anchors.fill: parent
spacing: 15
Repeater {
model: SystemTray.items
SysTrayItem {
required property SystemTrayItem modelData
item: modelData
}
}
StyledText {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: Appearance.font.pixelSize.larger
color: Appearance.colors.colSubtext
text: "•"
visible: {
SystemTray.items.values.length > 0
}
}
}
}

52
bar/SysTrayItem.qml Normal file
View File

@@ -0,0 +1,52 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.SystemTray
import qs.common
MouseArea {
id: root
required property SystemTrayItem item
property bool targetMenuOpen: false
property int trayItemWidth: Appearance.font.pixelSize.larger
acceptedButtons: Qt.LeftButton | Qt.RightButton
Layout.fillHeight: true
implicitWidth: trayItemWidth
onClicked: (event) => {
switch (event.button) {
case Qt.LeftButton:
item.activate();
break;
case Qt.RightButton:
if (item.hasMenu) {
menu.open();
}
break;
}
event.accepted = true;
}
QsMenuAnchor {
id: menu
menu: root.item.menu
anchor {
item: root // Works instead of using window
edges: Edges.Bottom | Edges.Right
gravity: Edges.Bottom | Edges.Left
margins {
top: 30
}
}
}
IconImage {
id: trayIcon
source: root.item.icon
anchors.centerIn: parent
width: parent.width
height: parent.height
}
}

16
bar/Time.qml Normal file
View File

@@ -0,0 +1,16 @@
pragma Singleton
import Quickshell
import QtQuick
Singleton {
id: root
readonly property string time: {
Qt.formatDateTime(clock.date, "ddd, MMM dd hh:mm:ss AP")
}
SystemClock {
id: clock
precision: SystemClock.Seconds
}
}

180
bar/Workspaces.qml Normal file
View File

@@ -0,0 +1,180 @@
import QtQuick
import QtQuick.Controls // button
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import Quickshell.Widgets
import qs.common
import qs.common.functions
import qs.common.widgets
import Qt5Compat.GraphicalEffects
Item {
id: root
property bool borderless: Config.options.bar.borderless
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(QsWindow.window?.screen)
readonly property list<HyprlandMonitor> monitors: Hyprland.monitors.values
readonly property HyprlandToplevel activeWindow: Hyprland.activeToplevel
readonly property int workspaceGroup: Math.floor((monitor?.activeWorkspace?.id - 1) / Config.options.bar.workspaces.shown)
property list<bool> workspaceOccupied: []
property int widgetPadding: 4
property int workspaceButtonWidth: 26
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1
property real workspaceIconMarginShrinked: -4
property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % Config.options.bar.workspaces.shown
function updateWorkspaceOccupied() {
workspaceOccupied = Array.from({ length: Config.options.bar.workspaces.shown }, (_, i) => {
return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * Config.options.bar.workspaces.shown + i + 1);
});
}
function isMonitorWorkspace(id) {
for (var i = 0; i < monitors.length; i++){
if (id === monitors[i].id) {
return true;
}
}
}
Component.onCompleted: updateWorkspaceOccupied()
Connections {
target: Hyprland.workspaces
function onValuesChanged() {
root.updateWorkspaceOccupied();
}
}
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: Appearance.sizes.barHeight
// Workspaces - background
RowLayout {
id: rowLayout
z: 1
spacing: 0
anchors.fill: parent
implicitHeight: Appearance.sizes.barHeight
// https://doc.qt.io/qt-6/qml-qtquick-repeater.html
Repeater {
model: Config.options.bar.workspaces.shown
Rectangle {
z: 1
implicitHeight: root.workspaceButtonWidth
implicitWidth: root.workspaceButtonWidth
radius: Appearance.rounding.full
property var leftOccupied: index - 1 >= 0 && (root.workspaceOccupied[index-1] || root.isMonitorWorkspace(index))
property var rightOccupied: index + 1 < workspaceOccupied.length && (root.workspaceOccupied[index+1] || root.isMonitorWorkspace(index+2))
property var radiusLeft: leftOccupied ? 0 : Appearance.rounding.full
property var radiusRight: rightOccupied ? 0 : Appearance.rounding.full
topLeftRadius: radiusLeft
bottomLeftRadius: radiusLeft
topRightRadius: radiusRight
bottomRightRadius: radiusRight
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
opacity: (root.workspaceOccupied[index] || root.isMonitorWorkspace(index+1)) ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on radiusLeft {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on radiusRight {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
}
}
// Active workspace
Rectangle {
z: 2
property real activeWorkspaceMargin: 2
implicitHeight: root.workspaceButtonWidth - activeWorkspaceMargin * 2
radius: Appearance.rounding.full
color: Appearance.colors.colPrimary
anchors.verticalCenter: parent.verticalCenter
property real idx1: root.workspaceIndexInGroup
property real idx2: root.workspaceIndexInGroup
x: Math.min(idx1, idx2) * root.workspaceButtonWidth + activeWorkspaceMargin
implicitWidth: Math.abs(idx1 - idx2) * root.workspaceButtonWidth + root.workspaceButtonWidth - activeWorkspaceMargin * 2
Behavior on activeWorkspaceMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on idx1 { // leading animation
NumberAnimation {
duration: 100
easing.type: Easing.OutSine
}
}
/*
Behavior on idx2 { // following animation
NumberAnimation {
duration: 100
easing.type: Easing.OutSine
}
}
*/
}
// workspaces - numbers
RowLayout {
id: rowLayoutNumbers
z: 3
spacing: 0
anchors.fill: parent
implicitHeight: Appearance.sizes.barHeight
Repeater {
model: Config.options.bar.workspaces.shown
Button {
id: button
property int workspaceValue: workspaceGroup * Config.options.bar.workspaces.shown + index + 1
Layout.fillHeight: true
onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`)
width: root.workspaceButtonWidth
background: Item {
id: workspaceButtonBackground
implicitWidth: root.workspaceButtonWidth
implicitHeight: root.workspaceButtonWidth
StyledText { // Workspace number text
opacity: (Config.options?.bar.workspaces.alwaysShowNumbers) ? 1 : 0
z: 3
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2)
text: `${button.workspaceValue}`
elide: Text.ElideRight
color: (monitor?.activeWorkspace?.id == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1Inactive
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
}
}
}
}
}