nova-shell/shell/dock/AppletDock.qml

466 lines
14 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import Quickshell.Services.Mpris
import Quickshell.Services.Pipewire
import "." as D
import "../services" as S
import "../applets" as C
import "../modules" as M
PanelWindow {
id: root
required property var screen
visible: _winVisible
color: "transparent"
property bool _winVisible: false
WlrLayershell.layer: S.DockState.mode === "pinned" ? WlrLayer.Top : WlrLayer.Overlay
WlrLayershell.exclusiveZone: S.DockState.mode === "pinned" ? _dockWidth : 0
WlrLayershell.namespace: "nova-dock"
anchors.top: true
anchors.right: true
anchors.bottom: true
// Counteract the bar's exclusive zone so the dock extends to the top of the screen
margins.top: -S.Theme.barHeight
readonly property int _dockWidth: S.Modules.dock.width ?? 300
readonly property var _applets: S.Modules.dock.applets ?? {}
readonly property color _accent: S.Theme.base0C
implicitWidth: _dockWidth
// Set reserved width for bar/corners when pinned
onVisibleChanged: {
if (S.DockState.mode === "pinned")
S.DockState.reservedWidth = _dockWidth;
}
Connections {
target: S.DockState
function onModeChanged() {
if (S.DockState.mode === "pinned")
S.DockState.reservedWidth = root._dockWidth;
}
}
// Slide animation - managed manually so window stays visible during hide
property real _slideX: _dockWidth
Connections {
target: S.DockState
function onOpenChanged() {
if (S.DockState.open) {
root._winVisible = true;
_hideAnim.stop();
if (S.Theme.reducedMotion) {
root._slideX = 0;
} else {
_showAnim.start();
}
} else {
_showAnim.stop();
if (S.Theme.reducedMotion) {
root._slideX = root._dockWidth;
root._winVisible = false;
} else {
_hideAnim.start();
}
}
}
}
NumberAnimation {
id: _showAnim
target: root
property: "_slideX"
to: 0
duration: 200
easing.type: Easing.OutCubic
}
NumberAnimation {
id: _hideAnim
target: root
property: "_slideX"
to: root._dockWidth
duration: 200
easing.type: Easing.OutCubic
onFinished: root._winVisible = false
}
// Overlay mode: close when cursor leaves
HoverHandler {
id: _dockHover
onHoveredChanged: {
if (!hovered && S.DockState.mode === "overlay")
_overlayCloseTimer.restart();
else
_overlayCloseTimer.stop();
}
}
Timer {
id: _overlayCloseTimer
interval: 200
onTriggered: if (S.DockState.mode === "overlay")
S.DockState.close()
}
// Background - fully opaque when pinned, semi-transparent in overlay
Rectangle {
id: _bg
anchors.fill: parent
color: S.Theme.base00
opacity: S.DockState.mode === "pinned" ? 1.0 : Math.max(S.Theme.barOpacity, 0.85)
transform: Translate {
x: root._slideX
}
}
// Left edge line - marks the virtual screen edge, matches bar border width.
// Uses base09 (rightmost bar gradient color).
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 1
color: S.Theme.base09
opacity: _bg.opacity
transform: Translate {
x: root._slideX
}
}
// Content - inset past the edge line + gap
Flickable {
id: _flickable
anchors.fill: parent
anchors.leftMargin: S.Theme.groupSpacing + 1
contentHeight: _column.height
clip: true
boundsBehavior: Flickable.StopAtBounds
transform: Translate {
x: root._slideX
}
Column {
id: _column
width: root._dockWidth
spacing: 8
topPadding: 8
bottomPadding: 8
leftPadding: 8
rightPadding: 8
// Clock
D.DockCard {
visible: root._applets.clock ?? true
icon: "\uF017"
title: "Clock"
accentColor: root._accent
width: parent.width - 16
C.ClockApplet {
width: parent.width
accentColor: root._accent
currentDate: _clock.date
}
}
// CPU
D.DockCard {
id: _cpuCard
visible: root._applets.cpu ?? true
icon: "\uF2DB"
title: "CPU"
accentColor: root._accent
width: parent.width - 16
C.CpuApplet {
width: parent.width
cores: S.SystemStats.cpuCores
coreMaxFreq: S.SystemStats.cpuCoreMaxFreq
coreTypes: S.SystemStats.cpuCoreTypes
processes: _cpuProcs.processes
accentColor: root._accent
active: _cpuCard.expanded
}
}
// GPU
D.DockCard {
visible: (root._applets.gpu ?? true) && S.SystemStats.gpuAvailable
icon: "\uEB4C"
title: "GPU"
accentColor: root._accent
width: parent.width - 16
C.GpuApplet {
width: parent.width
active: parent.expanded
accentColor: root._accent
}
}
// Memory
D.DockCard {
id: _memCard
visible: root._applets.memory ?? true
icon: "\uEFC5"
title: "Memory"
accentColor: root._accent
width: parent.width - 16
C.MemoryApplet {
width: parent.width
percent: S.SystemStats.memPercent
usedGb: S.SystemStats.memUsedGb
totalGb: S.SystemStats.memTotalGb
availGb: S.SystemStats.memAvailGb
cachedGb: S.SystemStats.memCachedGb
buffersGb: S.SystemStats.memBuffersGb
processes: _memProcs.processes
accentColor: root._accent
active: _memCard.expanded
}
}
// Temperature
D.DockCard {
visible: root._applets.temperature ?? true
icon: "\uF2C9"
title: "Temperature"
accentColor: root._accent
width: parent.width - 16
C.TemperatureApplet {
width: parent.width
temp: S.SystemStats.tempCelsius
warm: S.Modules.temperature.warm || 80
hot: S.Modules.temperature.hot || 90
history: S.SystemStats.tempHistory
devices: S.SystemStats.tempDevices
accentColor: root._accent
deviceFilter: S.Modules.temperature.device || ""
active: parent.expanded
}
}
// Disk
D.DockCard {
visible: root._applets.disk ?? true
icon: "\uF0C9"
title: "Disk"
accentColor: root._accent
width: parent.width - 16
C.DiskApplet {
width: parent.width
mounts: S.SystemStats.diskMounts
accentColor: root._accent
}
}
// Battery
D.DockCard {
visible: (root._applets.battery ?? true) && S.BatteryService.available
icon: "\uDB80\uDC84"
title: "Battery"
accentColor: root._accent
width: parent.width - 16
C.BatteryApplet {
width: parent.width
active: parent.expanded
accentColor: root._accent
}
}
// Network
D.DockCard {
visible: root._applets.network ?? true
icon: "\uF1EB"
title: "Network"
accentColor: root._accent
width: parent.width - 16
C.NetworkApplet {
width: parent.width
accentColor: root._accent
active: parent.expanded
}
}
// Bluetooth
D.DockCard {
visible: (root._applets.bluetooth ?? true) && S.BluetoothService.state !== "unavailable"
icon: "\uF294"
title: "Bluetooth"
accentColor: root._accent
width: parent.width - 16
C.BluetoothApplet {
width: parent.width
accentColor: root._accent
active: parent.expanded
}
}
// Volume
D.DockCard {
visible: root._applets.volume ?? true
icon: "\uF028"
title: "Sound"
accentColor: root._accent
width: parent.width - 16
C.VolumeApplet {
width: parent.width
sink: Pipewire.defaultAudioSink
sinkList: root._sinkList
streamList: root._streamList
accentColor: root._accent
}
}
// Backlight
D.DockCard {
visible: (root._applets.backlight ?? true) && S.BacklightService.available
icon: "\uF185"
title: "Brightness"
accentColor: root._accent
width: parent.width - 16
C.BacklightApplet {
width: parent.width
percent: S.BacklightService.percent
accentColor: root._accent
onSetPercent: pct => S.BacklightService.setPercent(pct)
}
}
// Weather
D.DockCard {
visible: (root._applets.weather ?? true) && S.WeatherService.available
icon: S.WeatherService.icon
title: "Weather"
accentColor: root._accent
width: parent.width - 16
C.WeatherApplet {
width: parent.width
accentColor: root._accent
}
}
// Now Playing
D.DockCard {
visible: (root._applets.mpris ?? true) && S.MprisService.player !== null
icon: "\uF04B"
title: "Now Playing"
accentColor: root._accent
width: parent.width - 16
C.MprisApplet {
width: parent.width
player: S.MprisService.player
players: S.MprisService.players
playing: S.MprisService.playing
accentColor: root._accent
cachedArt: S.MprisService.cachedArt
playerIdx: S.MprisService.playerIdx
onPlayerSwitched: idx => S.MprisService.switchPlayer(idx)
}
}
// Notifications
D.DockCard {
visible: root._applets.notifications ?? true
icon: "\uDB80\uDC9C"
title: "Notifications"
accentColor: root._accent
width: parent.width - 16
C.NotifApplet {
width: parent.width
contentWidth: root._dockWidth - 16
accentColor: root._accent
}
}
// Power
D.DockCard {
visible: root._applets.power ?? true
icon: "\uF011"
title: "Power"
accentColor: root._accent
width: parent.width - 16
C.PowerApplet {
width: parent.width
accentColor: root._accent
onRunCommand: cmd => {
_runner.command = cmd;
_runner.running = true;
}
onDismiss: S.DockState.close()
}
}
}
}
// Shared resources
SystemClock {
id: _clock
precision: SystemClock.Seconds
}
M.ProcessList {
id: _cpuProcs
sortBy: "cpu"
active: _cpuCard.expanded && S.DockState.open
}
M.ProcessList {
id: _memProcs
sortBy: "mem"
active: _memCard.expanded && S.DockState.open
}
PwObjectTracker {
objects: [Pipewire.defaultAudioSink, ...root._streamList]
}
readonly property var _sinkList: {
const sinks = [];
if (Pipewire.nodes) {
for (const node of Pipewire.nodes.values)
if (!node.isStream && node.isSink)
sinks.push(node);
}
return sinks;
}
readonly property var _streamList: {
const streams = [];
if (Pipewire.nodes) {
for (const node of Pipewire.nodes.values)
if (node.isStream && node.audio)
streams.push(node);
}
return streams;
}
Process {
id: _runner
}
}