mirror of
https://github.com/belsabbagh/dotfiles.git
synced 2026-04-11 17:47:09 +00:00
quickshell and hyprland additions
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.modules.functions
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: container
|
||||
property string displayName: screen?.name ?? ""
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
|
||||
property Toplevel activeToplevel: Compositor.isWorkspaceOccupied(Compositor.focusedWorkspaceId)
|
||||
? Compositor.activeToplevel
|
||||
: null
|
||||
|
||||
implicitWidth: row.implicitWidth + 30
|
||||
implicitHeight: ConfigResolver.bar(displayName).modules.height
|
||||
|
||||
function simplifyTitle(title) {
|
||||
if (!title)
|
||||
return ""
|
||||
|
||||
title = title.replace(/[●⬤○◉◌◎]/g, "") // Symbols to remove
|
||||
|
||||
// Normalize separators
|
||||
title = title
|
||||
.replace(/\s*[|—]\s*/g, " - ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
|
||||
const parts = title.split(" - ").map(p => p.trim()).filter(Boolean)
|
||||
|
||||
if (parts.length === 1)
|
||||
return parts[0]
|
||||
|
||||
// Known app names (extend freely my fellow contributors)
|
||||
const apps = [
|
||||
"Firefox", "Mozilla Firefox",
|
||||
"Chromium", "Google Chrome",
|
||||
"Neovim", "VS Code", "Code",
|
||||
"Kitty", "Alacritty", "Terminal",
|
||||
"Discord", "Spotify", "Steam",
|
||||
"Settings - Nucleus", "Settings"
|
||||
]
|
||||
|
||||
let app = ""
|
||||
for (let i = parts.length - 1; i >= 0; i--) { // loop over
|
||||
for (let a of apps) {
|
||||
if (parts[i].includes(a)) {
|
||||
app = a
|
||||
break
|
||||
}
|
||||
}
|
||||
if (app) break
|
||||
}
|
||||
|
||||
if (!app)
|
||||
app = parts[parts.length - 1]
|
||||
|
||||
const context = parts.find(p => p !== app)
|
||||
|
||||
return context ? `${app} · ${context}` : app
|
||||
}
|
||||
|
||||
|
||||
function formatAppId(appId) { // Random ass function to make it look good
|
||||
if (!appId || appId.length === 0)
|
||||
return "";
|
||||
|
||||
// split on dashes/underscores
|
||||
const parts = appId.split(/[-_]/);
|
||||
// capitalize each segment
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const p = parts[i];
|
||||
parts[i] = p.charAt(0).toUpperCase() + p.slice(1);
|
||||
}
|
||||
return parts.join("-");
|
||||
}
|
||||
|
||||
/* Column {
|
||||
id: col
|
||||
anchors.centerIn: parent
|
||||
|
||||
StyledText {
|
||||
id: workspaceText
|
||||
font.pixelSize: Metrics.fontSize("smallie")
|
||||
text: {
|
||||
if (!activeToplevel)
|
||||
return "Desktop"
|
||||
|
||||
const id = activeToplevel.appId || ""
|
||||
|
||||
return id // Just for aesthetics
|
||||
}
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: titleText
|
||||
text: StringUtils.shortText(simplifyTitle(activeToplevel?.title, 24) || `Workspace ${Hyprland.focusedWorkspaceId}`)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: Metrics.fontSize("smalle")
|
||||
}
|
||||
} */
|
||||
|
||||
Rectangle {
|
||||
visible: (ConfigResolver.bar(displayName).position === "top" || ConfigResolver.bar(displayName).position === "bottom")
|
||||
color: Appearance.m3colors.m3paddingContainer
|
||||
anchors.fill: parent
|
||||
height: 34
|
||||
width: row.height + 30
|
||||
radius: ConfigResolver.bar(displayName).modules.radius
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
spacing: 12
|
||||
anchors.centerIn: parent
|
||||
|
||||
MaterialSymbol {
|
||||
icon: "desktop_windows"
|
||||
rotation: (ConfigResolver.bar(displayName).position === "left" || ConfigResolver.bar(displayName).position === "right") ? 270 : 0
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: StringUtils.shortText(simplifyTitle(activeToplevel?.title), 24) || `Workspace ${Hyprland.focusedWorkspaceId}`
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: Appearance.font.size.small
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
id: batteryIndicatorModuleContainer
|
||||
|
||||
visible: UPower.batteryPresent
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
// Determine if bar is isVertical
|
||||
property bool isVertical: ConfigResolver.bar(screen?.name ?? "").position === "left" || ConfigResolver.bar(screen?.name ?? "").position === "right"
|
||||
|
||||
implicitWidth: bgRect.implicitWidth
|
||||
implicitHeight: bgRect.implicitHeight
|
||||
|
||||
Rectangle {
|
||||
id: bgRect
|
||||
color: isVertical ? Appearance.m3colors.m3primary : Appearance.m3colors.m3paddingContainer
|
||||
radius: ConfigResolver.bar(screen?.name ?? "").modules.radius * Config.runtime.appearance.rounding.factor // No need to use metrics here...
|
||||
|
||||
implicitWidth: child.implicitWidth + Appearance.margin.large - (isVertical ? 10 : 0)
|
||||
implicitHeight: ConfigResolver.bar(screen?.name ?? "").modules.height
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: child
|
||||
anchors.centerIn: parent
|
||||
spacing: isVertical ? 0 : Metrics.spacing(8)
|
||||
|
||||
// Icon for isVertical bars
|
||||
MaterialSymbol {
|
||||
visible: isVertical
|
||||
icon: UPower.battIcon
|
||||
iconSize: Metrics.iconSize(20)
|
||||
}
|
||||
|
||||
// Battery percentage text
|
||||
StyledText {
|
||||
animate: false
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
rotation: isVertical ? 270 : 0
|
||||
text: (isVertical ? UPower.percentage : UPower.percentage + "%")
|
||||
}
|
||||
|
||||
// Circular progress for horizontal bars
|
||||
CircularProgressBar {
|
||||
visible: !isVertical
|
||||
value: UPower.percentage / 100
|
||||
icon: UPower.battIcon
|
||||
iconSize: Metrics.iconSize(18)
|
||||
Layout.bottomMargin: Metrics.margin(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.services
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
|
||||
Item {
|
||||
id: clockContainer
|
||||
|
||||
property string format: isVertical ? "hh\nmm\nAP" : "hh:mm • dd/MM"
|
||||
property bool isVertical: (ConfigResolver.bar(screen?.name ?? "").position === "left" || ConfigResolver.bar(screen?.name ?? "").position === "right")
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
implicitWidth: 37
|
||||
implicitHeight: 30
|
||||
|
||||
AnimatedImage {
|
||||
id: art
|
||||
anchors.fill: parent
|
||||
source: Directories.assetsPath + "/gifs/bongo-cat.gif"
|
||||
cache: false // this is important
|
||||
smooth: true // smooooooth
|
||||
rotation: isVertical ? 270 : 0
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.services
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
|
||||
Item {
|
||||
id: clockContainer
|
||||
|
||||
property string format: isVertical ? "hh\nmm\nAP" : "hh:mm • dd/MM"
|
||||
property bool isVertical: (ConfigResolver.bar(screen?.name ?? "").position === "left" || ConfigResolver.bar(screen?.name ?? "").position === "right")
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
implicitWidth: bgRect.implicitWidth
|
||||
implicitHeight: bgRect.implicitHeight
|
||||
|
||||
// Let the layout compute size automatically
|
||||
|
||||
Rectangle {
|
||||
id: bgRect
|
||||
|
||||
color: isVertical ? "transparent" : Appearance.m3colors.m3paddingContainer
|
||||
radius: ConfigResolver.bar(screen?.name ?? "").modules.radius * Config.runtime.appearance.rounding.factor
|
||||
// Padding around the text
|
||||
implicitWidth: isVertical ? textItem.implicitWidth + 40 : textItem.implicitWidth + Metrics.margin("large")
|
||||
implicitHeight: ConfigResolver.bar(screen?.name ?? "").modules.height
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: textItem
|
||||
anchors.centerIn: parent
|
||||
animate: false
|
||||
text: Time.format(clockContainer.format)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Io
|
||||
|
||||
import qs.config
|
||||
import qs.modules.functions
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
id: mediaPlayer
|
||||
|
||||
property bool isVertical: (
|
||||
ConfigResolver.bar(screen?.name ?? "").position === "left" ||
|
||||
ConfigResolver.bar(screen?.name ?? "").position === "right"
|
||||
)
|
||||
|
||||
Layout.alignment: Qt.AlignCenter | Qt.AlignVCenter
|
||||
|
||||
implicitWidth: bgRect.implicitWidth
|
||||
implicitHeight: bgRect.implicitHeight
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: bgRect
|
||||
|
||||
color: Appearance.m3colors.m3paddingContainer
|
||||
radius: ConfigResolver.bar(screen?.name ?? "").modules.radius *
|
||||
Config.runtime.appearance.rounding.factor
|
||||
|
||||
implicitWidth: isVertical
|
||||
? row.implicitWidth + Metrics.margin("large") - 10
|
||||
: row.implicitWidth + Metrics.margin("large")
|
||||
|
||||
implicitHeight: ConfigResolver.bar(screen?.name ?? "").modules.height
|
||||
}
|
||||
|
||||
|
||||
Row {
|
||||
id: row
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Metrics.margin("small")
|
||||
|
||||
|
||||
ClippingRectangle {
|
||||
id: iconButton
|
||||
|
||||
width: 24
|
||||
height: 24
|
||||
radius: ConfigResolver.bar(screen?.name ?? "").modules.radius / 1.2
|
||||
|
||||
color: Appearance.colors.colLayer1Hover
|
||||
opacity: 0.9
|
||||
|
||||
clip: true
|
||||
layer.enabled: true
|
||||
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
|
||||
Image {
|
||||
id: art
|
||||
|
||||
anchors.fill: parent
|
||||
visible: Mpris.artUrl !== ""
|
||||
|
||||
source: Mpris.artUrl
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
smooth: true
|
||||
mipmap: true
|
||||
}
|
||||
|
||||
|
||||
MaterialSymbol {
|
||||
anchors.centerIn: parent
|
||||
|
||||
visible: Mpris.artUrl === ""
|
||||
icon: "music_note"
|
||||
|
||||
iconSize: 18
|
||||
color: Config.runtime.appearance.theme === "dark"
|
||||
? "#b1a4a4"
|
||||
: "grey"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onClicked: Mpris.playPause()
|
||||
|
||||
onEntered: iconButton.opacity = 1
|
||||
onExited: iconButton.opacity = 0.9
|
||||
}
|
||||
|
||||
|
||||
RotationAnimation on rotation {
|
||||
from: 0
|
||||
to: 360
|
||||
|
||||
duration: Metrics.chronoDuration(4000)
|
||||
loops: Animation.Infinite
|
||||
|
||||
running: Mpris.isPlaying &&
|
||||
Config.runtime.appearance.animations.enabled
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
id: textItem
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
text: StringUtils.shortText(Mpris.title, 16)
|
||||
|
||||
visible: !mediaPlayer.isVertical
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
id: statusIconsContainer
|
||||
|
||||
property bool isVertical: (ConfigResolver.bar(screen?.name ?? "").position === "left" || ConfigResolver.bar(screen?.name ?? "").position === "right")
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: ConfigResolver.bar(screen?.name ?? "").modules.statusIcons.enabled
|
||||
implicitWidth: bgRect.implicitWidth
|
||||
implicitHeight: bgRect.implicitHeight
|
||||
|
||||
StyledRect {
|
||||
id: bgRect
|
||||
|
||||
color: Globals.visiblility.sidebarRight ? Appearance.m3colors.m3paddingContainer : "transparent"
|
||||
radius: ConfigResolver.bar(screen?.name ?? "").modules.radius * Config.runtime.appearance.rounding.factor
|
||||
implicitWidth: isVertical ? contentRow.implicitWidth + Metrics.margin("large") - 8 : contentRow.implicitWidth + Metrics.margin("large")
|
||||
implicitHeight: ConfigResolver.bar(screen?.name ?? "").modules.height
|
||||
|
||||
RowLayout {
|
||||
id: contentRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: isVertical ? Metrics.spacing(8) : Metrics.spacing(16)
|
||||
|
||||
|
||||
MaterialSymbol {
|
||||
id: wifi
|
||||
animate: false
|
||||
visible: ConfigResolver.bar(screen?.name ?? "").modules.statusIcons.networkStatusEnabled
|
||||
rotation: isVertical ? 270 : 0
|
||||
icon: Network.icon
|
||||
iconSize: Metrics.fontSize("huge")
|
||||
}
|
||||
|
||||
MaterialSymbol {
|
||||
id: btIcon
|
||||
animate: false
|
||||
visible: ConfigResolver.bar(screen?.name ?? "").modules.statusIcons.bluetoothStatusEnabled
|
||||
rotation: isVertical ? 270 : 0
|
||||
icon: Bluetooth.icon
|
||||
iconSize: Metrics.fontSize("huge")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (Globals.visiblility.sidebarLeft)
|
||||
return
|
||||
Globals.visiblility.sidebarRight = !Globals.visiblility.sidebarRight
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import qs.modules.components
|
||||
import qs.config
|
||||
import qs.services
|
||||
import Quickshell.Services.SystemTray
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
readonly property Repeater items: items
|
||||
property bool horizontalMode: (ConfigResolver.bar(screen?.name ?? "").position === "top" || ConfigResolver.bar(screen?.name ?? "").position === "bottom")
|
||||
clip: true
|
||||
implicitWidth: layout.implicitWidth + Metrics.margin("verylarge")
|
||||
implicitHeight: 34
|
||||
|
||||
Rectangle {
|
||||
visible: (items.count > 0) ? 1 : 0
|
||||
id: padding
|
||||
implicitHeight: padding.height
|
||||
anchors.fill: parent
|
||||
radius: ConfigResolver.bar(screen?.name ?? "").modules.radius
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: layout
|
||||
anchors.centerIn: parent
|
||||
rows: 1
|
||||
columns: items.count
|
||||
rowSpacing: Metrics.spacing(10)
|
||||
columnSpacing: Metrics.spacing(10)
|
||||
|
||||
Repeater {
|
||||
id: items
|
||||
model: SystemTray.items
|
||||
|
||||
delegate: Item {
|
||||
id: trayItemRoot
|
||||
required property SystemTrayItem modelData
|
||||
implicitWidth: 20
|
||||
implicitHeight: 20
|
||||
|
||||
IconImage {
|
||||
visible: trayItemRoot.modelData.icon !== ""
|
||||
source: trayItemRoot.modelData.icon
|
||||
asynchronous: true
|
||||
anchors.fill: parent
|
||||
rotation: root.horizontalMode ? 0 : 270
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hover
|
||||
}
|
||||
|
||||
QsMenuOpener {
|
||||
id: menuOpener
|
||||
menu: trayItemRoot.modelData.menu
|
||||
}
|
||||
|
||||
StyledPopout {
|
||||
id: popout
|
||||
hoverTarget: hover
|
||||
interactable: true
|
||||
hCenterOnItem: true
|
||||
requiresHover: false
|
||||
|
||||
Component {
|
||||
Item {
|
||||
width: childColumn.implicitWidth
|
||||
height: childColumn.height
|
||||
|
||||
ColumnLayout {
|
||||
id: childColumn
|
||||
spacing: Metrics.spacing(5)
|
||||
|
||||
Repeater {
|
||||
model: menuOpener.children
|
||||
delegate: TrayMenuItem {
|
||||
parentColumn: childColumn
|
||||
Layout.preferredWidth: childColumn.width > 0 ? childColumn.width : implicitWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
hoverEnabled: true
|
||||
|
||||
onClicked: {
|
||||
if (popout.isVisible)
|
||||
popout.hide();
|
||||
else
|
||||
popout.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component TrayMenuItem: Item {
|
||||
id: itemRoot
|
||||
required property QsMenuEntry modelData
|
||||
required property ColumnLayout parentColumn
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: rowLayout.implicitWidth + 10
|
||||
implicitHeight: !itemRoot.modelData.isSeparator ? rowLayout.implicitHeight + 10 : 1
|
||||
|
||||
MouseArea {
|
||||
id: hover
|
||||
hoverEnabled: itemRoot.modelData.enabled
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (!itemRoot.modelData.hasChildren)
|
||||
itemRoot.modelData.triggered();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: itemBg
|
||||
anchors.fill: parent
|
||||
opacity: itemRoot.modelData.isSeparator ? 0.5 : 1
|
||||
color: itemRoot.modelData.isSeparator ? Appearance.m3colors.m3outline : hover.containsMouse ? Appearance.m3colors.m3surfaceContainer : Appearance.m3colors.m3surface
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
visible: !itemRoot.modelData.isSeparator
|
||||
opacity: itemRoot.modelData.isSeparator ? 0.5 : 1
|
||||
spacing: Metrics.spacing(5)
|
||||
anchors {
|
||||
left: itemBg.left
|
||||
leftMargin: Metrics.margin(5)
|
||||
top: itemBg.top
|
||||
topMargin:Metrics.margin(5)
|
||||
}
|
||||
|
||||
IconImage {
|
||||
visible: itemRoot.modelData.icon !== ""
|
||||
source: itemRoot.modelData.icon
|
||||
width: 15
|
||||
height: 15
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: itemRoot.modelData.text
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
MaterialSymbol {
|
||||
visible: itemRoot.modelData.hasChildren
|
||||
icon: "chevron_right"
|
||||
iconSize: Metrics.iconSize(16)
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
id: systemUsageContainer
|
||||
property string displayName: screen?.name ?? ""
|
||||
property bool isHorizontal: (ConfigResolver.bar(displayName).position === "top" || ConfigResolver.bar(displayName).position === "bottom")
|
||||
|
||||
visible: ConfigResolver.bar(displayName).modules.systemUsage.enabled && haveWidth
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
implicitWidth: bgRect.implicitWidth
|
||||
implicitHeight: bgRect.implicitHeight
|
||||
|
||||
property bool haveWidth:
|
||||
ConfigResolver.bar(displayName).modules.systemUsage.tempStatsEnabled ||
|
||||
ConfigResolver.bar(displayName).modules.systemUsage.cpuStatsEnabled ||
|
||||
ConfigResolver.bar(displayName).modules.systemUsage.memoryStatsEnabled
|
||||
|
||||
|
||||
// Normalize values so UI always receives correct ranges
|
||||
function normalize(v) {
|
||||
if (v > 1) return v / 100
|
||||
return v
|
||||
}
|
||||
|
||||
function percent(v) {
|
||||
if (v <= 1) return Math.round(v * 100)
|
||||
return Math.round(v)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bgRect
|
||||
color: Appearance.m3colors.m3paddingContainer
|
||||
radius: ConfigResolver.bar(displayName).modules.radius * Config.runtime.appearance.rounding.factor
|
||||
|
||||
implicitWidth: child.implicitWidth + Metrics.margin("large")
|
||||
implicitHeight: ConfigResolver.bar(displayName).modules.height
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: child
|
||||
anchors.centerIn: parent
|
||||
spacing: Metrics.spacing(4)
|
||||
|
||||
// CPU
|
||||
CircularProgressBar {
|
||||
rotation: !isHorizontal ? 270 : 0
|
||||
icon: "developer_board"
|
||||
visible: ConfigResolver.bar(displayName).modules.systemUsage.cpuStatsEnabled
|
||||
iconSize: Metrics.iconSize(14)
|
||||
value: normalize(SystemDetails.cpuPercent)
|
||||
Layout.bottomMargin: Metrics.margin(2)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: ConfigResolver.bar(displayName).modules.systemUsage.cpuStatsEnabled && isHorizontal
|
||||
animate: false
|
||||
text: percent(SystemDetails.cpuPercent) + "%"
|
||||
}
|
||||
|
||||
// RAM
|
||||
CircularProgressBar {
|
||||
rotation: !isHorizontal ? 270 : 0
|
||||
Layout.leftMargin: Metrics.margin(4)
|
||||
icon: "memory_alt"
|
||||
visible: ConfigResolver.bar(displayName).modules.systemUsage.memoryStatsEnabled
|
||||
iconSize: Metrics.iconSize(14)
|
||||
value: normalize(SystemDetails.ramPercent)
|
||||
Layout.bottomMargin: Metrics.margin(2)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: ConfigResolver.bar(displayName).modules.systemUsage.memoryStatsEnabled && isHorizontal
|
||||
animate: false
|
||||
text: percent(SystemDetails.ramPercent) + "%"
|
||||
}
|
||||
|
||||
// Temperature
|
||||
CircularProgressBar {
|
||||
rotation: !isHorizontal ? 270 : 0
|
||||
visible: ConfigResolver.bar(displayName).modules.systemUsage.tempStatsEnabled
|
||||
Layout.leftMargin: Metrics.margin(4)
|
||||
icon: "device_thermostat"
|
||||
iconSize: Metrics.iconSize(14)
|
||||
value: normalize(SystemDetails.cpuTempPercent)
|
||||
Layout.bottomMargin: Metrics.margin(2)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: ConfigResolver.bar(displayName).modules.systemUsage.tempStatsEnabled && isHorizontal
|
||||
animate: false
|
||||
text: percent(SystemDetails.cpuTempPercent) + "%"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import QtQuick.Layouts
|
||||
|
||||
StyledRect {
|
||||
id: bg
|
||||
|
||||
property string icon
|
||||
property color iconColor: Appearance.syntaxHighlightingTheme
|
||||
property int iconSize
|
||||
property bool toggle
|
||||
property bool transparentBg: false
|
||||
|
||||
signal toggled(bool value)
|
||||
|
||||
color: (ma.containsMouse && !transparentBg)
|
||||
? Appearance.m3colors.m3paddingContainer
|
||||
: "transparent"
|
||||
|
||||
radius: Metrics.radius("childish")
|
||||
|
||||
implicitWidth: textItem.implicitWidth + 12
|
||||
implicitHeight: textItem.implicitHeight + 6
|
||||
|
||||
MaterialSymbol {
|
||||
id: textItem
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: 0.4
|
||||
anchors.horizontalCenterOffset: 0.499
|
||||
iconSize: bg.iconSize
|
||||
icon: bg.icon
|
||||
color: bg.iconColor
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: ma
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: bg.toggled(!bg.toggle)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.modules.functions
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
id: workspaceContainer
|
||||
property string displayName: screen?.name ?? ""
|
||||
property int numWorkspaces: ConfigResolver.bar(displayName).modules.workspaces.workspaceIndicators
|
||||
property var workspaceOccupied: []
|
||||
property var occupiedRanges: []
|
||||
|
||||
function japaneseNumber(num) {
|
||||
var kanjiMap = {
|
||||
"0": "零",
|
||||
"1": "一",
|
||||
"2": "二",
|
||||
"3": "三",
|
||||
"4": "四",
|
||||
"5": "五",
|
||||
"6": "六",
|
||||
"7": "七",
|
||||
"8": "八",
|
||||
"9": "九",
|
||||
"10": "十"
|
||||
};
|
||||
return kanjiMap[num] !== undefined ? kanjiMap[num] : "Number out of range";
|
||||
}
|
||||
|
||||
function updateWorkspaceOccupied() {
|
||||
const offset = 1;
|
||||
workspaceOccupied = Array.from({
|
||||
"length": numWorkspaces
|
||||
}, (_, i) => {
|
||||
return Compositor.isWorkspaceOccupied(i + 1);
|
||||
});
|
||||
const ranges = [];
|
||||
let start = -1;
|
||||
for (let i = 0; i < workspaceOccupied.length; i++) {
|
||||
if (workspaceOccupied[i]) {
|
||||
if (start === -1)
|
||||
start = i;
|
||||
|
||||
} else if (start !== -1) {
|
||||
ranges.push({
|
||||
"start": start,
|
||||
"end": i - 1
|
||||
});
|
||||
start = -1;
|
||||
}
|
||||
}
|
||||
if (start !== -1)
|
||||
ranges.push({
|
||||
"start": start,
|
||||
"end": workspaceOccupied.length - 1
|
||||
});
|
||||
|
||||
occupiedRanges = ranges;
|
||||
}
|
||||
|
||||
visible: ConfigResolver.bar(displayName).modules.workspaces.enabled
|
||||
implicitWidth: bg.implicitWidth
|
||||
implicitHeight: ConfigResolver.bar(displayName).modules.height
|
||||
Component.onCompleted: updateWorkspaceOccupied()
|
||||
|
||||
Connections {
|
||||
function onStateChanged() {
|
||||
updateWorkspaceOccupied();
|
||||
}
|
||||
|
||||
target: Compositor
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bg
|
||||
|
||||
color: Appearance.m3colors.m3paddingContainer
|
||||
radius: ConfigResolver.bar(displayName).modules.radius * Config.runtime.appearance.rounding.factor
|
||||
implicitWidth: workspaceRow.implicitWidth + Metrics.margin("large") - 8
|
||||
implicitHeight: ConfigResolver.bar(displayName).modules.height
|
||||
|
||||
// occupied background highlight
|
||||
Item {
|
||||
id: occupiedStretchLayer
|
||||
|
||||
anchors.centerIn: workspaceRow
|
||||
width: workspaceRow.width
|
||||
height: 26
|
||||
z: 0
|
||||
visible: Compositor.require("hyprland") // Hyprland only
|
||||
|
||||
Repeater {
|
||||
model: occupiedRanges
|
||||
|
||||
Rectangle {
|
||||
height: 26
|
||||
radius: ConfigResolver.bar(displayName).modules.radius * Config.runtime.appearance.rounding.factor
|
||||
color: ColorUtils.mix(Appearance.m3colors.m3tertiary, Appearance.m3colors.m3surfaceContainerLowest)
|
||||
opacity: 0.8
|
||||
x: modelData.start * (26 + workspaceRow.spacing)
|
||||
width: (modelData.end - modelData.start + 1) * 26 + (modelData.end - modelData.start) * workspaceRow.spacing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// workspace highlight
|
||||
Rectangle {
|
||||
id: highlight
|
||||
|
||||
property int offset: Compositor.require("hyprland") ? 1 : 0
|
||||
property int index: Math.max(0, Compositor.focusedWorkspaceId - 1 - offset)
|
||||
property real itemWidth: 26
|
||||
property real spacing: workspaceRow.spacing
|
||||
property int highlightIndex: {
|
||||
if (!Compositor.focusedWorkspaceId)
|
||||
return 0;
|
||||
|
||||
if (Compositor.require("hyprland"))
|
||||
return Compositor.focusedWorkspaceId - 1;
|
||||
// Hyprland starts at 2 internally
|
||||
return Compositor.focusedWorkspaceId - 2; // Niri or default
|
||||
}
|
||||
property real targetX: Math.min(highlightIndex, numWorkspaces - 1) * (itemWidth + spacing) + 7.3
|
||||
property real animatedX1: targetX
|
||||
property real animatedX2: targetX
|
||||
|
||||
x: Math.min(animatedX1, animatedX2)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Math.abs(animatedX2 - animatedX1) + itemWidth - 1
|
||||
height: 24
|
||||
radius: ConfigResolver.bar(displayName).modules.radius * Config.runtime.appearance.rounding.factor
|
||||
color: Appearance.m3colors.m3tertiary
|
||||
onTargetXChanged: {
|
||||
animatedX1 = targetX;
|
||||
animatedX2 = targetX;
|
||||
}
|
||||
|
||||
Behavior on animatedX1 {
|
||||
enabled: Config.runtime.appearance.animations.enabled
|
||||
|
||||
NumberAnimation {
|
||||
duration: Metrics.chronoDuration(400)
|
||||
easing.type: Easing.OutSine
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on animatedX2 {
|
||||
enabled: Config.runtime.appearance.animations.enabled
|
||||
|
||||
NumberAnimation {
|
||||
duration: Metrics.chronoDuration(133)
|
||||
easing.type: Easing.OutSine
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: workspaceRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Metrics.spacing(10)
|
||||
|
||||
Repeater {
|
||||
model: numWorkspaces
|
||||
|
||||
Item {
|
||||
property int wsIndex: index + 1
|
||||
property bool occupied: Compositor.isWorkspaceOccupied(wsIndex)
|
||||
property bool focused: wsIndex === Compositor.focusedWorkspaceId
|
||||
|
||||
width: 26
|
||||
height: 26
|
||||
|
||||
// Icon container — only used on Hyprland
|
||||
ClippingRectangle {
|
||||
id: iconContainer
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: 20
|
||||
height: 20
|
||||
color: "transparent"
|
||||
radius: Appearance.rounding.small
|
||||
clip: true
|
||||
|
||||
IconImage {
|
||||
id: appIcon
|
||||
|
||||
anchors.fill: parent
|
||||
visible: Compositor.require("hyprland") && ConfigResolver.bar(displayName).modules.workspaces.showAppIcons && occupied
|
||||
rotation: (ConfigResolver.bar(displayName).position === "left" || ConfigResolver.bar(displayName).position === "right") ? 270 : 0
|
||||
source: {
|
||||
const win = Compositor.focusedWindowForWorkspace(wsIndex);
|
||||
return win ? AppRegistry.iconForClass(win.class) : "";
|
||||
}
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
saturation: (Config.runtime.appearance.tintIcons || (Config.runtime.appearance.colors.matugenScheme === "scheme-monochrome" && Config.runtime.appearance.colors.autogenerated) || Config.runtime.appearance.colors.scheme.toLowerCase() === "monochrome") ? -1.0 : 1.0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Kanji mode — only if not Hyprland
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
visible: ConfigResolver.bar(displayName).modules.workspaces.showJapaneseNumbers && !ConfigResolver.bar(displayName).modules.workspaces.showAppIcons
|
||||
text: japaneseNumber(index + 1)
|
||||
rotation: (ConfigResolver.bar(displayName).position === "left" || ConfigResolver.bar(displayName).position === "right") ? 270 : 0
|
||||
}
|
||||
|
||||
// Numbers mode — only if not Hyprland
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
visible: !ConfigResolver.bar(displayName).modules.workspaces.showJapaneseNumbers && !ConfigResolver.bar(displayName).modules.workspaces.showAppIcons
|
||||
text: index + 1
|
||||
rotation: (ConfigResolver.bar(displayName).position === "left" || ConfigResolver.bar(displayName).position === "right") ? 270 : 0
|
||||
}
|
||||
|
||||
// Symbols for unoccupied workspaces — only for Hyprland icons
|
||||
MaterialSymbol {
|
||||
property string displayText: Config.runtime.appearance.rounding.factor === 0 ? "crop_square" : "fiber_manual_record"
|
||||
|
||||
anchors.centerIn: parent
|
||||
visible: Compositor.require("hyprland") && ConfigResolver.bar(displayName).modules.workspaces.showAppIcons && !occupied
|
||||
text: displayText
|
||||
rotation: (ConfigResolver.bar(displayName).position === "left" || ConfigResolver.bar(displayName).position === "right") ? 270 : 0
|
||||
font.pixelSize: Metrics.iconSize(10)
|
||||
fill: 1
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: Compositor.changeWorkspace(wsIndex)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user