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:
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user