add slide-in applet dock with collapsible cards, edge trigger, and bar module toggle
This commit is contained in:
parent
6fd36c812f
commit
c22eb51dcd
14 changed files with 689 additions and 14 deletions
409
shell/dock/AppletDock.qml
Normal file
409
shell/dock/AppletDock.qml
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
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: D.DockState.open
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.layer: D.DockState.mode === "pinned" ? WlrLayer.Top : WlrLayer.Overlay
|
||||
WlrLayershell.exclusiveZone: D.DockState.mode === "pinned" ? _dockWidth : 0
|
||||
WlrLayershell.namespace: "nova-dock"
|
||||
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
anchors.bottom: true
|
||||
|
||||
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
|
||||
|
||||
// Slide animation
|
||||
property real _slideX: D.DockState.open ? 0 : _dockWidth
|
||||
|
||||
Behavior on _slideX {
|
||||
enabled: !S.Theme.reducedMotion
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay mode: close when cursor leaves
|
||||
HoverHandler {
|
||||
id: _dockHover
|
||||
onHoveredChanged: {
|
||||
if (!hovered && D.DockState.mode === "overlay")
|
||||
_overlayCloseTimer.restart();
|
||||
else
|
||||
_overlayCloseTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: _overlayCloseTimer
|
||||
interval: 200
|
||||
onTriggered: if (D.DockState.mode === "overlay")
|
||||
D.DockState.close()
|
||||
}
|
||||
|
||||
// Background
|
||||
Rectangle {
|
||||
id: _bg
|
||||
anchors.fill: parent
|
||||
color: S.Theme.base00
|
||||
opacity: Math.max(S.Theme.barOpacity, 0.85)
|
||||
|
||||
transform: Translate {
|
||||
x: root._slideX
|
||||
}
|
||||
}
|
||||
|
||||
// Accent border on the left edge
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
width: 1
|
||||
color: root._accent
|
||||
opacity: _bg.opacity
|
||||
|
||||
transform: Translate {
|
||||
x: root._slideX
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
Flickable {
|
||||
id: _flickable
|
||||
anchors.fill: parent
|
||||
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.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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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: D.DockState.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shared resources
|
||||
SystemClock {
|
||||
id: _clock
|
||||
precision: SystemClock.Seconds
|
||||
}
|
||||
|
||||
M.ProcessList {
|
||||
id: _cpuProcs
|
||||
sortBy: "cpu"
|
||||
active: _cpuCard.expanded && D.DockState.open
|
||||
}
|
||||
|
||||
M.ProcessList {
|
||||
id: _memProcs
|
||||
sortBy: "mem"
|
||||
active: _memCard.expanded && D.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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue