Merge branch 'rewrite-again'

This commit is contained in:
2026-08-10 13:26:09 +01:00
28 changed files with 1588 additions and 1649 deletions

65
Bar.qml
View File

@@ -3,14 +3,20 @@ import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
import "Bar" import "./Bar"
import "Color.js" as Colors import "Color.js" as Colors
Scope { Scope {
Variants { Variants {
model: Quickshell.screens id: panelDelegate
delegate: panelWindow
model: Quickshell.screens
}
Component {
id: panelWindow
delegate: Component {
PanelWindow { PanelWindow {
id: win id: win
@@ -23,9 +29,12 @@ Scope {
exclusionMode: ExclusionMode.Normal exclusionMode: ExclusionMode.Normal
exclusiveZone: barExclusionZone exclusiveZone: barExclusionZone
implicitHeight: barMaxHeight implicitHeight: barMaxHeight
mask: maskRegion
screen: modelData screen: modelData
mask: Region { Region {
id: maskRegion
Region { Region {
item: left item: left
} }
@@ -45,23 +54,42 @@ Scope {
top: true top: true
} }
MultiEffect {
anchors.fill: rowContainer
shadowBlur: 1.5
shadowColor: Colors.mantle
shadowEnabled: true
shadowHorizontalOffset: 5
shadowOpacity: 1
shadowVerticalOffset: 3
source: rowContainer
}
Item { Item {
id: rowContainer
anchors { anchors {
fill: parent fill: parent
topMargin: 3 topMargin: 3
} }
anchors {
left: parent.left
leftMargin: 3
right: parent.right
rightMargin: 3
top: parent.top
}
Row { Row {
id: left id: left
height: childrenRect.height
anchors { anchors {
left: parent.left left: parent.left
leftMargin: 7 top: parent.top
} }
Left { WorkspaceSelector {
monitor: win.monitor monitor: win.monitor
} }
} }
@@ -69,13 +97,12 @@ Scope {
Row { Row {
id: center id: center
height: childrenRect.height
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
top: parent.top
} }
Center { Time {
monitor: win.monitor monitor: win.monitor
} }
} }
@@ -83,14 +110,23 @@ Scope {
Row { Row {
id: right id: right
height: childrenRect.height Layout.alignment: Qt.AlignTop
spacing: 5
anchors { anchors {
right: parent.right right: parent.right
rightMargin: 7 top: parent.top
} }
Right { Network {
monitor: win.monitor
}
Bluetooth {
monitor: win.monitor
}
Battery {
monitor: win.monitor monitor: win.monitor
} }
} }
@@ -98,4 +134,3 @@ Scope {
} }
} }
} }
}

189
Bar/Battery.qml Normal file
View File

@@ -0,0 +1,189 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import "../Color.js" as Colors
import "../Components/"
import "../Services/"
Container {
id: root
property double batteryDisplayHeight: 100
required property HyprlandMonitor monitor
property bool opened: false
property double radius: 9
animOffset: 150
boxColor: Colors.green
boxRadius: radius
defaultItem: icon
exclusiveMonitor: root.monitor
exclusiveToScreen: true
forceHidden: !BatteryManager.hasLaptopBattery
states: [
State {
name: "closed"
when: root.hovered == false && root.opened == false
PropertyChanges {
root.boxHeight: 25
root.boxRadius: root.radius
root.boxWidth: 60
}
},
State {
name: "hovered"
when: root.hovered == true && root.opened == false
PropertyChanges {
root.boxHeight: 28
root.boxWidth: 64
}
},
State {
name: "opened"
when: root.opened == true && root.hovered == true
PropertyChanges {
root.boxHeight: root.batteryDisplayHeight
root.boxRadius: 12
root.boxWidth: 450
}
StateChangeScript {
script: {
stack.replace(batteryDisplay);
}
}
}
]
hover.onHoveredChanged: {
if (hover.hovered == false) {
root.opened = false;
if (root.state != "icon") {
root.stack.replace(icon);
}
}
}
tap.onTapped: {
root.opened = true;
}
Component {
id: icon
Item {
BatteryGradient {}
CenteredText {
property string component: "icon"
propo: true
text: BatteryManager.getBatteryText()
}
}
}
Component {
id: batteryDisplay
Item {
id: batteryDisplayRoot
property double innerMargin: 5
property double outerMargin: 6
BatteryGradient {}
Rectangle {
id: batteryBackground
anchors.fill: parent
anchors.margins: batteryDisplayRoot.outerMargin
color: Colors.surface1
radius: 8
RowLayout {
anchors.fill: parent
Rectangle {
id: percentBox
property double percentBoxRadius: 5
Layout.fillHeight: true
Layout.margins: batteryDisplayRoot.innerMargin
color: Colors.text
implicitWidth: root.batteryDisplayHeight - (batteryDisplayRoot.innerMargin * 2) - (
batteryDisplayRoot.outerMargin * 2)
radius: 5
Rectangle {
color: Colors.green
implicitHeight: (root.batteryDisplayHeight - (batteryDisplayRoot.innerMargin * 2) - (
batteryDisplayRoot.outerMargin * 2)) * BatteryManager.mainBattery.percentage
radius: percentBox.percentBoxRadius
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
}
CenteredText {
fontSize: 15
text: BatteryManager.getBatteryPercentage(BatteryManager.mainBattery) + "%"
}
}
Column {
id: batColumn
property var batInfo: BatteryManager.getMainBatteryInfo()
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
StyledText {
color: Colors.text
fontSize: 14
text: "Battery - " + batColumn.batInfo["name"]
}
StyledText {
color: Colors.subtext1
fontSize: 12
fontWeight: 5
text: batColumn.batInfo["energy"] + "/" + batColumn.batInfo["capacity"] + "Wh - "
+ BatteryManager.getChargingRateFormat()
}
StyledText {
property var timeInfo: batColumn.batInfo["time"]
color: Colors.subtext1
fontSize: 12
fontWeight: 5
text: BatteryManager.getTimeFormat()
}
}
}
}
}
}
component BatteryGradient: MidpointGradient {
anchors.fill: parent
angle: -45
blur: 0.6
color: BatteryManager.getBatteryColor()
midpoint: 0.55
radius: radius
}
}

213
Bar/Bluetooth.qml Normal file
View File

@@ -0,0 +1,213 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import "../Color.js" as Colors
import "../Components/"
import "../Services/"
Container {
id: root
property double devicePlaqueHeight: 60
property double devicePlaqueSpacing: 4
required property HyprlandMonitor monitor
property bool opened: false
animOffset: 200
boxColor: Colors.peach
boxHeight: 25
boxWidth: 33
defaultItem: icon
exclusiveMonitor: root.monitor
exclusiveToScreen: true
forceHidden: !BluetoothManager.anyConnected
states: [
State {
name: "closed"
when: root.hovered == false && root.opened == false
PropertyChanges {
root.boxHeight: 25
root.boxRadius: 9
root.boxWidth: 33
}
},
State {
name: "hovered"
when: root.hovered == true && root.opened == false
PropertyChanges {
root.boxHeight: 28
root.boxWidth: 35
}
},
State {
name: "opened"
when: root.opened == true && root.hovered == true
PropertyChanges {
root.boxHeight: (root.devicePlaqueHeight * BluetoothManager.getConnectedDevicesList().length) + (
root.devicePlaqueSpacing * (BluetoothManager.getConnectedDevicesList().length + 1)) + 2
root.boxRadius: 12
root.boxWidth: 350
}
StateChangeScript {
script: {
stack.replace(deviceList);
}
}
}
]
hover.onHoveredChanged: {
if (hover.hovered == false) {
root.opened = false;
if (root.state != "icon") {
root.stack.replace(icon);
}
}
}
tap.onTapped: {
root.opened = true;
}
Component {
id: icon
StyledText {
property string component: "icon"
horizontalAlignment: Qt.AlignCenter
propo: true
text: "󰂱"
verticalAlignment: Qt.AlignVCenter
}
}
Component {
id: deviceList
Column {
id: deviceListRoot
property string component: "deviceList"
property double plaqueHeight: root.devicePlaqueHeight
property double plaqueSpacing: root.devicePlaqueSpacing
spacing: plaqueSpacing
Item {
implicitHeight: 1
implicitWidth: 10
}
Repeater {
delegate: devicePlaque
model: BluetoothManager.getConnectedDevicesList()
}
Component {
id: devicePlaque
Rectangle {
id: devicePlaqueRoot
readonly property variant deviceText: BluetoothManager.getDeviceText(modelData)
readonly property double margin: 5
required property var modelData
color: Colors.surface1
implicitHeight: deviceListRoot.plaqueHeight
radius: 10
anchors {
left: parent.left
leftMargin: devicePlaqueRoot.margin
right: parent.right
rightMargin: devicePlaqueRoot.margin
}
RowLayout {
id: deviceInfoRow
property double margin: 7
spacing: margin * 2
anchors {
fill: parent
leftMargin: margin
}
Rectangle {
id: iconContainer
property double boxRadius: 4
color: Colors.text
implicitHeight: deviceListRoot.plaqueHeight - (deviceInfoRow.margin * 2)
implicitWidth: deviceListRoot.plaqueHeight - (deviceInfoRow.margin * 2)
radius: boxRadius
Rectangle {
id: batteryIndicator
color: Colors.green
implicitHeight: (deviceListRoot.plaqueHeight - (deviceInfoRow.margin * 2))
* devicePlaqueRoot.deviceText["batteryRaw"]
radius: iconContainer.boxRadius
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
}
HoverHandler {
id: batteryHoverHandler
onHoveredChanged: {
if (batteryHoverHandler.hovered == true) {
batteryText.text = devicePlaqueRoot.deviceText["battery"];
} else {
batteryText.text = devicePlaqueRoot.deviceText["icon"];
}
}
}
CenteredText {
id: batteryText
fontSize: 17
text: devicePlaqueRoot.deviceText["icon"]
}
}
Column {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
StyledText {
color: Colors.text
fontSize: 14
text: devicePlaqueRoot.deviceText["name"]
}
StyledText {
color: Colors.subtext1
fontSize: 10
text: devicePlaqueRoot.deviceText["mac"]
}
}
}
}
}
}
}
}

View File

@@ -1,263 +0,0 @@
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.restart();
}
}
}
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) {
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);
}
}
}
}
}

View File

@@ -1,55 +0,0 @@
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
}
}
}
}
}

View File

@@ -1,62 +0,0 @@
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
}
}
}
}
}

View File

@@ -1,49 +0,0 @@
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
maximumLineCount: 2
text: root.body
verticalAlignment: Text.AlignVCenter
width: root.width - 20
wrapMode: Text.WordWrap
font {
pointSize: 10
weight: 500
}
}
}
}

View File

@@ -1,307 +0,0 @@
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
}
}

View File

@@ -1,70 +0,0 @@
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
}
}

View File

@@ -1,86 +0,0 @@
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
}
}
}
}

View File

@@ -1,87 +0,0 @@
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
}
}
}

161
Bar/Network.qml Normal file
View File

@@ -0,0 +1,161 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import "../Color.js" as Colors
import "../Components/"
import "../Services/"
Container {
id: root
required property HyprlandMonitor monitor
property double networkDisplayHeight: 80
property bool opened: false
animOffset: 250
boxColor: Colors.red
boxHeight: 25
boxWidth: 33
defaultItem: icon
exclusiveMonitor: root.monitor
exclusiveToScreen: true
forceHidden: !NetworkManager.connectedWifi
states: [
State {
name: "closed"
when: root.hovered == false && root.opened == false
PropertyChanges {
root.boxHeight: 25
root.boxRadius: 9
root.boxWidth: 33
}
},
State {
name: "hovered"
when: root.hovered == true && root.opened == false
PropertyChanges {
root.boxHeight: 28
root.boxWidth: 35
}
},
State {
name: "opened"
when: root.opened == true && root.hovered == true
PropertyChanges {
root.boxHeight: root.networkDisplayHeight
root.boxRadius: 12
root.boxWidth: 350
}
StateChangeScript {
script: {
stack.replace(wifiInfo);
}
}
}
]
hover.onHoveredChanged: {
if (hover.hovered == false) {
root.opened = false;
if (root.state != "icon") {
root.stack.replace(icon);
}
}
}
tap.onTapped: {
root.opened = true;
}
Component {
id: icon
StyledText {
horizontalAlignment: Qt.AlignCenter
propo: true
text: NetworkManager.getNetworkDetails(NetworkManager.defaultAdapter)["icon"] || "󰤭"
verticalAlignment: Qt.AlignVCenter
}
}
Component {
id: wifiInfo
Item {
id: networkDisplayRoot
property double innerMargin: 5
property double outerMargin: 6
Rectangle {
id: networkDisplayBg
anchors.fill: parent
anchors.margins: networkDisplayRoot.outerMargin
color: Colors.surface1
radius: 8
RowLayout {
anchors.fill: parent
Rectangle {
id: iconBox
property double iconBoxRadius: 5
Layout.fillHeight: true
Layout.margins: networkDisplayRoot.innerMargin
color: Colors.red
implicitWidth: root.networkDisplayHeight - (networkDisplayRoot.innerMargin * 2) - (
networkDisplayRoot.outerMargin * 2)
radius: 5
CenteredText {
fontSize: 23
propo: true
text: NetworkManager.getNetworkDetails(NetworkManager.defaultAdapter)["icon"]
}
}
Column {
id: networkColumn
property var networkInfo: NetworkManager.getNetworkDetails(NetworkManager.defaultAdapter)
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
StyledText {
color: Colors.text
fontSize: 14
text: networkColumn.networkInfo["networkName"]
}
StyledText {
color: Colors.subtext1
fontSize: 12
fontWeight: 5
text: "Connected on: " + networkColumn.networkInfo["adapterName"]
}
StyledText {
property var timeInfo: networkColumn.networkInfo["time"]
color: Colors.subtext1
fontSize: 12
fontWeight: 5
text: "Network Details " + (networkColumn.networkInfo["saved"] == true ? "Saved" : "Not Saved")
}
}
}
}
}
}
}

