mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-10 07:25:37 -05:00
487 lines
20 KiB
QML
487 lines
20 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import Quickshell
|
|
import Quickshell.Widgets
|
|
import Quickshell.Hyprland
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
Rectangle {
|
|
id: root
|
|
|
|
property string screenName: ""
|
|
property real widgetHeight: 30
|
|
property int currentWorkspace: {
|
|
if (CompositorService.isNiri) {
|
|
return getNiriActiveWorkspace()
|
|
} else if (CompositorService.isHyprland) {
|
|
return Hyprland.focusedWorkspace ? Hyprland.focusedWorkspace.id : 1
|
|
}
|
|
return 1
|
|
}
|
|
property var workspaceList: {
|
|
if (CompositorService.isNiri) {
|
|
var baseList = getNiriWorkspaces()
|
|
return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList
|
|
} else if (CompositorService.isHyprland) {
|
|
var workspaces = Hyprland.workspaces ? Hyprland.workspaces.values : []
|
|
if (workspaces.length === 0) {
|
|
return [{id: 1, name: "1"}]
|
|
}
|
|
var sorted = workspaces.slice().sort((a, b) => a.id - b.id)
|
|
return SettingsData.showWorkspacePadding ? padWorkspaces(sorted) : sorted
|
|
}
|
|
return [1]
|
|
}
|
|
|
|
function getWorkspaceIcons(ws) {
|
|
if (!SettingsData.showWorkspaceApps) return []
|
|
|
|
var chunks = []
|
|
if (!ws) return chunks
|
|
|
|
var targetWorkspaceId
|
|
if (CompositorService.isNiri) {
|
|
// For Niri, we need to find the workspace ID from allWorkspaces
|
|
var wsNumber = typeof ws === "number" ? ws : -1
|
|
if (wsNumber > 0) {
|
|
for (var j = 0; j < NiriService.allWorkspaces.length; j++) {
|
|
var workspace = NiriService.allWorkspaces[j]
|
|
if (workspace.idx + 1 === wsNumber && workspace.output === root.screenName) {
|
|
targetWorkspaceId = workspace.id
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if (targetWorkspaceId === undefined) return chunks
|
|
} else if (CompositorService.isHyprland) {
|
|
targetWorkspaceId = ws.id !== undefined ? ws.id : ws
|
|
} else {
|
|
return chunks
|
|
}
|
|
|
|
var wins = []
|
|
if (CompositorService.isNiri) {
|
|
wins = NiriService.windows || []
|
|
} else if (CompositorService.isHyprland) {
|
|
wins = Hyprland.clients ? Hyprland.clients.values : []
|
|
}
|
|
|
|
var byApp = {}
|
|
var isActiveWs = false
|
|
if (CompositorService.isNiri) {
|
|
for (var j = 0; j < NiriService.allWorkspaces.length; j++) {
|
|
var ws2 = NiriService.allWorkspaces[j]
|
|
if (ws2.id === targetWorkspaceId && ws2.is_active) {
|
|
isActiveWs = true
|
|
break
|
|
}
|
|
}
|
|
} else if (CompositorService.isHyprland) {
|
|
isActiveWs = targetWorkspaceId === root.currentWorkspace
|
|
}
|
|
|
|
for (var i = 0; i < wins.length; i++) {
|
|
var w = wins[i]
|
|
if (!w) continue
|
|
|
|
var winWs
|
|
if (CompositorService.isNiri) {
|
|
winWs = w.workspace_id
|
|
} else if (CompositorService.isHyprland) {
|
|
winWs = w.workspace && w.workspace.id !== undefined ? w.workspace.id : w.workspaceId
|
|
}
|
|
|
|
if (winWs === undefined || winWs === null) continue
|
|
if (winWs !== targetWorkspaceId) continue
|
|
|
|
var keyBase = (w.app_id || w.appId || w.class || w.windowClass || w.exe || "unknown").toLowerCase()
|
|
var key = isActiveWs ? keyBase + "_" + i : keyBase
|
|
|
|
if (!byApp[key]) {
|
|
var icon = Quickshell.iconPath(DesktopEntries.heuristicLookup(Paths.moddedAppId(keyBase))?.icon, true)
|
|
byApp[key] = {
|
|
type: "icon",
|
|
icon: icon,
|
|
active: !!w.is_focused || !!w.activated,
|
|
count: 1,
|
|
windowId: w.id || w.address,
|
|
fallbackText: w.app_id || w.appId || w.class || w.title || ""
|
|
}
|
|
} else {
|
|
byApp[key].count++
|
|
if (w.is_focused || w.activated) byApp[key].active = true
|
|
}
|
|
}
|
|
|
|
for (var k in byApp)
|
|
chunks.push(byApp[k])
|
|
|
|
return chunks
|
|
}
|
|
|
|
function padWorkspaces(list) {
|
|
var padded = list.slice()
|
|
while (padded.length < 3) {
|
|
if (CompositorService.isHyprland) {
|
|
padded.push({id: -1, name: ""})
|
|
} else {
|
|
padded.push(-1)
|
|
}
|
|
}
|
|
return padded
|
|
}
|
|
|
|
function getNiriWorkspaces() {
|
|
if (NiriService.allWorkspaces.length === 0)
|
|
return [1, 2]
|
|
|
|
if (!root.screenName)
|
|
return NiriService.getCurrentOutputWorkspaceNumbers()
|
|
|
|
var displayWorkspaces = []
|
|
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
|
var ws = NiriService.allWorkspaces[i]
|
|
if (ws.output === root.screenName)
|
|
displayWorkspaces.push(ws.idx + 1)
|
|
}
|
|
return displayWorkspaces.length > 0 ? displayWorkspaces : [1, 2]
|
|
}
|
|
|
|
function getNiriActiveWorkspace() {
|
|
if (NiriService.allWorkspaces.length === 0)
|
|
return 1
|
|
|
|
if (!root.screenName)
|
|
return NiriService.getCurrentWorkspaceNumber()
|
|
|
|
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
|
var ws = NiriService.allWorkspaces[i]
|
|
if (ws.output === root.screenName && ws.is_active)
|
|
return ws.idx + 1
|
|
}
|
|
return 1
|
|
}
|
|
|
|
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 2 : Math.max(Theme.spacingS, SettingsData.topBarInnerPadding)
|
|
|
|
width: SettingsData.showWorkspacePadding ? Math.max(
|
|
120,
|
|
workspaceRow.implicitWidth + horizontalPadding * 2) : workspaceRow.implicitWidth
|
|
+ horizontalPadding * 2
|
|
height: widgetHeight
|
|
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
|
|
color: {
|
|
if (SettingsData.topBarNoBackground) return "transparent"
|
|
const baseColor = Theme.surfaceTextHover
|
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
|
|
baseColor.a * Theme.widgetTransparency)
|
|
}
|
|
visible: CompositorService.isNiri || CompositorService.isHyprland
|
|
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
acceptedButtons: Qt.NoButton
|
|
|
|
property real scrollAccumulator: 0
|
|
property real touchpadThreshold: 500
|
|
|
|
onWheel: (wheel) => {
|
|
const deltaY = wheel.angleDelta.y
|
|
const isMouseWheel = Math.abs(deltaY) >= 120
|
|
&& (Math.abs(deltaY) % 120) === 0
|
|
|
|
if (isMouseWheel) {
|
|
// Direct mouse wheel action
|
|
if (CompositorService.isNiri) {
|
|
var realWorkspaces = [];
|
|
for (var i = 0; i < root.workspaceList.length; i++) {
|
|
if (root.workspaceList[i] !== -1) {
|
|
realWorkspaces.push(root.workspaceList[i]);
|
|
}
|
|
}
|
|
|
|
if (realWorkspaces.length < 2) return;
|
|
|
|
var currentIndex = -1;
|
|
for (var i = 0; i < realWorkspaces.length; i++) {
|
|
if (realWorkspaces[i] === root.currentWorkspace) {
|
|
currentIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
if (currentIndex === -1) currentIndex = 0;
|
|
|
|
var nextIndex;
|
|
if (deltaY < 0) {
|
|
nextIndex = (currentIndex + 1) % realWorkspaces.length;
|
|
} else {
|
|
nextIndex = (currentIndex - 1 + realWorkspaces.length) % realWorkspaces.length;
|
|
}
|
|
NiriService.switchToWorkspace(realWorkspaces[nextIndex] - 1);
|
|
|
|
} else if (CompositorService.isHyprland) {
|
|
if (deltaY < 0) {
|
|
Hyprland.dispatch("workspace r+1");
|
|
} else {
|
|
Hyprland.dispatch("workspace r-1");
|
|
}
|
|
}
|
|
} else {
|
|
// Touchpad - accumulate small deltas
|
|
scrollAccumulator += deltaY
|
|
|
|
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
|
|
if (CompositorService.isNiri) {
|
|
var realWorkspaces = [];
|
|
for (var i = 0; i < root.workspaceList.length; i++) {
|
|
if (root.workspaceList[i] !== -1) {
|
|
realWorkspaces.push(root.workspaceList[i]);
|
|
}
|
|
}
|
|
|
|
if (realWorkspaces.length < 2) {
|
|
scrollAccumulator = 0;
|
|
return;
|
|
}
|
|
|
|
var currentIndex = -1;
|
|
for (var i = 0; i < realWorkspaces.length; i++) {
|
|
if (realWorkspaces[i] === root.currentWorkspace) {
|
|
currentIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
if (currentIndex === -1) currentIndex = 0;
|
|
|
|
var nextIndex;
|
|
if (scrollAccumulator < 0) {
|
|
nextIndex = (currentIndex + 1) % realWorkspaces.length;
|
|
} else {
|
|
nextIndex = (currentIndex - 1 + realWorkspaces.length) % realWorkspaces.length;
|
|
}
|
|
NiriService.switchToWorkspace(realWorkspaces[nextIndex] - 1);
|
|
|
|
} else if (CompositorService.isHyprland) {
|
|
if (scrollAccumulator < 0) {
|
|
Hyprland.dispatch("workspace r+1");
|
|
} else {
|
|
Hyprland.dispatch("workspace r-1");
|
|
}
|
|
}
|
|
|
|
scrollAccumulator = 0
|
|
}
|
|
}
|
|
|
|
wheel.accepted = true
|
|
}
|
|
}
|
|
|
|
Row {
|
|
id: workspaceRow
|
|
|
|
anchors.centerIn: parent
|
|
spacing: Theme.spacingS
|
|
|
|
Repeater {
|
|
model: root.workspaceList
|
|
|
|
Rectangle {
|
|
property bool isActive: {
|
|
if (CompositorService.isHyprland) {
|
|
return modelData && modelData.id === root.currentWorkspace
|
|
}
|
|
return modelData === root.currentWorkspace
|
|
}
|
|
property bool isPlaceholder: {
|
|
if (CompositorService.isHyprland) {
|
|
return modelData && modelData.id === -1
|
|
}
|
|
return modelData === -1
|
|
}
|
|
property bool isHovered: mouseArea.containsMouse
|
|
property var workspaceData: {
|
|
if (isPlaceholder)
|
|
return null
|
|
|
|
if (CompositorService.isNiri) {
|
|
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
|
var ws = NiriService.allWorkspaces[i]
|
|
if (ws.idx + 1 === modelData && ws.output === root.screenName)
|
|
return ws
|
|
}
|
|
} else if (CompositorService.isHyprland) {
|
|
return modelData
|
|
}
|
|
return null
|
|
}
|
|
property var iconData: workspaceData
|
|
&& workspaceData.name ? SettingsData.getWorkspaceNameIcon(
|
|
workspaceData.name) : null
|
|
property bool hasIcon: iconData !== null
|
|
property var icons: SettingsData.showWorkspaceApps ? root.getWorkspaceIcons(CompositorService.isHyprland ? modelData : (modelData === -1 ? null : modelData)) : []
|
|
|
|
width: {
|
|
if (SettingsData.showWorkspaceApps) {
|
|
if (icons.length > 0) {
|
|
return isActive ? widgetHeight * 1.0 + Theme.spacingXS + contentRow.implicitWidth : widgetHeight * 0.8 + contentRow.implicitWidth
|
|
} else {
|
|
return isActive ? widgetHeight * 1.0 + Theme.spacingXS : widgetHeight * 0.8
|
|
}
|
|
}
|
|
return isActive ? widgetHeight * 1.2 + Theme.spacingXS : widgetHeight * 0.8
|
|
}
|
|
height: SettingsData.showWorkspaceApps ? widgetHeight * 0.8 : widgetHeight * 0.6
|
|
radius: height / 2
|
|
color: isActive ? Theme.primary : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
|
|
|
|
MouseArea {
|
|
id: mouseArea
|
|
|
|
anchors.fill: parent
|
|
hoverEnabled: !isPlaceholder
|
|
cursorShape: isPlaceholder ? Qt.ArrowCursor : Qt.PointingHandCursor
|
|
enabled: !isPlaceholder
|
|
onClicked: {
|
|
if (!isPlaceholder) {
|
|
if (CompositorService.isNiri) {
|
|
NiriService.switchToWorkspace(modelData - 1)
|
|
} else if (CompositorService.isHyprland) {
|
|
if (modelData && modelData.id) {
|
|
Hyprland.dispatch(`workspace ${modelData.id}`)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
id: contentRow
|
|
anchors.centerIn: parent
|
|
spacing: 4
|
|
visible: SettingsData.showWorkspaceApps && icons.length > 0
|
|
|
|
Repeater {
|
|
model: icons.slice(0, 3)
|
|
delegate: Item {
|
|
width: 18
|
|
height: 18
|
|
|
|
IconImage {
|
|
id: appIcon
|
|
property var windowId: modelData.windowId
|
|
anchors.fill: parent
|
|
source: modelData.icon
|
|
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
|
|
MouseArea {
|
|
id: appMouseArea
|
|
hoverEnabled: true
|
|
anchors.fill: parent
|
|
enabled: isActive
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
if (CompositorService.isHyprland) {
|
|
Hyprland.dispatch(`focuswindow address:${appIcon.windowId}`)
|
|
} else if (CompositorService.isNiri) {
|
|
NiriService.focusWindow(appIcon.windowId)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
visible: modelData.count > 1 && !isActive
|
|
width: 12
|
|
height: 12
|
|
radius: 6
|
|
color: "black"
|
|
border.color: "white"
|
|
border.width: 1
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
z: 2
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: modelData.count
|
|
font.pixelSize: 8
|
|
color: "white"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DankIcon {
|
|
visible: hasIcon && iconData.type === "icon" && (!SettingsData.showWorkspaceApps || icons.length === 0)
|
|
anchors.centerIn: parent
|
|
name: hasIcon
|
|
&& iconData.type === "icon" ? iconData.value : ""
|
|
size: Theme.fontSizeSmall
|
|
color: isActive ? Qt.rgba(Theme.surfaceContainer.r,
|
|
Theme.surfaceContainer.g,
|
|
Theme.surfaceContainer.b,
|
|
0.95) : Theme.surfaceTextMedium
|
|
weight: isActive && !isPlaceholder ? 500 : 400
|
|
}
|
|
|
|
StyledText {
|
|
visible: hasIcon && iconData.type === "text" && (!SettingsData.showWorkspaceApps || icons.length === 0)
|
|
anchors.centerIn: parent
|
|
text: hasIcon
|
|
&& iconData.type === "text" ? iconData.value : ""
|
|
color: isActive ? Qt.rgba(Theme.surfaceContainer.r,
|
|
Theme.surfaceContainer.g,
|
|
Theme.surfaceContainer.b,
|
|
0.95) : Theme.surfaceTextMedium
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: isActive
|
|
&& !isPlaceholder ? Font.DemiBold : Font.Normal
|
|
}
|
|
|
|
StyledText {
|
|
visible: (SettingsData.showWorkspaceIndex && !hasIcon && (!SettingsData.showWorkspaceApps || icons.length === 0))
|
|
anchors.centerIn: parent
|
|
text: {
|
|
if (CompositorService.isHyprland) {
|
|
if (modelData && modelData.id === -1) {
|
|
return index + 1
|
|
}
|
|
return modelData && modelData.id ? modelData.id : ""
|
|
}
|
|
if (modelData === -1) {
|
|
return index + 1
|
|
}
|
|
return modelData - 1
|
|
}
|
|
color: isActive ? Qt.rgba(
|
|
Theme.surfaceContainer.r,
|
|
Theme.surfaceContainer.g,
|
|
Theme.surfaceContainer.b,
|
|
0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: isActive
|
|
&& !isPlaceholder ? Font.DemiBold : Font.Normal
|
|
}
|
|
|
|
Behavior on width {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Behavior on color {
|
|
ColorAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |