mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-08 06:42:06 -04:00
merge the two workspace switcher widgets and add "Show apps" option
This commit is contained in:
@@ -1,288 +0,0 @@
|
||||
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) {
|
||||
var chunks = [];
|
||||
if (!ws)
|
||||
return chunks;
|
||||
|
||||
var wsCandidates = [ws.id, ws.idx].filter(x => typeof x !== "undefined");
|
||||
|
||||
var wins = [];
|
||||
if (CompositorService.isNiri) {
|
||||
wins = NiriService.windows || [];
|
||||
} else if (CompositorService.isHyprland) {
|
||||
wins = Hyprland.clients ? Hyprland.clients.values : [];
|
||||
}
|
||||
|
||||
var byApp = {}; // key = app_id/class, value =
|
||||
|
||||
var isActiveWs = ws.is_active;
|
||||
|
||||
for (var i = 0; i < wins.length; i++) {
|
||||
var w = wins[i];
|
||||
if (!w)
|
||||
continue;
|
||||
|
||||
var winWs = w.workspace_id || w.workspaceId || (w.workspace && w.workspace.id) || w.idx || null;
|
||||
if (winWs === null)
|
||||
continue;
|
||||
if (wsCandidates.indexOf(winWs) === -1)
|
||||
continue;
|
||||
|
||||
// --- normalize app id
|
||||
var keyBase = (w.app_id || w.appId || w.class || w.windowClass || w.exe || "unknown").toLowerCase();
|
||||
|
||||
// For active workspace every key should be unique. For inactive we just count the duplicates
|
||||
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,
|
||||
count: 1,
|
||||
windowId: w.id,
|
||||
fallbackText: w.app_id || w.class || w.title || ""
|
||||
};
|
||||
} else {
|
||||
byApp[key].count++;
|
||||
if (w.is_focused)
|
||||
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
|
||||
|
||||
Row {
|
||||
id: workspaceRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: root.workspaceList
|
||||
|
||||
Rectangle {
|
||||
id: wsBox
|
||||
property bool isActive: {
|
||||
if (CompositorService.isHyprland)
|
||||
return modelData && modelData.id === root.currentWorkspace;
|
||||
return modelData === root.currentWorkspace;
|
||||
}
|
||||
|
||||
property var wsData: {
|
||||
if (CompositorService.isHyprland)
|
||||
return modelData;
|
||||
if (CompositorService.isNiri) {
|
||||
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
|
||||
var ws = NiriService.allWorkspaces[i];
|
||||
if (ws.idx + 1 === modelData)
|
||||
return ws;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
property var icons: wsData ? root.getWorkspaceIcons(wsData) : []
|
||||
property bool isHovered: mouseArea.containsMouse
|
||||
|
||||
width: isActive ? widgetHeight * 1.2 + Theme.spacingXS + contentRow.implicitWidth : widgetHeight * 0.8 + contentRow.implicitWidth
|
||||
height: widgetHeight * 0.8
|
||||
radius: height / 2
|
||||
color: isActive ? Theme.primary : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
enabled: wsData !== null
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!wsData)
|
||||
return;
|
||||
if (CompositorService.isHyprland) {
|
||||
Hyprland.dispatch(`workspace ${wsData.id}`);
|
||||
} else if (CompositorService.isNiri) {
|
||||
NiriService.switchToWorkspace(wsData.idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
model: root.getWorkspaceIcons(wsData)
|
||||
delegate: Item {
|
||||
width: wsBox.height * 0.9
|
||||
height: wsBox.height * 0.9
|
||||
|
||||
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: wsBox.isActive
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (CompositorService.isHyprland) {
|
||||
Hyprland.dispatch(`focuswindow address:${appIcon.windowId}`);
|
||||
} else if (CompositorService.isNiri) {
|
||||
NiriService.focusWindow(appIcon.windowId);
|
||||
} else {
|
||||
console.log("ERROR: Can't focus window with ", appIcon.windowId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Counter Badge
|
||||
Rectangle {
|
||||
visible: modelData.count > 1 && !wsBox.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: 9
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback: if there're no apps - we show workspace number/name
|
||||
Rectangle {
|
||||
visible: root.getWorkspaceIcons(wsData).length === 0
|
||||
anchors.centerIn: parent
|
||||
color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : Theme.surfaceTextMedium
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: wsData ? (wsData.name || (wsData.idx ? wsData.idx : (wsData.id ? wsData.id : ""))) : ""
|
||||
font.pixelSize: 12
|
||||
color: modelData && modelData.active ? Theme.surfaceContainer : Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,8 +340,6 @@ PanelWindow {
|
||||
return true
|
||||
case "workspaceSwitcher":
|
||||
return true
|
||||
case "advancedWorkspaceSwitcher":
|
||||
return true
|
||||
case "focusedWindow":
|
||||
return true
|
||||
case "runningApps":
|
||||
@@ -397,8 +395,6 @@ PanelWindow {
|
||||
return launcherButtonComponent
|
||||
case "workspaceSwitcher":
|
||||
return workspaceSwitcherComponent
|
||||
case "advancedWorkspaceSwitcher":
|
||||
return advancedWorkspaceSwitcherComponent
|
||||
case "focusedWindow":
|
||||
return focusedWindowComponent
|
||||
case "runningApps":
|
||||
@@ -752,14 +748,6 @@ PanelWindow {
|
||||
widgetHeight: root.widgetHeight
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: advancedWorkspaceSwitcherComponent
|
||||
|
||||
AdvancedWorkspaceSwitcher {
|
||||
screenName: root.screenName
|
||||
widgetHeight: root.widgetHeight
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: focusedWindowComponent
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Hyprland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
@@ -34,6 +35,92 @@ Rectangle {
|
||||
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) {
|
||||
@@ -236,9 +323,19 @@ Rectangle {
|
||||
&& 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: isActive ? widgetHeight * 1.2 + Theme.spacingXS : widgetHeight * 0.8
|
||||
height: widgetHeight * 0.6
|
||||
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
|
||||
|
||||
@@ -262,8 +359,65 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
visible: hasIcon && iconData.type === "icon" && (!SettingsData.showWorkspaceApps || icons.length === 0)
|
||||
anchors.centerIn: parent
|
||||
name: hasIcon
|
||||
&& iconData.type === "icon" ? iconData.value : ""
|
||||
@@ -276,7 +430,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: hasIcon && iconData.type === "text"
|
||||
visible: hasIcon && iconData.type === "text" && (!SettingsData.showWorkspaceApps || icons.length === 0)
|
||||
anchors.centerIn: parent
|
||||
text: hasIcon
|
||||
&& iconData.type === "text" ? iconData.value : ""
|
||||
@@ -290,7 +444,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: SettingsData.showWorkspaceIndex && !hasIcon
|
||||
visible: (SettingsData.showWorkspaceIndex && !hasIcon && (!SettingsData.showWorkspaceApps || icons.length === 0))
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (CompositorService.isHyprland) {
|
||||
|
||||
Reference in New Issue
Block a user