View File

@@ -1,27 +0,0 @@
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
spacing: 10
Battery {
exclusiveMonitor: root.monitor
}
Bluetooth {
exclusiveMonitor: root.monitor
}
}

View File

@@ -1,28 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Services.UPower
import "../../Color.js" as Colors
import "../../Components/"
import "../../Services/"
import "Bluetooth"
Container {
id: root
animOffset: 160
boxColor: Colors.green
boxHeight: 25
boxWidth: 40
defaultItem: icon
exclusiveToScreen: true
forceHidden: BatteryManager.battery.isLaptopBattery
Component {
id: icon
CenteredText {
text: BatteryManager.batteryPercentage
}
}
}

View File

@@ -1,98 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Bluetooth
import "../../Color.js" as Colors
import "../../Components/"
import "../../Services/"
import "Bluetooth"
Container {
id: root
property bool shown: false
animOffset: 200
boxColor: Colors.peach
boxHeight: 25
boxWidth: 30
defaultItem: icon
exclusiveToScreen: true
forceHidden: !(root.shown || BluetoothManager.getConnected())
hoverableWhenHidden: true
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);
root.shown = true;
}
}
}
]
hover.onHoveredChanged: {
if (hover.hovered == false) {
root.state = "";
hoverIntervalTimer.start();
} else {
root.shown = true;
hoverIntervalTimer.start();
}
}
tap.onTapped: state = "expanded"
Timer {
id: hoverIntervalTimer
interval: 500
repeat: false
running: false
onTriggered: {
if (root.hover.hovered == false) {
root.shown = false;
}
}
}
Component {
id: icon
Item {
CenteredText {
text: BluetoothManager.getConnected() == true ? "󰂱" : "󰂲"
}
}
}
Component {
id: expanded
Expanded {}
}
}

View File

@@ -1,384 +0,0 @@
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
}
}
}
}

337
Bar/Time.qml Normal file
View File

@@ -0,0 +1,337 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.Notifications
import Quickshell.Widgets
import "../Color.js" as Colors
import "../Components/"
import "../Services/"
Container {
id: root
property Notification latestNotif
required property HyprlandMonitor monitor
property bool mpris: false
property double mprisHeight: 60
property double mprisWidth: 350
property double notificationHeight: 100
property double notificationWidth: 350
property bool notified: false
property bool overriden: false
boxColor: Colors.mauve
defaultItem: time
exclusiveMonitor: root.monitor
exclusiveToScreen: true
states: [
State {
name: "closed"
when: root.hovered == false && root.overriden == false
PropertyChanges {
root.boxHeight: 28
root.boxRadius: 9
root.boxWidth: 100
}
StateChangeScript {
script: {
if (!root.stack.currentItem) {
return;
}
if (root.stack.currentItem.component != "time") {
root.stack.replace(time);
}
}
}
},
State {
name: "hovered"
when: root.hovered == true && root.overriden == false
PropertyChanges {
root.boxHeight: MprisManager.getPlaying() == false ? 32 : root.mprisHeight
root.boxRadius: 12
root.boxWidth: MprisManager.getPlaying() == false ? 107 : root.mprisWidth
}
StateChangeScript {
script: {
root.stack.replace(MprisManager.getPlaying() == true ? mprisToast : time);
}
}
},
State {
name: "notified"
when: root.notified == true
PropertyChanges {
root.boxHeight: root.notificationHeight
root.boxRadius: 12
root.boxWidth: root.notificationWidth
root.visibleTopMargin: 5
}
StateChangeScript {
script: {
root.stack.replace(notification);
notificationTimer.restart();
}
}
},
State {
name: "mpris"
when: root.mpris == true
PropertyChanges {
root.boxHeight: root.mprisHeight
root.boxRadius: 12
root.boxWidth: root.mprisWidth
root.visibleTopMargin: 5
}
StateChangeScript {
script: {
root.stack.replace(mprisToast);
mprisTimer.restart();
}
}
}
]
Connections {
function onNewNotification() {
notification => {
if (notification.lastGeneration == false) {
root.latestNotif = notification;
root.overriden = true;
root.notified = true;
}
};
}
target: NotificationManager
}
Connections {
function onTrackChanged() {
root.overriden = true;
root.mpris = true;
console.log("track changed");
}
target: MprisManager.defaultPlayer
}
Timer {
id: notificationTimer
interval: 2000
repeat: false
running: false
onTriggered: {
root.notified = false;
root.overriden = false;
}
}
Timer {
id: mprisTimer
interval: 3000
repeat: false
running: false
onTriggered: {
root.mpris = false;
root.overriden = false;
}
}
Component {
id: time
StyledText {
property string component: "time"
horizontalAlignment: Qt.AlignCenter
text: Time.time
verticalAlignment: Qt.AlignVCenter
}
}
Component {
id: notification
Item {
id: notificationRoot
property double innerMargin: 5
property double outerMargin: 6
function getInnerHeight() {
let fullHeight = root.notificationHeight;
let fullMargin = notificationRoot.innerMargin + notificationRoot.outerMargin;
return fullHeight - (fullMargin * 2);
}
Rectangle {
color: Colors.surface1
radius: 9
anchors {
fill: parent
margins: notificationRoot.outerMargin
}
RowLayout {
Rectangle {
Layout.margins: notificationRoot.innerMargin
color: Colors.mauve
implicitHeight: notificationRoot.getInnerHeight()
implicitWidth: notificationRoot.getInnerHeight()
radius: 5
IconImage {
anchors.fill: parent
anchors.margins: 10
source: root.latestNotif.image
}
}
ColumnLayout {
property double textMargin: 4
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
Layout.leftMargin: 0
Layout.margins: notificationRoot.innerMargin + textMargin
StyledText {
color: Colors.text
fontSize: 14
text: root.latestNotif.summary
}
StyledText {
Layout.fillHeight: true
Layout.maximumWidth: root.notificationWidth - ((notificationRoot.outerMargin * 2) + (
notificationRoot.innerMargin * 3) + notificationRoot.getInnerHeight())
color: Colors.subtext0
elide: Qt.ElideRight
fontWeight: 5
maximumLineCount: 2
text: root.latestNotif.body
wrapMode: Text.WordWrap
}
}
}
}
}
}
Component {
id: mprisToast
Item {
id: mprisToastRoot
property double innerMargin: 4
property double outerMargin: 4
property var trackInfo: MprisManager.getTrackInfo()
function getInnerHeight() {
let fullHeight = root.mprisHeight;
let fullMargin = mprisToastRoot.innerMargin + mprisToastRoot.outerMargin;
return fullHeight - (fullMargin * 2);
}
Rectangle {
color: Colors.surface1
radius: 9
anchors {
fill: parent
margins: mprisToastRoot.outerMargin
}
RowLayout {
Rectangle {
Layout.margins: mprisToastRoot.innerMargin
color: Colors.mauve
implicitHeight: mprisToastRoot.getInnerHeight()
implicitWidth: mprisToastRoot.getInnerHeight()
radius: 5
IconImage {
anchors.fill: parent
source: mprisToastRoot.trackInfo["albumArt"]
}
}
ColumnLayout {
property double textMargin: 1
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
Layout.leftMargin: 0
Layout.margins: mprisToastRoot.innerMargin + textMargin
StyledText {
Layout.maximumWidth: root.mprisWidth - ((mprisToastRoot.outerMargin * 2) + (
mprisToastRoot.innerMargin * 5) + mprisToastRoot.getInnerHeight())
color: Colors.text
fontSize: 14
text: mprisToastRoot.trackInfo["name"] + " - " + mprisToastRoot.trackInfo["artist"]
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.maximumWidth: root.mprisWidth - ((mprisToastRoot.outerMargin * 2) + (
mprisToastRoot.innerMargin * 5) + mprisToastRoot.getInnerHeight())
Layout.preferredWidth: root.mprisWidth - ((mprisToastRoot.outerMargin * 2) + (
mprisToastRoot.innerMargin * 5) + mprisToastRoot.getInnerHeight())
Rectangle {
anchors.fill: parent
color: Colors.overlay0
radius: 100
Rectangle {
color: Colors.mauve
implicitWidth: parent.width * mprisToastRoot.trackInfo["lengthPercent"]
radius: 100
anchors {
bottom: parent.bottom
left: parent.left
top: parent.top
}
}
StyledText {
text: MprisManager.getTimeString()
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
}
}
}
}
}
}
}
}
}

