1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

merge the two workspace switcher widgets and add "Show apps" option

This commit is contained in:
bbedward
2025-09-02 18:55:36 -04:00
parent 213543acb3
commit cfbeb6062b
7 changed files with 179 additions and 1289 deletions

View File

@@ -46,6 +46,7 @@ Singleton {
property bool controlCenterShowAudioIcon: true
property bool showWorkspaceIndex: false
property bool showWorkspacePadding: false
property bool showWorkspaceApps: false
property var workspaceNameIcons: ({})
property bool clockCompactMode: false
property bool focusedWindowCompactMode: false
@@ -232,6 +233,8 @@ Singleton {
!== undefined ? settings.showWorkspaceIndex : false
showWorkspacePadding = settings.showWorkspacePadding
!== undefined ? settings.showWorkspacePadding : false
showWorkspaceApps = settings.showWorkspaceApps
!== undefined ? settings.showWorkspaceApps : false
workspaceNameIcons = settings.workspaceNameIcons
!== undefined ? settings.workspaceNameIcons : ({})
clockCompactMode = settings.clockCompactMode
@@ -373,6 +376,7 @@ Singleton {
"controlCenterShowAudioIcon": controlCenterShowAudioIcon,
"showWorkspaceIndex": showWorkspaceIndex,
"showWorkspacePadding": showWorkspacePadding,
"showWorkspaceApps": showWorkspaceApps,
"workspaceNameIcons": workspaceNameIcons,
"clockCompactMode": clockCompactMode,
"focusedWindowCompactMode": focusedWindowCompactMode,
@@ -426,6 +430,11 @@ Singleton {
saveSettings()
}
function setShowWorkspaceApps(enabled) {
showWorkspaceApps = enabled
saveSettings()
}
function setWorkspaceNameIcon(workspaceName, iconData) {
var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons))
iconMap[workspaceName] = iconData

View File

@@ -20,12 +20,6 @@ Item {
"description": "Shows current workspace and allows switching",
"icon": "view_module",
"enabled": true
}, {
"id": "advancedWorkspaceSwitcher",
"text": "Advanced Workspace Switcher",
"description": "Shows workspaced with opened apps and allows switching",
"icon": "view_module",
"enabled": true
}, {
"id": "focusedWindow",
"text": "Focused Window",

View File

@@ -231,6 +231,17 @@ Item {
checked)
}
}
DankToggle {
width: parent.width
text: "Show Workspace Apps"
description: "Display application icons in workspace indicators"
checked: SettingsData.showWorkspaceApps
onToggled: checked => {
return SettingsData.setShowWorkspaceApps(
checked)
}
}
}
}

View File

