1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-29 07:52:50 -05:00

Refactor workspaceSwitcher (#246)

* Refactor WorkspaceSwitcher

Implement async data loading, dynamic UI, and fix QML connection
warnings.

* Enhance WorkspaceSwitcher

Adjust width dynamically based on icon count for better space
utilization

* fix(WorkspaceSwitcher): correct ternary logic for placeholder workspaces
This commit is contained in:
lonerorz
2025-09-25 02:36:38 +08:00
committed by GitHub
parent 7516d44de9
commit bed2259944

View File

@@ -147,7 +147,7 @@ Rectangle {
function getHyprlandWorkspaces() { function getHyprlandWorkspaces() {
const workspaces = Hyprland.workspaces?.values || [] const workspaces = Hyprland.workspaces?.values || []
if (!root.screenName || !SettingsData.workspacesPerMonitor) { if (!root.screenName || !SettingsData.workspacesPerMonitor) {
// Show all workspaces on all monitors if per-monitor filtering is disabled // Show all workspaces on all monitors if per-monitor filtering is disabled
const sorted = workspaces.slice().sort((a, b) => a.id - b.id) const sorted = workspaces.slice().sort((a, b) => a.id - b.id)
@@ -162,7 +162,7 @@ Rectangle {
const monitorWorkspaces = workspaces.filter(ws => { const monitorWorkspaces = workspaces.filter(ws => {
return ws.lastIpcObject && ws.lastIpcObject.monitor === root.screenName return ws.lastIpcObject && ws.lastIpcObject.monitor === root.screenName
}) })
if (monitorWorkspaces.length === 0) { if (monitorWorkspaces.length === 0) {
// Fallback if no workspaces exist for this monitor // Fallback if no workspaces exist for this monitor
return [{ return [{
@@ -183,7 +183,7 @@ Rectangle {
// Find the monitor object for this screen // Find the monitor object for this screen
const monitors = Hyprland.monitors?.values || [] const monitors = Hyprland.monitors?.values || []
const currentMonitor = monitors.find(monitor => monitor.name === root.screenName) const currentMonitor = monitors.find(monitor => monitor.name === root.screenName)
if (!currentMonitor) { if (!currentMonitor) {
return 1 return 1
} }
@@ -271,6 +271,8 @@ Rectangle {
model: root.workspaceList model: root.workspaceList
Rectangle { Rectangle {
id: delegateRoot
property bool isActive: { property bool isActive: {
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
return modelData && modelData.id === root.currentWorkspace return modelData && modelData.id === root.currentWorkspace
@@ -284,34 +286,72 @@ Rectangle {
return modelData === -1 return modelData === -1
} }
property bool isHovered: mouseArea.containsMouse property bool isHovered: mouseArea.containsMouse
property var workspaceData: {
if (isPlaceholder) {
return null
}
if (CompositorService.isNiri) { property var loadedWorkspaceData: null
return NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.screenName) || null property var loadedIconData: null
} property bool loadedHasIcon: false
return CompositorService.isHyprland ? modelData : null property var loadedIcons: []
}
property var iconData: 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: { Timer {
if (SettingsData.showWorkspaceApps) { id: dataUpdateTimer
if (icons.length > 0) { interval: 50 // Defer data calculation by 50ms
return isActive ? widgetHeight * 1.0 + Theme.spacingXS + contentRow.implicitWidth : widgetHeight * 0.8 + contentRow.implicitWidth onTriggered: {
if (isPlaceholder) {
delegateRoot.loadedWorkspaceData = null
delegateRoot.loadedIconData = null
delegateRoot.loadedHasIcon = false
delegateRoot.loadedIcons = []
return
}
var wsData = null;
if (CompositorService.isNiri) {
wsData = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.screenName) || null;
} else if (CompositorService.isHyprland) {
wsData = modelData;
}
delegateRoot.loadedWorkspaceData = wsData;
var icData = null;
if (wsData?.name) {
icData = SettingsData.getWorkspaceNameIcon(wsData.name);
}
delegateRoot.loadedIconData = icData;
delegateRoot.loadedHasIcon = icData !== null;
if (SettingsData.showWorkspaceApps) {
delegateRoot.loadedIcons = root.getWorkspaceIcons(CompositorService.isHyprland ? modelData : (modelData === -1 ? null : modelData));
} else { } else {
return isActive ? widgetHeight * 1.0 + Theme.spacingXS : widgetHeight * 0.8 delegateRoot.loadedIcons = [];
} }
} }
return isActive ? widgetHeight * 1.2 + Theme.spacingXS : widgetHeight * 0.8 }
function updateAllData() {
dataUpdateTimer.restart()
}
width: {
if (SettingsData.showWorkspaceApps && loadedIcons.length > 0) {
const numIcons = Math.min(loadedIcons.length, SettingsData.maxWorkspaceIcons);
const iconsWidth = numIcons * 18 + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0);
const baseWidth = isActive ? root.widgetHeight * 1.0 + Theme.spacingXS : root.widgetHeight * 0.8;
return baseWidth + iconsWidth;
}
return isActive ? root.widgetHeight * 1.2 : root.widgetHeight * 0.8;
} }
height: SettingsData.showWorkspaceApps ? widgetHeight * 0.8 : widgetHeight * 0.6 height: SettingsData.showWorkspaceApps ? widgetHeight * 0.8 : widgetHeight * 0.6
radius: height / 2 radius: height / 2
color: isActive ? Theme.primary : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha color: isActive ? Theme.primary : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.outlineButton : Theme.surfaceTextAlpha
Behavior on width {
enabled: (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3)
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
MouseArea { MouseArea {
id: mouseArea id: mouseArea
@@ -332,118 +372,153 @@ Rectangle {
} }
} }
Row { // Loader for App Icons
id: contentRow Loader {
anchors.centerIn: parent id: appIconsLoader
spacing: 4 anchors.fill: parent
visible: SettingsData.showWorkspaceApps && icons.length > 0 active: SettingsData.showWorkspaceApps
sourceComponent: Item {
Row {
id: contentRow
anchors.centerIn: parent
spacing: 4
visible: loadedIcons.length > 0
Repeater { Repeater {
model: icons.slice(0, SettingsData.maxWorkspaceIcons) model: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
delegate: Item { delegate: Item {
width: 18 width: 18
height: 18 height: 18
IconImage { IconImage {
id: appIcon id: appIcon
property var windowId: modelData.windowId property var windowId: modelData.windowId
anchors.fill: parent anchors.fill: parent
source: modelData.icon source: modelData.icon
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6 opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
visible: !modelData.isSteamApp visible: !modelData.isSteamApp
} }
DankIcon { DankIcon {
anchors.centerIn: parent anchors.centerIn: parent
size: 18 size: 18
name: "sports_esports" name: "sports_esports"
color: Theme.surfaceText color: Theme.surfaceText
opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6 opacity: modelData.active ? 1.0 : appMouseArea.containsMouse ? 0.8 : 0.6
visible: modelData.isSteamApp visible: modelData.isSteamApp
} }
MouseArea { MouseArea {
id: appMouseArea id: appMouseArea
hoverEnabled: true hoverEnabled: true
anchors.fill: parent anchors.fill: parent
enabled: isActive enabled: isActive
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
Hyprland.dispatch(`focuswindow address:${appIcon.windowId}`) Hyprland.dispatch(`focuswindow address:${appIcon.windowId}`)
} else if (CompositorService.isNiri) { } else if (CompositorService.isNiri) {
NiriService.focusWindow(appIcon.windowId) 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"
}
} }
} }
} }
}
}
}
Rectangle { // Loader for Custom Name Icon
visible: modelData.count > 1 && !isActive Loader {
width: 12 id: customIconLoader
height: 12 anchors.fill: parent
radius: 6 active: !isPlaceholder && loadedHasIcon && loadedIconData.type === "icon" && !SettingsData.showWorkspaceApps
color: "black" sourceComponent: Item {
border.color: "white" DankIcon {
border.width: 1 anchors.centerIn: parent
anchors.right: parent.right name: loadedIconData ? loadedIconData.value : "" // NULL CHECK
anchors.bottom: parent.bottom size: Theme.fontSizeSmall
z: 2 color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : Theme.surfaceTextMedium
weight: isActive && !isPlaceholder ? 500 : 400
}
}
}
Text { // Loader for Custom Name Text
anchors.centerIn: parent Loader {
text: modelData.count id: customTextLoader
font.pixelSize: 8 anchors.fill: parent
color: "white" active: !isPlaceholder && loadedHasIcon && loadedIconData.type === "text" && !SettingsData.showWorkspaceApps
sourceComponent: Item {
StyledText {
anchors.centerIn: parent
text: loadedIconData ? loadedIconData.value : "" // NULL CHECK
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
}
}
}
// Loader for Workspace Index
Loader {
id: indexLoader
anchors.fill: parent
active: !isPlaceholder && SettingsData.showWorkspaceIndex && !loadedHasIcon && !SettingsData.showWorkspaceApps
sourceComponent: Item {
StyledText {
anchors.centerIn: parent
text: {
const isPlaceholder = CompositorService.isHyprland ? (modelData?.id === -1) : (modelData === -1)
if (isPlaceholder) {
return index + 1
} }
return CompositorService.isHyprland ? (modelData?.id || "") : (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
} }
} }
} }
DankIcon { // --- LOGIC / TRIGGERS ---
visible: hasIcon && iconData.type === "icon" && (!SettingsData.showWorkspaceApps || icons.length === 0) Component.onCompleted: updateAllData()
anchors.centerIn: parent
name: (hasIcon && iconData.type === "icon") ? iconData.value : "" Connections {
size: Theme.fontSizeSmall target: CompositorService
color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : Theme.surfaceTextMedium function onSortedToplevelsChanged() { delegateRoot.updateAllData() }
weight: isActive && !isPlaceholder ? 500 : 400
} }
Connections {
StyledText { target: NiriService
visible: hasIcon && iconData.type === "text" && (!SettingsData.showWorkspaceApps || icons.length === 0) enabled: CompositorService.isNiri
anchors.centerIn: parent function onAllWorkspacesChanged() { delegateRoot.updateAllData() }
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
} }
Connections {
StyledText { target: SettingsData
visible: (SettingsData.showWorkspaceIndex && !hasIcon && (!SettingsData.showWorkspaceApps || icons.length === 0)) function onShowWorkspaceAppsChanged() { delegateRoot.updateAllData() }
anchors.centerIn: parent function onWorkspaceNameIconsChanged() { delegateRoot.updateAllData() }
text: {
const isPlaceholder = CompositorService.isHyprland ? (modelData?.id === -1) : (modelData === -1)
if (isPlaceholder) {
return index + 1
}
return CompositorService.isHyprland ? (modelData?.id || "") : (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 {
// When having more icons, animation becomes clunky
enabled: (!SettingsData.showWorkspaceApps || SettingsData.maxWorkspaceIcons <= 3)
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
} }
} }
} }