108
Bar/WorkspaceSelector.qml Normal file
View File

@@ -0,0 +1,108 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import "../Color.js" as Colors
import "../Components/"
import "../Services/"
Container {
id: root
property double barHeight: 25
required property HyprlandMonitor monitor
property double rowLeftMargin: 5
property double workspaceButtonSpacing: 4
property double workspaceCount: WorkspaceManager.getNumberOfWorkspaces(monitor)
property double workspaceWidth: 17
boxHeight: barHeight
boxRadius: 8
boxWidth: (2 * rowLeftMargin) + (workspaceCount * workspaceWidth) + ((workspaceCount - 1)
* workspaceButtonSpacing)
defaultItem: placeholder
exclusiveMonitor: monitor
states: [
State {
name: "hovered"
when: root.hovered
PropertyChanges {
root.barHeight: 30
root.workspaceWidth: 20
}
}
]
Component {
id: placeholder
Item {}
}
RowLayout {
spacing: root.workspaceButtonSpacing
anchors {
bottom: parent.bottom
left: parent.left
leftMargin: root.rowLeftMargin
top: parent.top
}
Repeater {
delegate: workspaceButton
delegateModelAccess: DelegateModel.ReadOnly
model: WorkspaceManager.getWorkspacesForMonitor(root.exclusiveMonitor)
}
Component {
id: workspaceButton
Item {
id: buttonRoot
required property var modelData
property double stateInt: WorkspaceManager.getWorkspaceStateIndex(modelData)
property variant workspaceColors: [Colors.lavender, Colors.overlay2, Colors.surface2]
property string workspaceId: modelData.id
Layout.alignment: Qt.AlignLeft
implicitHeight: root.barHeight - 7
implicitWidth: root.workspaceWidth
Behavior on implicitHeight {
SpringAnimation {
damping: 0.3
spring: 4
}
}
Behavior on implicitWidth {
SpringAnimation {
damping: 0.3
spring: 4
}
}
TapHandler {
id: tapHandler
onTapped: WorkspaceManager.activateWorkspaceById(buttonRoot.modelData.id)
}
Rectangle {
anchors.fill: parent
color: buttonRoot.workspaceColors[buttonRoot.stateInt]
radius: 4
CenteredText {
text: buttonRoot.modelData.id
}
}
}
}
}
}

View File

@@ -9,6 +9,8 @@ import "../Color.js" as Colors
Item { Item {
id: root id: root
property bool _fullyHidden: false
property bool _widthCollapsed: false
property double animOffset: 0 property double animOffset: 0
property color boxColor: Colors.surface0 property color boxColor: Colors.surface0
property double boxHeight: 28 property double boxHeight: 28
@@ -35,13 +37,15 @@ Item {
property bool forceHidden: false property bool forceHidden: false
property alias hover: hoverHandler property alias hover: hoverHandler
property bool hoverableWhenHidden: false property bool hoverableWhenHidden: false
property bool hovered: false
property alias rect: container property alias rect: container
property alias stack: containerContent property alias stack: containerContent
property alias tap: tapHandler property alias tap: tapHandler
property double visibleTopMargin: 0 property double visibleTopMargin: 0
implicitHeight: boxHeight implicitHeight: boxHeight
implicitWidth: boxWidth implicitWidth: root._widthCollapsed ? 0 : root.boxWidth
visible: !root._fullyHidden
Behavior on anchors.topMargin { Behavior on anchors.topMargin {
animation: defaultCurve animation: defaultCurve
@@ -58,13 +62,49 @@ Item {
spring: 4 spring: 4
} }
} }
Behavior on boxWidth { Behavior on implicitWidth {
SpringAnimation { SpringAnimation {
damping: 0.3 damping: 0.3
spring: 4 spring: 4
} }
} }
onForceHiddenChanged: {
if (root.forceHidden == true) {
hideTimer.restart();
} else {
hideTimer.stop();
subHideTimer.stop();
root._fullyHidden = false;
root._widthCollapsed = false;
}
}
Timer {
id: hideTimer
interval: 250
repeat: false
running: false
onTriggered: {
root._widthCollapsed = true;
subHideTimer.restart();
}
}
Timer {
id: subHideTimer
interval: 200
repeat: false
running: false
onTriggered: {
root._fullyHidden = true;
}
}
anchors { anchors {
top: parent.top top: parent.top
topMargin: calculatedTopMargin topMargin: calculatedTopMargin
@@ -79,22 +119,20 @@ Item {
HoverHandler { HoverHandler {
id: hoverHandler id: hoverHandler
onHoveredChanged: {
if (hoverHandler.hovered == true) {
root.hovered = true;
} else {
root.hovered = false;
}
}
} }
TapHandler { TapHandler {
id: tapHandler id: tapHandler
} }
RectangularShadow {
anchors.fill: container
blur: 30
color: Colors.mantle
offset.x: 7
offset.y: 3
radius: container.radius
spread: 10
}
Rectangle { Rectangle {
id: container id: container

View File

@@ -1,19 +1,62 @@
import QtQuick import QtQuick
import QtQuick.Shapes
Gradient { Item {
id: root id: root
required property color color property real angle: 0 // Degrees; 0 = transparent left, colour right
property double midpoint: 0.6 readonly property real angleRadians: angle * Math.PI / 180.0
property real blur: 0.0 // 0 = sharp, 1 = very soft
// Public API
property color color: "#4da3ffff"
// Half the diagonal ensures the gradient covers the entire item.
readonly property real gradientRadius: Math.sqrt(width * width + height * height) / 2.0
property real midpoint: 0.5 // 0..1
property double radius: 10
readonly property real safeBlur: Math.max(blur, 0.00001)
readonly property real safeMidpoint: clamp01(midpoint)
readonly property real transitionEnd: clamp01(safeMidpoint + safeBlur / 2.0)
readonly property real transitionStart: clamp01(safeMidpoint - safeBlur / 2.0)
readonly property color transparentColor: Qt.rgba(color.r, color.g, color.b, 0.0)
function clamp01(value) {
return Math.max(0.0, Math.min(1.0, value));
}
Behavior on color {
ColorAnimation {
duration: 100
}
}
Shape {
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath {
strokeWidth: -1
fillGradient: LinearGradient {
x1: root.width / 2.0 - root.gradientRadius * Math.cos(root.angleRadians)
x2: root.width / 2.0 + root.gradientRadius * Math.cos(root.angleRadians)
y1: root.height / 2.0 - root.gradientRadius * Math.sin(root.angleRadians)
y2: root.height / 2.0 + root.gradientRadius * Math.sin(root.angleRadians)
GradientStop { GradientStop {
color: "transparent" color: root.transparentColor
position: 0.0 position: 0.0
} }
GradientStop { GradientStop {
color: "transparent" color: root.transparentColor
position: root.midpoint position: root.transitionStart
}
GradientStop {
color: root.color
position: root.transitionEnd
} }
GradientStop { GradientStop {
@@ -21,3 +64,12 @@ Gradient {
position: 1.0 position: 1.0
} }
} }
PathRectangle {
height: root.height
radius: Math.min(root.radius, Math.min(width, height) / 2.0)
width: root.width
}
}
}
}

View File

@@ -2,12 +2,18 @@ import QtQuick
import "../Color.js" as Colors import "../Color.js" as Colors
Text { Text {
id: root
property double fontSize: 12
property double fontWeight: 8
property bool propo: false
color: Colors.base color: Colors.base
elide: Text.ElideRight elide: Text.ElideRight
font { font {
family: "FiraMono Nerd Font" family: root.propo == true ? "FiraMono Nerd Font Propo" : "FiraMono Nerd Font"
pointSize: 12 pointSize: fontSize
weight: 800 weight: root.fontWeight * 100
} }
} }

View File

@@ -3,21 +3,160 @@ import QtQuick
import Quickshell import Quickshell
import Quickshell.Services.UPower import Quickshell.Services.UPower
import "../Color.js" as Colors
Singleton { Singleton {
id: root id: root
property UPowerDevice battery: Upower.displayDevice property double batteryPercentage: root.mainBattery.percentage || 0
property bool batteryInUse: root.batteryPresent && root.batteryLaptop property bool batteryPresent: root.mainBattery.ready || false
property bool batteryLaptop: root.battery.isLaptopBattery property UPowerDevice mainBattery: UPower.displayDevice || undefined
property double batteryPercentage: root.battery.percentage property bool hasLaptopBattery: root.mainBattery && root.mainBattery.isLaptopBattery
property bool batteryPresent: root.battery.ready
function getBatteryIcon() {
let battery = root.battery;
if (battery.state = UpowerDeviceState.Charging) { function getBatteryColor() {
let criticalLevel = 20;
let batteryPercentage = getBatteryPercentage(mainBattery);
if (mainBattery.state == UPowerDeviceState.FullyCharged) {
return Colors.sky;
} else if (mainBattery.state == UPowerDeviceState.Charging) {
return Colors.mauve;
} else if (mainBattery.state == UPowerDeviceState.PendingCharge | mainBattery.state
== UPowerDeviceState.PendingDischarge) {
return Colors.peach;
} else if (batteryPercentage <= criticalLevel) {
return Colors.red;
} else {
return Colors.green;
}
}
function getBatteryIcon(battery) {
if (battery.state == UPowerDeviceState.Charging) {
return "󰂄"; return "󰂄";
} } else if (battery.state == UPowerDeviceState.PendingCharge | battery.state
if (battery.state = UpowerDeviceState.Discharging) { == UPowerDeviceState.PendingDischarge) {
return "󰚥";
} else if (battery.state == UPowerDeviceState.Discharging | battery.state
== UPowerDeviceState.FullyCharged) {
let iconArr = ["󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"];
let batteryCharge = battery.percentage;
let chargeIndex = Math.ceil(batteryCharge * iconArr.length) - 1;
return iconArr[chargeIndex];
} }
} }
function getBatteryName(battery) {
let pathsplit = battery.nativePath.split("/");
let batteryName = pathsplit[pathsplit.length];
return pathsplit;
}
function getBatteryPercentage(battery) {
return battery.percentage * 100;
}
function getBatteryText() {
let icon = getBatteryIcon(mainBattery);
let percentage = getBatteryPercentage(mainBattery);
return icon + "" + percentage + "%";
}
function getChargingRateFormat() {
let rate = mainBattery.changeRate.toFixed(1);
let finalString = "harging at " + rate + "W";
if (rate == 0) {
finalString = "Battery Idle";
} else if (rate <= 0 || mainBattery.state == UPowerDeviceState.Charging) {
finalString = "C" + finalString;
} else {
finalString = "Disc" + finalString;
}
return finalString;
}
function getMainBatteryInfo() {
let battery = UPower.devices.values[0];
let batteryState = battery.state;
let batteryName = getBatteryName(battery);
let time = getTimeFormat();
return {
"name": batteryName,
"charge": battery.percentage,
"energy": battery.energy.toFixed(1),
"capacity": battery.energyCapacity.toFixed(1),
"health": battery.healthPercentage
};
}
function getTimeFormat() {
let state = mainBattery.state;
let finalString = "";
if (state == UPowerDeviceState.FullyCharged) {
finalString += "Battery Full";
} else if (state == UPowerDeviceState.Charging) {
let time = mainBattery.timeToFull;
if (time == 0) {
return "Getting Time...";
} else {
let timeArr = secondsToTime(time);
// If more than 1 hour
if (timeArr.hours >= 1 || timeArr.hourNeedsIncrement == 1) {
finalString += timeArr.Hours + " Hours ";
// If minutes not 0
if (timeArr.minutesRounded > 1) {
finalString += timeArr.minutesRounded + " Minutes";
}
} else {
finalString += timeArr.minutes + " Minutes ";
finalString += timeArr.seconds + " Seconds";
}
}
finalString += " Until Full";
} else if (state == UPowerDeviceState.Discharging) {
let time = mainBattery.timeToEmpty;
if (time == 0) {
return "Getting Time...";
} else {
let timeArr = secondsToTime(time);
// If more than 1 hour
if (timeArr.hours >= 1 || timeArr.hourNeedsIncrement == 1) {
finalString += timeArr.hours + " Hours ";
// If minutes not 0
if (timeArr.minutesRounded > 1) {
finalString += timeArr.minutesRounded + " Minutes";
}
} else {
finalString += timeArr.minutes + " Minutes ";
finalString += timeArr.seconds + " Seconds";
}
}
finalString += " Remaining";
} else {
finalString += "Getting Time...";
}
return finalString;
}
function secondsToTime(totalSeconds) {
totalSeconds = Math.max(0, Number(totalSeconds));
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = Math.floor(totalSeconds % 60);
const roundedMinutes = minutes + (seconds >= 30 ? 1 : 0);
const hourNeedsIncrement = roundedMinutes === 60;
return {
hours,
minutes,
seconds,
minutesRounded: hourNeedsIncrement ? 0 : roundedMinutes,
hourNeedsIncrement
};
}
} }

View File

@@ -6,21 +6,59 @@ import Quickshell.Bluetooth
Singleton { Singleton {
id: root id: root
property BluetoothAdapter defaultAdapter: Bluetooth.defaultAdapter property BluetoothAdapter defaultAdapter: Bluetooth.defaultAdapter || null
property string defaultAdapterName: Bluetooth.defaultAdapter.adapterId property string defaultAdapterName: defaultAdapter ? defaultAdapter.adapterId : ""
property bool anyConnected: root.defaultAdapter != null
&& root.defaultAdapter.devices.values.some(device => device.connected)
function getConnected() { function getConnected() {
let deviceList = getDevicesList(); return root.anyConnected;
for (let device in deviceList) { }
let currentDevice = deviceList[device];
if (currentDevice.connected == true) { function getConnectedDevicesList() {
return true; let devicesList = getDevicesList();
if (!devicesList) {
return [];
}
let connectedList = [];
for (let device of devicesList) {
if (device.connected == true) {
connectedList.push(device);
} }
} }
return false; return connectedList;
}
function getDeviceText(device) {
if (!device) {
return undefined;
}
let deviceName = device.deviceName;
let deviceIcon = getIcon(device.icon);
let deviceMac = device.address;
let hasBattery = device.batteryAvailable;
let deviceBattery = deviceIcon;
let deviceBatteryRaw = 0;
if (hasBattery) {
deviceBattery = device.battery * 100;
deviceBatteryRaw = device.battery;
}
return {
"icon": deviceIcon,
"name": deviceName,
"mac": deviceMac,
"hasBattery": hasBattery,
"battery": deviceBattery,
"batteryRaw": deviceBatteryRaw
};
} }
function getDevicesList() { function getDevicesList() {
if (!root.defaultAdapter) {
return [];
}
return root.defaultAdapter.devices.values; return root.defaultAdapter.devices.values;
} }
@@ -41,29 +79,4 @@ Singleton {
return icons[name] || "󰾰"; 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
}
} }

View File

@@ -6,8 +6,7 @@ import Quickshell.Services.Mpris
Singleton { Singleton {
id: root id: root
property MprisPlayer defaultPlayer: Mpris.players.values[0] property MprisPlayer defaultPlayer: Mpris.players.values[0] || null
property real pos: defaultPlayer.position / defaultPlayer.length
function getPlaying() { function getPlaying() {
if (!defaultPlayer) { if (!defaultPlayer) {
@@ -17,6 +16,55 @@ Singleton {
} }
} }
function getTimeString() {
let trackInfo = getTrackInfo();
let position = trackInfo["position"];
let length = trackInfo["length"];
if (length.hours > 0) {
return position.hours + ":" + position.minutes + ":" + position.seconds + "/" + length.hours
+ ":" + length.minutes + ":" + length.seconds;
} else {
return position.minutes + ":" + position.seconds + "/" + length.minutes + ":" + length.seconds;
}
}
function getTrackInfo() {
let trackName = root.defaultPlayer.trackTitle;
let trackArtist = root.defaultPlayer.trackArtist;
let trackAlbum = root.defaultPlayer.trackAlbum;
let trackPosition = root.defaultPlayer.position;
let trackLength = root.defaultPlayer.length;
let trackArt = root.defaultPlayer.trackArtUrl;
return {
"name": trackName,
"artist": trackArtist,
"album": trackAlbum,
"position": msToArray(trackPosition),
"length": msToArray(trackLength),
"lengthRemaining": msToArray(trackLength - trackPosition),
"lengthPercent": trackPosition / trackLength,
"albumArt": trackArt
};
}
function msToArray(time) {
time = Math.max(0, Number(time));
const inthours = Math.floor(time / 3600000);
const intminutes = Math.floor((time % 60000) / 60);
const intseconds = Math.floor((time % 1000) % 60);
const hours = String(inthours).padStart(2, "0");
const minutes = String(intminutes).padStart(2, "0");
const seconds = String(intseconds).padStart(2, "0");
return {
hours,
minutes,
seconds
};
}
function prev() { function prev() {
defaultPlayer.previous(); defaultPlayer.previous();
} }

View File

@@ -0,0 +1,99 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Networking
Singleton {
id: root
property bool connectedWifi: root.defaultAdapter != null && root.defaultAdapter.connected
&& root.defaultAdapter.type == DeviceType.Wifi
property NetworkDevice defaultAdapter: Networking.devices.values[0] || null
function getConnectedNetworks(networksArr) {
let connectedNetworks = [];
for (let network of networksArr) {
if (network.state == ConnectionState.Connected) {
connectedNetworks.push(network);
}
}
return connectedNetworks;
}
function getNetworkDetails(adapter) {
if (!adapter) {
return {
"adapterName": "",
"adapterConnected": false,
"adapterIsWifi": false,
"networkName": "",
"networkStrength": 0,
"icon": "󰤭"
};
}
let networksArr = adapter.networks.values;
let connectedNetworks = getConnectedNetworks(networksArr);
let deviceWifi = isDeviceWifi(adapter);
let primaryNetwork = connectedNetworks[0];
if (!primaryNetwork) {
return {
"adapterName": adapter.name,
"adapterConnected": adapter.connected,
"adapterIsWifi": deviceWifi,
"networkName": "No Network",
"networkStrength": 0,
"icon": "󰤭"
};
}
let networkName = primaryNetwork.name;
let networkStrength = primaryNetwork.signalStrength;
let networkIcon = getWifiIcon(networkStrength);
let outputDict = {
"adapterName": adapter.name,
"adapterConnected": adapter.connected,
"adapterIsWifi": deviceWifi,
"networkName": networkName,
"networkStrength": networkStrength,
"icon": networkIcon,
"saved": primaryNetwork.known
};
return outputDict;
}
function getWifiIcon(strength) {
let iconArr = ["󰤟", "󰤢", "󰤥", "󰤨"];
let strengthIndex = Math.ceil(strength * iconArr.length);
return iconArr[strengthIndex - 1];
}
function getWifiText() {
let info = getNetworkDetails(defaultAdapter);
let adapter = info["adapterName"];
let name = info["networkName"];
let finalString = adapter + " - " + name;
return finalString;
}
function isConnectedAndWifi() {
return root.connectedWifi;
}
function isDeviceWifi(adapter) {
if (adapter.type == DeviceType.Wifi) {
return true;
} else if (adapter.type == DeviceType.Wired) {
return false;
} else {
console.log(adapter.type);
return false;
}
}
}

View File

@@ -2,12 +2,15 @@ pragma Singleton
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
import "../Color.js" as Colors
Singleton { Singleton {
id: root id: root
property alias notif: server property alias notif: server
signal newNotification(notificiation: Notification)
function getLatestNotification() { function getLatestNotification() {
let notificationList = server.trackedNotifications.values; let notificationList = server.trackedNotifications.values;
let len = notificationList.length; let len = notificationList.length;
@@ -24,9 +27,11 @@ Singleton {
actionsSupported: true actionsSupported: true
bodyMarkupSupported: true bodyMarkupSupported: true
bodySupported: true bodySupported: true
imageSupported: true
onNotification: notification => { onNotification: notification => {
notification.tracked = true; notification.tracked = true;
root.newNotification(notification);
} }
} }
} }

View File

@@ -62,6 +62,17 @@ Singleton {
} }
} }
function getWorkspaceStateIndex(workspace) {
let state = getWorkspaceState(workspace);
if (state == "focused") {
return 0;
} else if (state == "active") {
return 1;
} else if (state == "inactive") {
return 2;
}
}
function getWorkspacesForMonitor(monitor) { function getWorkspacesForMonitor(monitor) {
let allWorkspaces = getAllNumberedWorkspaces(); let allWorkspaces = getAllNumberedWorkspaces();
let monitorWorkspaces = []; let monitorWorkspaces = [];

View File

@@ -5,3 +5,4 @@ singleton WorkspaceManager 1.0 WorkspaceManager.qml
singleton BluetoothManager 1.0 BluetoothManager.qml singleton BluetoothManager 1.0 BluetoothManager.qml
singleton MprisManager 1.0 MprisManager.qml singleton MprisManager 1.0 MprisManager.qml
singleton BatteryManager 1.0 BatteryManager.qml singleton BatteryManager 1.0 BatteryManager.qml
singleton NetworkManager 1.0 NetworkManager.qml