@@ -1,978 +0,0 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: widgetsTab
property var baseWidgetDefinitions: [{
"id": "launcherButton",
"text": "App Launcher",
"description": "Quick access to application launcher",
"icon": "apps",
"enabled": true
}, {
"id": "workspaceSwitcher",
"text": "Workspace Switcher",
"description": "Shows current workspace and allows switching",
"icon": "view_module",
"enabled": true
}, {
"id": "advancedWorkspaceSwitcher",
"text": "Advanced Workspace Switcher",
"description": "Shows workspaced with opened apps and allows switching",
"icon": "view_module",
"enabled": true
}, {
"id": "focusedWindow",
"text": "Focused Window",
"description": "Display currently focused application title",
"icon": "window",
"enabled": true
}, {
"id": "runningApps",
"text": "Running Apps",
"description": "Shows all running applications with focus indication",
"icon": "apps",
"enabled": true
}, {
"id": "clock",
"text": "Clock",
"description": "Current time and date display",
"icon": "schedule",
"enabled": true
}, {
"id": "weather",
"text": "Weather Widget",
"description": "Current weather conditions and temperature",
"icon": "wb_sunny",
"enabled": true
}, {
"id": "music",
"text": "Media Controls",
"description": "Control currently playing media",
"icon": "music_note",
"enabled": true
}, {
"id": "clipboard",
"text": "Clipboard Manager",
"description": "Access clipboard history",
"icon": "content_paste",
"enabled": true
}, {
"id": "cpuUsage",
"text": "CPU Usage",
"description": "CPU usage indicator",
"icon": "memory",
"enabled": DgopService.dgopAvailable,
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined
}, {
"id": "memUsage",
"text": "Memory Usage",
"description": "Memory usage indicator",
"icon": "storage",
"enabled": DgopService.dgopAvailable,
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined
}, {
"id": "cpuTemp",
"text": "CPU Temperature",
"description": "CPU temperature display",
"icon": "device_thermostat",
"enabled": DgopService.dgopAvailable,
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined
}, {
"id": "gpuTemp",
"text": "GPU Temperature",
"description": "GPU temperature display",
"icon": "auto_awesome_mosaic",
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : "This widget prevents GPU power off states, which can significantly impact battery life on laptops. It is not recommended to use this on laptops with hybrid graphics.",
"enabled": DgopService.dgopAvailable
}, {
"id": "systemTray",
"text": "System Tray",
"description": "System notification area icons",
"icon": "notifications",
"enabled": true
}, {
"id": "privacyIndicator",
"text": "Privacy Indicator",
"description": "Shows when microphone, camera, or screen sharing is active",
"icon": "privacy_tip",
"enabled": true
}, {
"id": "controlCenterButton",
"text": "Control Center",
"description": "Access to system controls and settings",
"icon": "settings",
"enabled": true
}, {
"id": "notificationButton",
"text": "Notification Center",
"description": "Access to notifications and do not disturb",
"icon": "notifications",
"enabled": true
}, {
"id": "notepadButton",
"text": "Notepad",
"description": "Quick access to notepad",
"icon": "assignment",
"enabled": true
}, {
"id": "battery",
"text": "Battery",
"description": "Battery level and power management",
"icon": "battery_std",
"enabled": true
}, {
"id": "idleInhibitor",
"text": "Idle Inhibitor",
"description": "Prevent screen timeout",
"icon": "motion_sensor_active",
"enabled": true
}, {
"id": "spacer",
"text": "Spacer",
"description": "Customizable empty space",
"icon": "more_horiz",
"enabled": true
}, {
"id": "separator",
"text": "Separator",
"description": "Visual divider between widgets",
"icon": "remove",
"enabled": true
}, {
"id": "network_speed_monitor",
"text": "Network Speed Monitor",
"description": "Network download and upload speed display",
"icon": "network_check",
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
"enabled": DgopService.dgopAvailable
}, {
"id": "keyboard_layout_name",
"text": "Keyboard Layout Name",
"description": "Displays the active keyboard layout and allows switching",
"icon": "keyboard",
}]
property var defaultLeftWidgets: [{
"id": "launcherButton",
"enabled": true
}, {
"id": "workspaceSwitcher",
"enabled": true
}, {
"id": "focusedWindow",
"enabled": true
}]
property var defaultCenterWidgets: [{
"id": "music",
"enabled": true
}, {
"id": "clock",
"enabled": true
}, {
"id": "weather",
"enabled": true
}]
property var defaultRightWidgets: [{
"id": "privacyIndicator",
"enabled": true
}, {
"id": "systemTray",
"enabled": true
}, {
"id": "clipboard",
"enabled": true
}, {
"id": "notificationButton",
"enabled": true
}, {
"id": "battery",
"enabled": true
}, {
"id": "controlCenterButton",
"enabled": true
}]
function addWidgetToSection(widgetId, targetSection) {
var widgetObj = {
"id": widgetId,
"enabled": true
}
if (widgetId === "spacer")
widgetObj.size = 20
if (widgetId === "gpuTemp") {
widgetObj.selectedGpuIndex = 0
widgetObj.pciId = ""
}
var widgets = []
if (targetSection === "left") {
widgets = SettingsData.topBarLeftWidgets.slice()
widgets.push(widgetObj)
SettingsData.setTopBarLeftWidgets(widgets)
} else if (targetSection === "center") {
widgets = SettingsData.topBarCenterWidgets.slice()
widgets.push(widgetObj)
SettingsData.setTopBarCenterWidgets(widgets)
} else if (targetSection === "right") {
widgets = SettingsData.topBarRightWidgets.slice()
widgets.push(widgetObj)
SettingsData.setTopBarRightWidgets(widgets)
}
}
function removeWidgetFromSection(sectionId, widgetIndex) {
var widgets = []
if (sectionId === "left") {
widgets = SettingsData.topBarLeftWidgets.slice()
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
widgets.splice(widgetIndex, 1)
}
SettingsData.setTopBarLeftWidgets(widgets)
} else if (sectionId === "center") {
widgets = SettingsData.topBarCenterWidgets.slice()
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
widgets.splice(widgetIndex, 1)
}
SettingsData.setTopBarCenterWidgets(widgets)
} else if (sectionId === "right") {
widgets = SettingsData.topBarRightWidgets.slice()
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
widgets.splice(widgetIndex, 1)
}
SettingsData.setTopBarRightWidgets(widgets)
}
}
function handleItemEnabledChanged(sectionId, itemId, enabled) {
var widgets = []
if (sectionId === "left")
widgets = SettingsData.topBarLeftWidgets.slice()
else if (sectionId === "center")
widgets = SettingsData.topBarCenterWidgets.slice()
else if (sectionId === "right")
widgets = SettingsData.topBarRightWidgets.slice()
for (var i = 0; i < widgets.length; i++) {
var widget = widgets[i]
var widgetId = typeof widget === "string" ? widget : widget.id
if (widgetId === itemId) {
if (typeof widget === "string") {
widgets[i] = {
"id": widget,
"enabled": enabled
}
} else {
var newWidget = {
"id": widget.id,
"enabled": enabled
}
if (widget.size !== undefined)
newWidget.size = widget.size
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex
else if (widget.id === "gpuTemp")
newWidget.selectedGpuIndex = 0
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId
else if (widget.id === "gpuTemp")
newWidget.pciId = ""
widgets[i] = newWidget
}
break
}
}
if (sectionId === "left")
SettingsData.setTopBarLeftWidgets(widgets)
else if (sectionId === "center")
SettingsData.setTopBarCenterWidgets(widgets)
else if (sectionId === "right")
SettingsData.setTopBarRightWidgets(widgets)
}
function handleItemOrderChanged(sectionId, newOrder) {
if (sectionId === "left")
SettingsData.setTopBarLeftWidgets(newOrder)
else if (sectionId === "center")
SettingsData.setTopBarCenterWidgets(newOrder)
else if (sectionId === "right")
SettingsData.setTopBarRightWidgets(newOrder)
}
function handleSpacerSizeChanged(sectionId, itemId, newSize) {
var widgets = []
if (sectionId === "left")
widgets = SettingsData.topBarLeftWidgets.slice()
else if (sectionId === "center")
widgets = SettingsData.topBarCenterWidgets.slice()
else if (sectionId === "right")
widgets = SettingsData.topBarRightWidgets.slice()
for (var i = 0; i < widgets.length; i++) {
var widget = widgets[i]
var widgetId = typeof widget === "string" ? widget : widget.id
if (widgetId === itemId && widgetId === "spacer") {
if (typeof widget === "string") {
widgets[i] = {
"id": widget,
"enabled": true,
"size": newSize
}
} else {
var newWidget = {
"id": widget.id,
"enabled": widget.enabled,
"size": newSize
}
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId
widgets[i] = newWidget
}
break
}
}
if (sectionId === "left")
SettingsData.setTopBarLeftWidgets(widgets)
else if (sectionId === "center")
SettingsData.setTopBarCenterWidgets(widgets)
else if (sectionId === "right")
SettingsData.setTopBarRightWidgets(widgets)
}
function handleGpuSelectionChanged(sectionId, widgetIndex, selectedGpuIndex) {
var widgets = []
if (sectionId === "left")
widgets = SettingsData.topBarLeftWidgets.slice()
else if (sectionId === "center")
widgets = SettingsData.topBarCenterWidgets.slice()
else if (sectionId === "right")
widgets = SettingsData.topBarRightWidgets.slice()
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
var widget = widgets[widgetIndex]
if (typeof widget === "string") {
widgets[widgetIndex] = {
"id": widget,
"enabled": true,
"selectedGpuIndex": selectedGpuIndex,
"pciId": DgopService.availableGpus
&& DgopService.availableGpus.length
> selectedGpuIndex ? DgopService.availableGpus[selectedGpuIndex].pciId : ""
}
} else {
var newWidget = {
"id": widget.id,
"enabled": widget.enabled,
"selectedGpuIndex": selectedGpuIndex,
"pciId": DgopService.availableGpus
&& DgopService.availableGpus.length
> selectedGpuIndex ? DgopService.availableGpus[selectedGpuIndex].pciId : ""
}
if (widget.size !== undefined)
newWidget.size = widget.size
widgets[widgetIndex] = newWidget
}
}
if (sectionId === "left")
SettingsData.setTopBarLeftWidgets(widgets)
else if (sectionId === "center")
SettingsData.setTopBarCenterWidgets(widgets)
else if (sectionId === "right")
SettingsData.setTopBarRightWidgets(widgets)
}
function getItemsForSection(sectionId) {
var widgets = []
var widgetData = []
if (sectionId === "left")
widgetData = SettingsData.topBarLeftWidgets || []
else if (sectionId === "center")
widgetData = SettingsData.topBarCenterWidgets || []
else if (sectionId === "right")
widgetData = SettingsData.topBarRightWidgets || []
widgetData.forEach(widget => {
var widgetId = typeof widget === "string" ? widget : widget.id
var widgetEnabled = typeof widget
=== "string" ? true : widget.enabled
var widgetSize = typeof widget === "string" ? undefined : widget.size
var widgetSelectedGpuIndex = typeof widget
=== "string" ? undefined : widget.selectedGpuIndex
var widgetPciId = typeof widget
=== "string" ? undefined : widget.pciId
var widgetDef = baseWidgetDefinitions.find(w => {
return w.id === widgetId
})
if (widgetDef) {
var item = Object.assign({}, widgetDef)
item.enabled = widgetEnabled
if (widgetSize !== undefined)
item.size = widgetSize
if (widgetSelectedGpuIndex !== undefined)
item.selectedGpuIndex = widgetSelectedGpuIndex
if (widgetPciId !== undefined)
item.pciId = widgetPciId
widgets.push(item)
}
})
return widgets
}
Component.onCompleted: {
// Only set defaults if widgets have never been configured (null/undefined, not empty array)
if (!SettingsData.topBarLeftWidgets)
SettingsData.setTopBarLeftWidgets(defaultLeftWidgets)
if (!SettingsData.topBarCenterWidgets)
SettingsData.setTopBarCenterWidgets(defaultCenterWidgets)
if (!SettingsData.topBarRightWidgets)
SettingsData.setTopBarRightWidgets(defaultRightWidgets)
["left", "center", "right"].forEach(sectionId => {
var widgets = []
if (sectionId === "left")
widgets = SettingsData.topBarLeftWidgets.slice()
else if (sectionId === "center")
widgets = SettingsData.topBarCenterWidgets.slice()
else if (sectionId === "right")
widgets = SettingsData.topBarRightWidgets.slice()
var updated = false
for (var i = 0; i < widgets.length; i++) {
var widget = widgets[i]
if (typeof widget === "object"
&& widget.id === "spacer" && !widget.size) {
widgets[i] = Object.assign({}, widget, {
"size": 20
})
updated = true
}
}
if (updated) {
if (sectionId === "left")
SettingsData.setTopBarLeftWidgets(widgets)
else if (sectionId === "center")
SettingsData.setTopBarCenterWidgets(widgets)
else if (sectionId === "right")
SettingsData.setTopBarRightWidgets(widgets)
}
})
}
DankFlickable {
anchors.fill: parent
anchors.topMargin: Theme.spacingL
clip: true
contentHeight: mainColumn.height
contentWidth: width
Column {
id: mainColumn
width: parent.width
spacing: Theme.spacingXL
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "widgets"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Top Bar Widget Management"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - 400
height: 1
}
Rectangle {
width: 80
height: 28
radius: Theme.cornerRadius
color: resetArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariant
anchors.verticalCenter: parent.verticalCenter
border.width: 1
border.color: resetArea.containsMouse ? Theme.outline : Qt.rgba(
Theme.outline.r,
Theme.outline.g,
Theme.outline.b,
0.5)
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "refresh"
size: 14
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Reset"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: resetArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
SettingsData.setTopBarLeftWidgets(
defaultLeftWidgets)
SettingsData.setTopBarCenterWidgets(
defaultCenterWidgets)
SettingsData.setTopBarRightWidgets(
defaultRightWidgets)
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on border.color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
Rectangle {
width: parent.width
height: messageText.contentHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
border.width: 1
StyledText {
id: messageText
anchors.centerIn: parent
text: "Drag widgets to reorder within sections. Use the eye icon to hide/show widgets (maintains spacing), or X to remove them completely."
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
width: parent.width - Theme.spacingM * 2
wrapMode: Text.WordWrap
}
}
Column {
width: parent.width
spacing: Theme.spacingL
WidgetsTabSection {
width: parent.width
title: "Left Section"
titleIcon: "format_align_left"
sectionId: "left"
allWidgets: widgetsTab.baseWidgetDefinitions
items: widgetsTab.getItemsForSection("left")
onItemEnabledChanged: (sectionId, itemId, enabled) => {
widgetsTab.handleItemEnabledChanged(
sectionId, itemId, enabled)
}
onItemOrderChanged: newOrder => {
widgetsTab.handleItemOrderChanged(
"left", newOrder)
}
onAddWidget: sectionId => {
widgetSelectionPopup.allWidgets
= widgetsTab.baseWidgetDefinitions
widgetSelectionPopup.targetSection = sectionId
widgetSelectionPopup.safeOpen()
}
onRemoveWidget: (sectionId, widgetIndex) => {
widgetsTab.removeWidgetFromSection(
sectionId, widgetIndex)
}
onSpacerSizeChanged: (sectionId, itemId, newSize) => {
widgetsTab.handleSpacerSizeChanged(
sectionId, itemId, newSize)
}
onCompactModeChanged: (widgetId, value) => {
if (widgetId === "clock") {
SettingsData.setClockCompactMode(
value)
} else if (widgetId === "music") {
SettingsData.setMediaSize(
value)
} else if (widgetId === "focusedWindow") {
SettingsData.setFocusedWindowCompactMode(
value)
} else if (widgetId === "runningApps") {
SettingsData.setRunningAppsCompactMode(
value)
}
}
onGpuSelectionChanged: (sectionId, widgetIndex, selectedIndex) => {
widgetsTab.handleGpuSelectionChanged(
sectionId, widgetIndex,
selectedIndex)
}
}
WidgetsTabSection {
width: parent.width
title: "Center Section"
titleIcon: "format_align_center"
sectionId: "center"
allWidgets: widgetsTab.baseWidgetDefinitions
items: widgetsTab.getItemsForSection("center")
onItemEnabledChanged: (sectionId, itemId, enabled) => {
widgetsTab.handleItemEnabledChanged(
sectionId, itemId, enabled)
}
onItemOrderChanged: newOrder => {
widgetsTab.handleItemOrderChanged(
"center", newOrder)
}
onAddWidget: sectionId => {
widgetSelectionPopup.allWidgets
= widgetsTab.baseWidgetDefinitions
widgetSelectionPopup.targetSection = sectionId
widgetSelectionPopup.safeOpen()
}
onRemoveWidget: (sectionId, widgetIndex) => {
widgetsTab.removeWidgetFromSection(
sectionId, widgetIndex)
}
onSpacerSizeChanged: (sectionId, itemId, newSize) => {
widgetsTab.handleSpacerSizeChanged(
sectionId, itemId, newSize)
}
onCompactModeChanged: (widgetId, value) => {
if (widgetId === "clock") {
SettingsData.setClockCompactMode(
value)
} else if (widgetId === "music") {
SettingsData.setMediaSize(
value)
} else if (widgetId === "focusedWindow") {
SettingsData.setFocusedWindowCompactMode(
value)
} else if (widgetId === "runningApps") {
SettingsData.setRunningAppsCompactMode(
value)
}
}
onGpuSelectionChanged: (sectionId, widgetIndex, selectedIndex) => {
widgetsTab.handleGpuSelectionChanged(
sectionId, widgetIndex,
selectedIndex)
}
}
WidgetsTabSection {
width: parent.width
title: "Right Section"
titleIcon: "format_align_right"
sectionId: "right"
allWidgets: widgetsTab.baseWidgetDefinitions
items: widgetsTab.getItemsForSection("right")
onItemEnabledChanged: (sectionId, itemId, enabled) => {
widgetsTab.handleItemEnabledChanged(
sectionId, itemId, enabled)
}
onItemOrderChanged: newOrder => {
widgetsTab.handleItemOrderChanged(
"right", newOrder)
}
onAddWidget: sectionId => {
widgetSelectionPopup.allWidgets
= widgetsTab.baseWidgetDefinitions
widgetSelectionPopup.targetSection = sectionId
widgetSelectionPopup.safeOpen()
}
onRemoveWidget: (sectionId, widgetIndex) => {
widgetsTab.removeWidgetFromSection(
sectionId, widgetIndex)
}
onSpacerSizeChanged: (sectionId, itemId, newSize) => {
widgetsTab.handleSpacerSizeChanged(
sectionId, itemId, newSize)
}
onCompactModeChanged: (widgetId, value) => {
if (widgetId === "clock") {
SettingsData.setClockCompactMode(
value)
} else if (widgetId === "music") {
SettingsData.setMediaSize(
value)
} else if (widgetId === "focusedWindow") {
SettingsData.setFocusedWindowCompactMode(
value)
} else if (widgetId === "runningApps") {
SettingsData.setRunningAppsCompactMode(
value)
}
}
onGpuSelectionChanged: (sectionId, widgetIndex, selectedIndex) => {
widgetsTab.handleGpuSelectionChanged(
sectionId, widgetIndex,
selectedIndex)
}
}
}
StyledRect {
width: parent.width
height: workspaceSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
border.width: 1
Column {
id: workspaceSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "view_module"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Workspace Settings"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
DankToggle {
width: parent.width
text: "Workspace Index Numbers"
description: "Show workspace index numbers in the top bar workspace switcher"
checked: SettingsData.showWorkspaceIndex
onToggled: checked => {
return SettingsData.setShowWorkspaceIndex(
checked)
}
}
DankToggle {
width: parent.width
text: "Workspace Padding"
description: "Always show a minimum of 3 workspaces, even if fewer are available"
checked: SettingsData.showWorkspacePadding
onToggled: checked => {
return SettingsData.setShowWorkspacePadding(
checked)
}
}
}
}
StyledRect {
width: parent.width
height: workspaceIconsSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
border.width: 1
visible: SettingsData.hasNamedWorkspaces()
Column {
id: workspaceIconsSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "label"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Named Workspace Icons"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
width: parent.width
text: "Configure icons for named workspaces. Icons take priority over numbers when both are enabled."
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
wrapMode: Text.WordWrap
}
Repeater {
model: SettingsData.getNamedWorkspaces()
Rectangle {
width: parent.width
height: workspaceIconRow.implicitHeight + Theme.spacingM
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r,
Theme.surfaceContainer.g,
Theme.surfaceContainer.b, 0.5)
border.color: Qt.rgba(Theme.outline.r,
Theme.outline.g,
Theme.outline.b, 0.3)
border.width: 1
Row {
id: workspaceIconRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingM
StyledText {
text: "\"" + modelData + "\""
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
width: 150
elide: Text.ElideRight
}
DankIconPicker {
id: iconPicker
anchors.verticalCenter: parent.verticalCenter
Component.onCompleted: {
var iconData = SettingsData.getWorkspaceNameIcon(
modelData)
if (iconData) {
setIcon(iconData.value,
iconData.type)
}
}
onIconSelected: (iconName, iconType) => {
SettingsData.setWorkspaceNameIcon(
modelData, {
"type": iconType,
"value": iconName
})
setIcon(iconName,
iconType)
}
Connections {
target: SettingsData
function onWorkspaceIconsUpdated() {
var iconData = SettingsData.getWorkspaceNameIcon(
modelData)
if (iconData) {
iconPicker.setIcon(
iconData.value,
iconData.type)
} else {
iconPicker.setIcon("", "icon")
}
}
}
}
Rectangle {
width: 28
height: 28
radius: Theme.cornerRadius
color: clearMouseArea.containsMouse ? Theme.errorHover : Theme.surfaceContainer
border.color: clearMouseArea.containsMouse ? Theme.error : Theme.outline
border.width: 1
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "close"
size: 16
color: clearMouseArea.containsMouse ? Theme.error : Theme.outline
anchors.centerIn: parent
}
MouseArea {
id: clearMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
SettingsData.removeWorkspaceNameIcon(
modelData)
}
}
}
Item {
width: parent.width - 150 - 240 - 28 - Theme.spacingM * 4
height: 1
}
}
}
}
}
}
}
}
DankWidgetSelectionPopup {
id: widgetSelectionPopup
anchors.centerIn: parent
onWidgetSelected: (widgetId, targetSection) => {
widgetsTab.addWidgetToSection(widgetId,
targetSection)
}
}
}

View File

@@ -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
}
}
}
}
}
}
}

View File

@@ -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

View File

@@ -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) {