mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 21:45:38 -05:00
initial structure refactor
This commit is contained in:
117
Modules/TopBar/AudioVisualization.qml
Normal file
117
Modules/TopBar/AudioVisualization.qml
Normal file
@@ -0,0 +1,117 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Mpris
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var audioLevels: [0, 0, 0, 0]
|
||||
readonly property MprisPlayer activePlayer: MprisController.activePlayer
|
||||
readonly property bool hasActiveMedia: activePlayer !== null
|
||||
property bool cavaAvailable: false
|
||||
|
||||
width: 20
|
||||
height: Theme.iconSize
|
||||
|
||||
Process {
|
||||
id: cavaCheck
|
||||
|
||||
command: ["which", "cava"]
|
||||
running: true
|
||||
onExited: (exitCode) => {
|
||||
root.cavaAvailable = exitCode === 0;
|
||||
if (root.cavaAvailable) {
|
||||
console.log("cava found - enabling real audio visualization");
|
||||
cavaProcess.running = Qt.binding(() => {
|
||||
return root.hasActiveMedia && root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing;
|
||||
});
|
||||
} else {
|
||||
console.log("cava not found - using fallback animation");
|
||||
fallbackTimer.running = Qt.binding(() => {
|
||||
return root.hasActiveMedia && root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: cavaProcess
|
||||
|
||||
running: false
|
||||
command: ["sh", "-c", `printf '[general]\nmode=normal\nframerate=30\nautosens=0\nsensitivity=50\nbars=4\n[output]\nmethod=raw\nraw_target=/dev/stdout\ndata_format=ascii\nchannels=mono\nmono_option=average\n[smoothing]\nnoise_reduction=20' | cava -p /dev/stdin`]
|
||||
onRunningChanged: {
|
||||
if (!running)
|
||||
root.audioLevels = [0, 0, 0, 0];
|
||||
|
||||
}
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (data) => {
|
||||
if (data.trim()) {
|
||||
let points = data.split(";").map((p) => {
|
||||
return parseFloat(p.trim());
|
||||
}).filter((p) => {
|
||||
return !isNaN(p);
|
||||
});
|
||||
if (points.length >= 4)
|
||||
root.audioLevels = [points[0], points[1], points[2], points[3]];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: fallbackTimer
|
||||
|
||||
running: false
|
||||
interval: 100
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
root.audioLevels = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20];
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: 4
|
||||
|
||||
Rectangle {
|
||||
width: 3
|
||||
height: {
|
||||
if (root.activePlayer && root.activePlayer.playbackState === MprisPlaybackState.Playing && root.audioLevels.length > index) {
|
||||
const rawLevel = root.audioLevels[index] || 0;
|
||||
const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100;
|
||||
const maxHeight = Theme.iconSize - 2;
|
||||
const minHeight = 3;
|
||||
return minHeight + (scaledLevel / 100) * (maxHeight - minHeight);
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
radius: 1.5
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: 80
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
77
Modules/TopBar/ClockWidget.qml
Normal file
77
Modules/TopBar/ClockWidget.qml
Normal file
@@ -0,0 +1,77 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property date currentDate: new Date()
|
||||
|
||||
signal clockClicked()
|
||||
|
||||
width: clockRow.implicitWidth + Theme.spacingS * 2
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: clockMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
Component.onCompleted: {
|
||||
root.currentDate = systemClock.date;
|
||||
}
|
||||
|
||||
Row {
|
||||
id: clockRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: Prefs.use24HourClock ? Qt.formatTime(root.currentDate, "H:mm") : Qt.formatTime(root.currentDate, "h:mm AP")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: Qt.formatDate(root.currentDate, "ddd d")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SystemClock {
|
||||
id: systemClock
|
||||
|
||||
precision: SystemClock.Seconds
|
||||
onDateChanged: root.currentDate = systemClock.date
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clockMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.clockClicked();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
134
Modules/TopBar/ControlCenterButton.qml
Normal file
134
Modules/TopBar/ControlCenterButton.qml
Normal file
@@ -0,0 +1,134 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool isActive: false
|
||||
|
||||
signal clicked()
|
||||
|
||||
width: Math.max(80, controlIndicators.implicitWidth + Theme.spacingS * 2)
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: controlCenterArea.containsMouse || root.isActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
|
||||
Row {
|
||||
id: controlIndicators
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
// Network Status Icon
|
||||
DankIcon {
|
||||
name: {
|
||||
if (NetworkService.networkStatus === "ethernet") {
|
||||
return "lan";
|
||||
} else if (NetworkService.networkStatus === "wifi") {
|
||||
switch (WifiService.wifiSignalStrength) {
|
||||
case "excellent":
|
||||
return "wifi";
|
||||
case "good":
|
||||
return "wifi_2_bar";
|
||||
case "fair":
|
||||
return "wifi_1_bar";
|
||||
case "poor":
|
||||
return "wifi_calling_3";
|
||||
default:
|
||||
return "wifi";
|
||||
}
|
||||
} else {
|
||||
return "wifi_off";
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize - 8
|
||||
color: NetworkService.networkStatus !== "disconnected" ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: true
|
||||
}
|
||||
|
||||
// Bluetooth Icon (when available and enabled) - moved next to network
|
||||
DankIcon {
|
||||
name: "bluetooth"
|
||||
size: Theme.iconSize - 8
|
||||
color: BluetoothService.enabled ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: BluetoothService.available && BluetoothService.enabled
|
||||
}
|
||||
|
||||
// Audio Icon with scroll wheel support
|
||||
Rectangle {
|
||||
width: audioIcon.implicitWidth + 4
|
||||
height: audioIcon.implicitHeight + 4
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
DankIcon {
|
||||
id: audioIcon
|
||||
|
||||
name: AudioService.sinkMuted ? "volume_off" : AudioService.volumeLevel < 33 ? "volume_down" : "volume_up"
|
||||
size: Theme.iconSize - 8
|
||||
color: audioWheelArea.containsMouse || controlCenterArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// Scroll up - increase volume
|
||||
// Scroll down - decrease volume
|
||||
|
||||
id: audioWheelArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function(wheelEvent) {
|
||||
let delta = wheelEvent.angleDelta.y;
|
||||
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0;
|
||||
let newVolume;
|
||||
if (delta > 0)
|
||||
newVolume = Math.min(100, currentVolume + 5);
|
||||
else
|
||||
newVolume = Math.max(0, currentVolume - 5);
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
}
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Microphone Icon (when active)
|
||||
DankIcon {
|
||||
name: "mic"
|
||||
size: Theme.iconSize - 8
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: false // TODO: Add mic detection
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: controlCenterArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
85
Modules/TopBar/FocusedAppWidget.qml
Normal file
85
Modules/TopBar/FocusedAppWidget.qml
Normal file
@@ -0,0 +1,85 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
width: Math.max(contentRow.implicitWidth + Theme.spacingS * 2, 60)
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
clip: true
|
||||
visible: FocusedWindowService.niriAvailable && (FocusedWindowService.focusedAppName || FocusedWindowService.focusedWindowTitle)
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
id: appText
|
||||
|
||||
text: FocusedWindowService.focusedAppName || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
// Limit app name width
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
width: Math.min(implicitWidth, 120)
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: appText.text && titleText.text
|
||||
}
|
||||
|
||||
Text {
|
||||
id: titleText
|
||||
|
||||
text: FocusedWindowService.focusedWindowTitle || ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
// Limit title width - increased for longer titles
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
width: Math.min(implicitWidth, 350)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// Non-interactive widget - just provides hover state for visual feedback
|
||||
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Smooth width animation when the text changes
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
46
Modules/TopBar/LauncherButton.qml
Normal file
46
Modules/TopBar/LauncherButton.qml
Normal file
@@ -0,0 +1,46 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
signal clicked()
|
||||
|
||||
property bool isActive: false
|
||||
|
||||
readonly property bool nerdFontAvailable: Qt.fontFamilies()
|
||||
.indexOf("Symbols Nerd Font") !== -1
|
||||
|
||||
width: 40
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: launcherArea.containsMouse || isActive ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: nerdFontAvailable && OSDetectorService.osLogo || "apps"
|
||||
font.family: nerdFontAvailable && OSDetectorService.osLogo ? "Symbols Nerd Font" : Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: launcherArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
256
Modules/TopBar/MediaWidget.qml
Normal file
256
Modules/TopBar/MediaWidget.qml
Normal file
@@ -0,0 +1,256 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.Mpris
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
readonly property MprisPlayer activePlayer: MprisController.activePlayer
|
||||
readonly property bool playerAvailable: activePlayer !== null
|
||||
readonly property int contentWidth: Math.min(280, mediaRow.implicitWidth + Theme.spacingS * 2)
|
||||
|
||||
signal clicked()
|
||||
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
states: [
|
||||
State {
|
||||
name: "shown"
|
||||
when: playerAvailable
|
||||
|
||||
PropertyChanges {
|
||||
target: root
|
||||
opacity: 1
|
||||
width: contentWidth
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "hidden"
|
||||
when: !playerAvailable
|
||||
|
||||
PropertyChanges {
|
||||
target: root
|
||||
opacity: 0
|
||||
width: 0
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "shown"
|
||||
to: "hidden"
|
||||
|
||||
SequentialAnimation {
|
||||
PauseAnimation {
|
||||
duration: 500
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
properties: "opacity,width"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
Transition {
|
||||
from: "hidden"
|
||||
to: "shown"
|
||||
|
||||
NumberAnimation {
|
||||
properties: "opacity,width"
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
Row {
|
||||
id: mediaRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
// Media info section (clickable to open full player)
|
||||
Row {
|
||||
id: mediaInfo
|
||||
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
AudioVisualization {
|
||||
width: 20
|
||||
height: Theme.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id: mediaText
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 140
|
||||
text: {
|
||||
if (!activePlayer || !activePlayer.trackTitle)
|
||||
return "";
|
||||
|
||||
let identity = activePlayer.identity || "";
|
||||
let isWebMedia = identity.toLowerCase().includes("firefox") || identity.toLowerCase().includes("chrome") || identity.toLowerCase().includes("chromium") || identity.toLowerCase().includes("edge") || identity.toLowerCase().includes("safari");
|
||||
let title = "";
|
||||
let subtitle = "";
|
||||
if (isWebMedia && activePlayer.trackTitle) {
|
||||
title = activePlayer.trackTitle;
|
||||
subtitle = activePlayer.trackArtist || identity;
|
||||
} else {
|
||||
title = activePlayer.trackTitle || "Unknown Track";
|
||||
subtitle = activePlayer.trackArtist || "";
|
||||
}
|
||||
if (title.length > 20)
|
||||
title = title.substring(0, 20) + "...";
|
||||
|
||||
if (subtitle.length > 22)
|
||||
subtitle = subtitle.substring(0, 22) + "...";
|
||||
|
||||
return subtitle.length > 0 ? title + " • " + subtitle : title;
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Control buttons
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// Previous button
|
||||
Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: prevArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
visible: root.playerAvailable
|
||||
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_previous"
|
||||
size: 12
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: prevArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (activePlayer)
|
||||
activePlayer.previous();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Play/Pause button
|
||||
Rectangle {
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
|
||||
visible: root.playerAvailable
|
||||
opacity: activePlayer ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow"
|
||||
size: 14
|
||||
color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (activePlayer)
|
||||
activePlayer.togglePlaying();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Next button
|
||||
Rectangle {
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: nextArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
visible: playerAvailable
|
||||
opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_next"
|
||||
size: 12
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nextArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (activePlayer)
|
||||
activePlayer.next();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
57
Modules/TopBar/NotificationCenterButton.qml
Normal file
57
Modules/TopBar/NotificationCenterButton.qml
Normal file
@@ -0,0 +1,57 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool hasUnread: false
|
||||
property bool isActive: false
|
||||
|
||||
signal clicked()
|
||||
|
||||
width: 40
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: notificationArea.containsMouse || root.isActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "notifications"
|
||||
size: Theme.iconSize - 6
|
||||
color: notificationArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
// Notification dot indicator
|
||||
Rectangle {
|
||||
width: 8
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Theme.error
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 6
|
||||
anchors.topMargin: 6
|
||||
visible: root.hasUnread
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: notificationArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
107
Modules/TopBar/SystemTrayWidget.qml
Normal file
107
Modules/TopBar/SystemTrayWidget.qml
Normal file
@@ -0,0 +1,107 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.SystemTray
|
||||
import qs.Common
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
signal menuRequested(var menu, var item, real x, real y)
|
||||
|
||||
width: Math.max(40, systemTrayRow.implicitWidth + Theme.spacingS * 2)
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
visible: systemTrayRow.children.length > 0
|
||||
|
||||
Row {
|
||||
id: systemTrayRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items
|
||||
|
||||
delegate: Rectangle {
|
||||
property var trayItem: modelData
|
||||
|
||||
width: 24
|
||||
height: 24
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: trayItemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: 18
|
||||
height: 18
|
||||
source: {
|
||||
let icon = trayItem && trayItem.icon;
|
||||
if (typeof icon === 'string' || icon instanceof String) {
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=");
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
return ""; // Return empty string if icon is not a string
|
||||
}
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: trayItemArea
|
||||
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: (mouse) => {
|
||||
if (!trayItem)
|
||||
return ;
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (!trayItem.onlyMenu)
|
||||
trayItem.activate();
|
||||
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (trayItem && trayItem.hasMenu)
|
||||
customTrayMenu.showMenu(mouse.x, mouse.y);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: customTrayMenu
|
||||
|
||||
property bool menuVisible: false
|
||||
|
||||
function showMenu(x, y) {
|
||||
root.menuRequested(customTrayMenu, trayItem, x, y);
|
||||
menuVisible = true;
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
menuVisible = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
286
Modules/TopBar/TopBar.qml
Normal file
286
Modules/TopBar/TopBar.qml
Normal file
@@ -0,0 +1,286 @@
|
||||
import "../../Common/Utilities.js" as Utils
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
// Proxy objects for external connections
|
||||
|
||||
id: root
|
||||
|
||||
property var modelData
|
||||
property string screenName: modelData.name
|
||||
// Transparency property for the top bar background
|
||||
property real backgroundTransparency: Prefs.topBarTransparency
|
||||
// Notification properties
|
||||
readonly property int notificationCount: NotificationService.notifications.length
|
||||
|
||||
screen: modelData
|
||||
implicitHeight: Theme.barHeight - 4
|
||||
color: "transparent"
|
||||
|
||||
Connections {
|
||||
function onTopBarTransparencyChanged() {
|
||||
root.backgroundTransparency = Prefs.topBarTransparency;
|
||||
}
|
||||
|
||||
target: Prefs
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: notificationHistory
|
||||
|
||||
property int count: 0
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
// Floating panel container with margins
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
anchors.topMargin: 6
|
||||
anchors.bottomMargin: 0
|
||||
anchors.leftMargin: 8
|
||||
anchors.rightMargin: 8
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadiusXLarge
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, root.backgroundTransparency)
|
||||
layer.enabled: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
radius: parent.radius
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
|
||||
radius: parent.radius
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: false
|
||||
loops: Animation.Infinite
|
||||
|
||||
NumberAnimation {
|
||||
to: 0.08
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
to: 0.02
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 4
|
||||
shadowBlur: 0.5 // radius/32, adjusted for visual match
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.15)
|
||||
shadowOpacity: 0.15
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
anchors.bottomMargin: Theme.spacingXS
|
||||
clip: true
|
||||
|
||||
Row {
|
||||
id: leftSection
|
||||
|
||||
height: parent.height
|
||||
spacing: Theme.spacingXS
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
LauncherButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
isActive: launcher.isVisible
|
||||
onClicked: {
|
||||
appLauncher.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
WorkspaceSwitcher {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
screenName: root.screenName
|
||||
}
|
||||
|
||||
FocusedAppWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: Prefs.showFocusedWindow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ClockWidget {
|
||||
id: clockWidget
|
||||
|
||||
anchors.centerIn: parent
|
||||
onClockClicked: {
|
||||
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible;
|
||||
}
|
||||
}
|
||||
|
||||
MediaWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: clockWidget.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
visible: Prefs.showMusic && MprisController.activePlayer
|
||||
onClicked: {
|
||||
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible;
|
||||
}
|
||||
}
|
||||
|
||||
WeatherWidget {
|
||||
id: weatherWidget
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: clockWidget.right
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
visible: Prefs.showWeather && WeatherService.weather.available && WeatherService.weather.temp > 0 && WeatherService.weather.tempF > 0
|
||||
onClicked: {
|
||||
centerCommandCenter.calendarVisible = !centerCommandCenter.calendarVisible;
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: rightSection
|
||||
|
||||
height: parent.height
|
||||
spacing: Theme.spacingXS
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
SystemTrayWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: Prefs.showSystemTray
|
||||
onMenuRequested: (menu, item, x, y) => {
|
||||
trayMenuPopup.currentTrayMenu = menu;
|
||||
trayMenuPopup.currentTrayItem = item;
|
||||
trayMenuPopup.trayMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL;
|
||||
trayMenuPopup.trayMenuY = Theme.barHeight - Theme.spacingXS;
|
||||
trayMenuPopup.showTrayMenu = true;
|
||||
menu.menuVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 40
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: clipboardArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: Prefs.showClipboard
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "content_paste"
|
||||
size: Theme.iconSize - 6
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clipboardArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
clipboardHistoryPopup.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// System Monitor Widgets
|
||||
CpuMonitorWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: Prefs.showSystemResources
|
||||
}
|
||||
|
||||
RamMonitorWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: Prefs.showSystemResources
|
||||
}
|
||||
|
||||
NotificationCenterButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
hasUnread: root.notificationCount > 0
|
||||
isActive: notificationCenter.notificationHistoryVisible
|
||||
onClicked: {
|
||||
notificationCenter.notificationHistoryVisible = !notificationCenter.notificationHistoryVisible;
|
||||
}
|
||||
}
|
||||
|
||||
// Battery Widget
|
||||
BatteryWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
batteryPopupVisible: batteryControlPopup.batteryPopupVisible
|
||||
onToggleBatteryPopup: {
|
||||
batteryControlPopup.batteryPopupVisible = !batteryControlPopup.batteryPopupVisible;
|
||||
}
|
||||
}
|
||||
|
||||
ControlCenterButton {
|
||||
// Bluetooth devices are automatically updated via signals
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
isActive: controlCenterPopup.controlCenterVisible
|
||||
onClicked: {
|
||||
controlCenterPopup.controlCenterVisible = !controlCenterPopup.controlCenterVisible;
|
||||
if (controlCenterPopup.controlCenterVisible) {
|
||||
if (NetworkService.wifiEnabled)
|
||||
WifiService.scanWifi();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
65
Modules/TopBar/WeatherWidget.qml
Normal file
65
Modules/TopBar/WeatherWidget.qml
Normal file
@@ -0,0 +1,65 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
signal clicked()
|
||||
|
||||
// Visibility is now controlled by TopBar.qml
|
||||
width: visible ? Math.min(100, weatherRow.implicitWidth + Theme.spacingS * 2) : 0
|
||||
height: 30
|
||||
radius: Theme.cornerRadius
|
||||
color: weatherArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
|
||||
Row {
|
||||
id: weatherRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
|
||||
size: Theme.iconSize - 4
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: (Prefs.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + (Prefs.useFahrenheit ? "F" : "C")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: weatherArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
124
Modules/TopBar/WorkspaceSwitcher.qml
Normal file
124
Modules/TopBar/WorkspaceSwitcher.qml
Normal file
@@ -0,0 +1,124 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string screenName: ""
|
||||
property int currentWorkspace: getDisplayActiveWorkspace()
|
||||
property var workspaceList: getDisplayWorkspaces()
|
||||
|
||||
function getDisplayWorkspaces() {
|
||||
if (!NiriWorkspaceService.niriAvailable || NiriWorkspaceService.allWorkspaces.length === 0)
|
||||
return [1, 2];
|
||||
|
||||
if (!root.screenName)
|
||||
return NiriWorkspaceService.getCurrentOutputWorkspaceNumbers();
|
||||
|
||||
var displayWorkspaces = [];
|
||||
for (var i = 0; i < NiriWorkspaceService.allWorkspaces.length; i++) {
|
||||
var ws = NiriWorkspaceService.allWorkspaces[i];
|
||||
if (ws.output === root.screenName)
|
||||
displayWorkspaces.push(ws.idx + 1);
|
||||
|
||||
}
|
||||
return displayWorkspaces.length > 0 ? displayWorkspaces : [1, 2];
|
||||
}
|
||||
|
||||
function getDisplayActiveWorkspace() {
|
||||
if (!NiriWorkspaceService.niriAvailable || NiriWorkspaceService.allWorkspaces.length === 0)
|
||||
return 1;
|
||||
|
||||
if (!root.screenName)
|
||||
return NiriWorkspaceService.getCurrentWorkspaceNumber();
|
||||
|
||||
for (var i = 0; i < NiriWorkspaceService.allWorkspaces.length; i++) {
|
||||
var ws = NiriWorkspaceService.allWorkspaces[i];
|
||||
if (ws.output === root.screenName && ws.is_active)
|
||||
return ws.idx + 1;
|
||||
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
width: Math.max(120, workspaceRow.implicitWidth + Theme.spacingL * 2)
|
||||
height: 30
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
visible: NiriWorkspaceService.niriAvailable
|
||||
|
||||
Connections {
|
||||
function onAllWorkspacesChanged() {
|
||||
root.workspaceList = root.getDisplayWorkspaces();
|
||||
root.currentWorkspace = root.getDisplayActiveWorkspace();
|
||||
}
|
||||
|
||||
function onFocusedWorkspaceIndexChanged() {
|
||||
root.currentWorkspace = root.getDisplayActiveWorkspace();
|
||||
}
|
||||
|
||||
function onNiriAvailableChanged() {
|
||||
if (NiriWorkspaceService.niriAvailable) {
|
||||
root.workspaceList = root.getDisplayWorkspaces();
|
||||
root.currentWorkspace = root.getDisplayActiveWorkspace();
|
||||
}
|
||||
}
|
||||
|
||||
target: NiriWorkspaceService
|
||||
}
|
||||
|
||||
Row {
|
||||
id: workspaceRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: root.workspaceList
|
||||
|
||||
Rectangle {
|
||||
property bool isActive: modelData === root.currentWorkspace
|
||||
property bool isHovered: mouseArea.containsMouse
|
||||
property int sequentialNumber: index + 1
|
||||
|
||||
width: isActive ? Theme.spacingXL + Theme.spacingM : Theme.spacingL + Theme.spacingXS
|
||||
height: Theme.spacingM
|
||||
radius: height / 2
|
||||
color: isActive ? Theme.primary : isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", modelData.toString()]);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user