add full proj
This commit is contained in:
69
widgets/Bar.qml
Normal file
69
widgets/Bar.qml
Normal file
@@ -0,0 +1,69 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import "../config"
|
||||
|
||||
// One bar instance per monitor.
|
||||
PanelWindow {
|
||||
id: panel
|
||||
required property var modelData
|
||||
screen: modelData
|
||||
|
||||
color: "transparent"
|
||||
|
||||
// Auto-rotation flips the output transform and resizes this layer surface
|
||||
// in place; the old buffer bleeds through the area that isn't fully
|
||||
// repainted, leaving stale "garbage" pixels. Remapping the surface on any
|
||||
// geometry change forces Sway to hand us a fresh, fully-painted buffer.
|
||||
readonly property string geomKey: modelData
|
||||
? (modelData.width + "x" + modelData.height)
|
||||
: ""
|
||||
onGeomKeyChanged: Qt.callLater(function () {
|
||||
if (!panel.visible) return;
|
||||
panel.visible = false;
|
||||
panel.visible = true;
|
||||
})
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
implicitHeight: Theme.barHeight
|
||||
|
||||
// background strip
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.barColor
|
||||
}
|
||||
|
||||
// left: workspaces
|
||||
Workspaces {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Theme.gap + 2
|
||||
screen: panel.modelData
|
||||
}
|
||||
|
||||
// center: clock
|
||||
Clock {
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
// right: battery, system metrics, volume, tray
|
||||
RowLayout {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: Theme.gap + 2
|
||||
spacing: Theme.gap
|
||||
|
||||
Battery {}
|
||||
CpuGraph {}
|
||||
CpuTemp {}
|
||||
Ram {}
|
||||
Disk {}
|
||||
Network {}
|
||||
Volume {}
|
||||
Tray { panelWindow: panel }
|
||||
}
|
||||
}
|
||||
17
widgets/Battery.qml
Normal file
17
widgets/Battery.qml
Normal file
@@ -0,0 +1,17 @@
|
||||
import "../config"
|
||||
import "../services"
|
||||
|
||||
MetricPill {
|
||||
readonly property bool charging: SysStats.batteryStatus === "Charging"
|
||||
|| SysStats.batteryStatus === "Full"
|
||||
|
||||
visible: SysStats.hasBattery
|
||||
|
||||
icon: charging ? Icons.bolt : Icons.battery(SysStats.battery)
|
||||
iconColor: charging ? Theme.green
|
||||
: SysStats.battery <= 15 ? Theme.red
|
||||
: SysStats.battery <= 30 ? Theme.peach
|
||||
: Theme.green
|
||||
value: SysStats.battery + "%"
|
||||
reserve: "100%"
|
||||
}
|
||||
52
widgets/Clock.qml
Normal file
52
widgets/Clock.qml
Normal file
@@ -0,0 +1,52 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import "../config"
|
||||
|
||||
Pill {
|
||||
id: root
|
||||
background: Theme.surface1
|
||||
property var now: new Date()
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
running: true
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: root.now = new Date()
|
||||
}
|
||||
|
||||
Text {
|
||||
text: Icons.clock
|
||||
font.family: Theme.monoFont
|
||||
font.pixelSize: Theme.fontSize + 1
|
||||
color: Theme.lavender
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: dateMetrics
|
||||
font: dateText.font
|
||||
// widest day/month abbreviations in this locale
|
||||
text: "Wed 00 MMM"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: dateText
|
||||
text: Qt.formatDateTime(root.now, "ddd dd MMM")
|
||||
font.family: Theme.font
|
||||
font.pixelSize: Theme.fontSize
|
||||
color: Theme.subtext
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: dateMetrics.advanceWidth
|
||||
}
|
||||
|
||||
Text {
|
||||
text: Qt.formatDateTime(root.now, "HH:mm")
|
||||
font.family: Theme.font
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.bold: true
|
||||
color: Theme.text
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
66
widgets/CpuGraph.qml
Normal file
66
widgets/CpuGraph.qml
Normal file
@@ -0,0 +1,66 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import "../config"
|
||||
import "../services"
|
||||
|
||||
// CPU usage as a live filled-area graph plus the current percentage.
|
||||
Pill {
|
||||
id: root
|
||||
spacing: Theme.spacing
|
||||
|
||||
Text {
|
||||
text: Icons.cpu
|
||||
font.family: Theme.monoFont
|
||||
font.pixelSize: Theme.fontSize + 1
|
||||
color: Theme.loadColor(SysStats.cpu)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
// One vertical bar per core, filled from the bottom by that core's load.
|
||||
Row {
|
||||
id: cores
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: 1
|
||||
readonly property int barH: Theme.barHeight - Theme.gap * 2 - 12
|
||||
|
||||
Repeater {
|
||||
model: SysStats.coreCount
|
||||
delegate: Rectangle {
|
||||
readonly property real load: SysStats.coreLoads[index] || 0
|
||||
width: 3
|
||||
height: cores.barH
|
||||
radius: 1
|
||||
color: Qt.rgba(1, 1, 1, 0.08) // unfilled track
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
radius: 1
|
||||
height: Math.max(1, parent.height * (parent.load / 100))
|
||||
color: Theme.loadColor(parent.load)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: SysStats.cpu.toFixed(0) + "%"
|
||||
font.family: Theme.font
|
||||
font.pixelSize: Theme.fontSize
|
||||
color: Theme.text
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: 42 // fits "100%" at fontSize 15
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
|
||||
// Max current core frequency, shown in GHz.
|
||||
Text {
|
||||
text: (SysStats.cpuFreq / 1000).toFixed(1) + "GHz"
|
||||
font.family: Theme.font
|
||||
font.pixelSize: Theme.fontSize
|
||||
color: Theme.subtext
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: 56 // fits "0.0GHz" at fontSize 15
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
}
|
||||
13
widgets/CpuTemp.qml
Normal file
13
widgets/CpuTemp.qml
Normal file
@@ -0,0 +1,13 @@
|
||||
import "../config"
|
||||
import "../services"
|
||||
|
||||
MetricPill {
|
||||
icon: Icons.thermo
|
||||
// temperature thresholds differ from load: warm >70, hot >85
|
||||
iconColor: SysStats.temp >= 85 ? Theme.red
|
||||
: SysStats.temp >= 70 ? Theme.peach
|
||||
: SysStats.temp >= 55 ? Theme.yellow
|
||||
: Theme.green
|
||||
value: SysStats.temp + "°C"
|
||||
reserve: "100°C"
|
||||
}
|
||||
9
widgets/Disk.qml
Normal file
9
widgets/Disk.qml
Normal file
@@ -0,0 +1,9 @@
|
||||
import "../config"
|
||||
import "../services"
|
||||
|
||||
MetricPill {
|
||||
icon: Icons.disk
|
||||
iconColor: Theme.loadColor(SysStats.disk)
|
||||
value: SysStats.disk + "%"
|
||||
reserve: "100%"
|
||||
}
|
||||
40
widgets/MetricPill.qml
Normal file
40
widgets/MetricPill.qml
Normal file
@@ -0,0 +1,40 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import "../config"
|
||||
|
||||
// A pill showing a coloured Nerd Font icon followed by a value string.
|
||||
Pill {
|
||||
id: root
|
||||
property string icon: ""
|
||||
property string value: ""
|
||||
property color iconColor: Theme.text
|
||||
property alias label: valueText
|
||||
// Widest value the pill will ever show, e.g. "100%". When set, the value
|
||||
// column reserves that width so changing values never shift other widgets.
|
||||
property string reserve: ""
|
||||
|
||||
Text {
|
||||
text: root.icon
|
||||
font.family: Theme.monoFont
|
||||
font.pixelSize: Theme.fontSize + 1
|
||||
color: root.iconColor
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: reserveMetrics
|
||||
font: valueText.font
|
||||
text: root.reserve
|
||||
}
|
||||
|
||||
Text {
|
||||
id: valueText
|
||||
text: root.value
|
||||
font.family: Theme.font
|
||||
font.pixelSize: Theme.fontSize
|
||||
color: Theme.text
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: root.reserve ? reserveMetrics.advanceWidth : implicitWidth
|
||||
}
|
||||
}
|
||||
43
widgets/Network.qml
Normal file
43
widgets/Network.qml
Normal file
@@ -0,0 +1,43 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import "../config"
|
||||
import "../services"
|
||||
|
||||
// Down/up throughput on the active interface, with a wifi/ethernet icon.
|
||||
Pill {
|
||||
Text {
|
||||
text: SysStats.iface.startsWith("wl") ? Icons.wifi : Icons.ethernet
|
||||
font.family: Theme.monoFont
|
||||
font.pixelSize: Theme.fontSize + 1
|
||||
color: Theme.sky
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
// reserve the width of the widest rate string so values never shift
|
||||
TextMetrics {
|
||||
id: rateMetrics
|
||||
font: rxText.font
|
||||
text: Icons.down + " 99.9M"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: rxText
|
||||
text: Icons.down + " " + SysStats.fmtRate(SysStats.rxRate)
|
||||
font.family: Theme.monoFont
|
||||
font.pixelSize: Theme.fontSize - 1
|
||||
color: Theme.green
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: rateMetrics.advanceWidth
|
||||
}
|
||||
|
||||
Text {
|
||||
text: Icons.up + " " + SysStats.fmtRate(SysStats.txRate)
|
||||
font.family: Theme.monoFont
|
||||
font.pixelSize: Theme.fontSize - 1
|
||||
color: Theme.peach
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: rateMetrics.advanceWidth
|
||||
}
|
||||
}
|
||||
23
widgets/Pill.qml
Normal file
23
widgets/Pill.qml
Normal file
@@ -0,0 +1,23 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import "../config"
|
||||
|
||||
// Rounded background container used by every bar module for a consistent look.
|
||||
Rectangle {
|
||||
id: pill
|
||||
|
||||
default property alias content: row.data
|
||||
property int spacing: Theme.spacing
|
||||
property color background: Theme.surface0
|
||||
|
||||
implicitWidth: row.implicitWidth + Theme.padding * 2
|
||||
implicitHeight: Theme.barHeight - Theme.gap * 2
|
||||
radius: Theme.radius
|
||||
color: background
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
anchors.centerIn: parent
|
||||
spacing: pill.spacing
|
||||
}
|
||||
}
|
||||
9
widgets/Ram.qml
Normal file
9
widgets/Ram.qml
Normal file
@@ -0,0 +1,9 @@
|
||||
import "../config"
|
||||
import "../services"
|
||||
|
||||
MetricPill {
|
||||
icon: Icons.memory
|
||||
iconColor: Theme.loadColor(SysStats.mem)
|
||||
value: SysStats.mem.toFixed(0) + "%"
|
||||
reserve: "100%"
|
||||
}
|
||||
62
widgets/Tray.qml
Normal file
62
widgets/Tray.qml
Normal file
@@ -0,0 +1,62 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
import "../config"
|
||||
|
||||
// StatusNotifier system tray. Left-click activates, middle-click does the
|
||||
// secondary action, right-click opens the item's native menu.
|
||||
Pill {
|
||||
id: root
|
||||
// the PanelWindow this tray lives in, needed to anchor context menus
|
||||
property var panelWindow
|
||||
|
||||
visible: SystemTray.items.values.length > 0
|
||||
spacing: Theme.spacing + 2
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items
|
||||
|
||||
Item {
|
||||
id: entry
|
||||
required property var modelData
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
implicitWidth: 18
|
||||
implicitHeight: 18
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: entry.modelData.icon
|
||||
sourceSize.width: 18
|
||||
sourceSize.height: 18
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||
onClicked: mouse => {
|
||||
const item = entry.modelData;
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (item.onlyMenu) openMenu();
|
||||
else item.activate();
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
item.secondaryActivate();
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
openMenu();
|
||||
}
|
||||
}
|
||||
function openMenu() {
|
||||
const item = entry.modelData;
|
||||
if (!item.hasMenu || !root.panelWindow) return;
|
||||
const p = entry.mapToItem(null, 0, entry.height + Theme.gap);
|
||||
item.display(root.panelWindow, p.x, p.y);
|
||||
}
|
||||
onWheel: wheel =>
|
||||
entry.modelData.scroll(wheel.angleDelta.y, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
widgets/Volume.qml
Normal file
63
widgets/Volume.qml
Normal file
@@ -0,0 +1,63 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Pipewire
|
||||
import "../config"
|
||||
|
||||
// PipeWire default sink volume. Scroll to adjust, click to mute.
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
readonly property var sink: Pipewire.defaultAudioSink
|
||||
readonly property var audio: sink ? sink.audio : null
|
||||
readonly property bool muted: audio ? audio.muted : true
|
||||
readonly property int volPct: audio ? Math.round(audio.volume * 100) : 0
|
||||
|
||||
implicitWidth: pill.implicitWidth
|
||||
implicitHeight: pill.implicitHeight
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
onClicked: { if (audio) audio.muted = !audio.muted; }
|
||||
onWheel: wheel => {
|
||||
if (!audio) return;
|
||||
audio.volume = Math.max(0, Math.min(1,
|
||||
audio.volume + (wheel.angleDelta.y > 0 ? 0.05 : -0.05)));
|
||||
}
|
||||
|
||||
// keep the sink's audio properties live
|
||||
PwObjectTracker { objects: root.sink ? [root.sink] : [] }
|
||||
|
||||
Pill {
|
||||
id: pill
|
||||
anchors.fill: parent
|
||||
|
||||
Text {
|
||||
text: root.muted ? Icons.volMute
|
||||
: root.volPct < 50 ? Icons.volLow
|
||||
: Icons.volHigh
|
||||
font.family: Theme.monoFont
|
||||
font.pixelSize: Theme.fontSize + 1
|
||||
color: root.muted ? Theme.overlay : Theme.teal
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: Theme.fontSize + 4
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: volMetrics
|
||||
font: volText.font
|
||||
text: "muted"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: volText
|
||||
text: root.muted ? "muted" : root.volPct + "%"
|
||||
font.family: Theme.font
|
||||
font.pixelSize: Theme.fontSize
|
||||
color: Theme.text
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: volMetrics.advanceWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
54
widgets/Workspaces.qml
Normal file
54
widgets/Workspaces.qml
Normal file
@@ -0,0 +1,54 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.I3
|
||||
import "../config"
|
||||
|
||||
// Sway/i3 workspace switcher for a single monitor.
|
||||
Row {
|
||||
id: root
|
||||
required property var screen // ShellScreen this bar lives on
|
||||
spacing: Theme.gap
|
||||
|
||||
// Name of the i3/sway output this bar is on, used to filter workspaces.
|
||||
readonly property string monitorName: screen ? screen.name : ""
|
||||
|
||||
Repeater {
|
||||
model: I3.workspaces
|
||||
|
||||
Rectangle {
|
||||
id: chip
|
||||
required property var modelData
|
||||
// only show workspaces belonging to this monitor
|
||||
// I3Workspace.monitor is an I3Monitor object, so compare its name
|
||||
visible: modelData.monitor && modelData.monitor.name === root.monitorName
|
||||
width: visible ? Math.max(height, label.implicitWidth + 16) : 0
|
||||
height: Theme.barHeight - Theme.gap * 2
|
||||
radius: Theme.radius
|
||||
|
||||
readonly property bool isFocused: modelData.focused
|
||||
readonly property bool isUrgent: modelData.urgent
|
||||
|
||||
color: isUrgent ? Theme.red
|
||||
: isFocused ? Theme.blue
|
||||
: modelData.active ? Theme.surface1
|
||||
: Theme.surface0
|
||||
|
||||
Text {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
text: chip.modelData.name
|
||||
font.family: Theme.font
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.bold: chip.isFocused || chip.isUrgent
|
||||
color: (chip.isFocused || chip.isUrgent) ? Theme.mantle : Theme.text
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: chip.modelData.activate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user