mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
435 lines
19 KiB
QML
435 lines
19 KiB
QML
import QtQuick
|
|
import QtQuick.Effects
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import Quickshell.Hyprland
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
Item {
|
|
id: root
|
|
required property var panelWindow
|
|
required property bool overviewOpen
|
|
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
|
|
readonly property real dpr: CompositorService.getScreenScale(panelWindow.screen)
|
|
readonly property int workspacesShown: SettingsData.overviewRows * SettingsData.overviewColumns
|
|
|
|
readonly property var allWorkspaces: Hyprland.workspaces?.values || []
|
|
readonly property var allWorkspaceIds: {
|
|
const workspaces = allWorkspaces
|
|
if (!workspaces || workspaces.length === 0) return []
|
|
try {
|
|
const ids = workspaces.map(ws => ws?.id).filter(id => id !== null && id !== undefined)
|
|
return ids.sort((a, b) => a - b)
|
|
} catch (e) {
|
|
return []
|
|
}
|
|
}
|
|
|
|
readonly property var thisMonitorWorkspaceIds: {
|
|
const workspaces = allWorkspaces
|
|
const mon = monitor
|
|
if (!workspaces || workspaces.length === 0 || !mon) return []
|
|
try {
|
|
const filtered = workspaces.filter(ws => ws?.monitor?.name === mon.name)
|
|
return filtered.map(ws => ws?.id).filter(id => id !== null && id !== undefined).sort((a, b) => a - b)
|
|
} catch (e) {
|
|
return []
|
|
}
|
|
}
|
|
|
|
readonly property var displayedWorkspaceIds: {
|
|
if (!allWorkspaceIds || allWorkspaceIds.length === 0) {
|
|
const result = []
|
|
for (let i = 1; i <= workspacesShown; i++) {
|
|
result.push(i)
|
|
}
|
|
return result
|
|
}
|
|
|
|
try {
|
|
const maxExisting = Math.max(...allWorkspaceIds)
|
|
const totalNeeded = Math.max(workspacesShown, allWorkspaceIds.length)
|
|
const result = []
|
|
|
|
for (let i = 1; i <= maxExisting; i++) {
|
|
result.push(i)
|
|
}
|
|
|
|
let nextId = maxExisting + 1
|
|
while (result.length < totalNeeded) {
|
|
result.push(nextId)
|
|
nextId++
|
|
}
|
|
|
|
return result
|
|
} catch (e) {
|
|
const result = []
|
|
for (let i = 1; i <= workspacesShown; i++) {
|
|
result.push(i)
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
|
|
readonly property int minWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[0] : 1
|
|
readonly property int maxWorkspaceId: displayedWorkspaceIds.length > 0 ? displayedWorkspaceIds[displayedWorkspaceIds.length - 1] : workspacesShown
|
|
readonly property int displayWorkspaceCount: displayedWorkspaceIds.length
|
|
|
|
readonly property int effectiveColumns: SettingsData.overviewColumns
|
|
readonly property int effectiveRows: Math.max(SettingsData.overviewRows, Math.ceil(displayWorkspaceCount / effectiveColumns))
|
|
|
|
function getWorkspaceMonitorName(workspaceId) {
|
|
if (!allWorkspaces || !workspaceId) return ""
|
|
try {
|
|
const ws = allWorkspaces.find(w => w?.id === workspaceId)
|
|
return ws?.monitor?.name ?? ""
|
|
} catch (e) {
|
|
return ""
|
|
}
|
|
}
|
|
|
|
function workspaceHasWindows(workspaceId) {
|
|
if (!workspaceId) return false
|
|
try {
|
|
const workspace = allWorkspaces.find(ws => ws?.id === workspaceId)
|
|
if (!workspace) return false
|
|
const toplevels = workspace?.toplevels?.values || []
|
|
return toplevels.length > 0
|
|
} catch (e) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
property bool monitorIsFocused: monitor?.focused ?? false
|
|
property real scale: SettingsData.overviewScale
|
|
property color activeBorderColor: Theme.primary
|
|
|
|
readonly property real monitorPhysicalWidth: panelWindow.screen ? (panelWindow.screen.width / root.dpr) : (monitor?.width ?? 1920)
|
|
readonly property real monitorPhysicalHeight: panelWindow.screen ? (panelWindow.screen.height / root.dpr) : (monitor?.height ?? 1080)
|
|
property real workspaceImplicitWidth: monitorPhysicalWidth * root.scale
|
|
property real workspaceImplicitHeight: monitorPhysicalHeight * root.scale
|
|
|
|
property int workspaceZ: 0
|
|
property int windowZ: 1
|
|
property int monitorLabelZ: 2
|
|
property int windowDraggingZ: 99999
|
|
property real workspaceSpacing: 5
|
|
|
|
property int draggingFromWorkspace: -1
|
|
property int draggingTargetWorkspace: -1
|
|
|
|
implicitWidth: overviewBackground.implicitWidth + Theme.spacingL * 2
|
|
implicitHeight: overviewBackground.implicitHeight + Theme.spacingL * 2
|
|
|
|
Component.onCompleted: {
|
|
Hyprland.refreshToplevels()
|
|
Hyprland.refreshWorkspaces()
|
|
Hyprland.refreshMonitors()
|
|
}
|
|
|
|
onOverviewOpenChanged: {
|
|
if (overviewOpen) {
|
|
Hyprland.refreshToplevels()
|
|
Hyprland.refreshWorkspaces()
|
|
Hyprland.refreshMonitors()
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: overviewBackground
|
|
property real padding: 10
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingL
|
|
|
|
implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2
|
|
implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2
|
|
radius: Theme.cornerRadius
|
|
color: Theme.surfaceContainer
|
|
|
|
layer.enabled: true
|
|
layer.effect: MultiEffect {
|
|
shadowEnabled: true
|
|
shadowBlur: 0.5
|
|
shadowHorizontalOffset: 0
|
|
shadowVerticalOffset: 4
|
|
shadowColor: Theme.shadowStrong
|
|
shadowOpacity: 1
|
|
blurMax: 32
|
|
}
|
|
|
|
ColumnLayout {
|
|
id: workspaceColumnLayout
|
|
|
|
z: root.workspaceZ
|
|
anchors.centerIn: parent
|
|
spacing: workspaceSpacing
|
|
|
|
Repeater {
|
|
model: root.effectiveRows
|
|
delegate: RowLayout {
|
|
id: row
|
|
property int rowIndex: index
|
|
spacing: workspaceSpacing
|
|
|
|
Repeater {
|
|
model: root.effectiveColumns
|
|
Rectangle {
|
|
id: workspace
|
|
property int colIndex: index
|
|
property int workspaceIndex: rowIndex * root.effectiveColumns + colIndex
|
|
property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1
|
|
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
|
|
property var workspaceObj: (workspaceExists && Hyprland.workspaces?.values) ? Hyprland.workspaces.values.find(ws => ws?.id === workspaceValue) : null
|
|
property bool isActive: workspaceObj?.active ?? false
|
|
property bool isOnThisMonitor: (workspaceObj && root.monitor) ? (workspaceObj.monitor?.name === root.monitor.name) : true
|
|
property bool hasWindows: (workspaceValue > 0) ? root.workspaceHasWindows(workspaceValue) : false
|
|
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : ""
|
|
property color defaultWorkspaceColor: workspaceExists ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, 0.3)
|
|
property color hoveredWorkspaceColor: Qt.lighter(defaultWorkspaceColor, 1.1)
|
|
property color hoveredBorderColor: Theme.surfaceVariant
|
|
property bool hoveredWhileDragging: false
|
|
property bool shouldShowActiveIndicator: isActive && isOnThisMonitor && hasWindows
|
|
|
|
visible: workspaceValue !== -1
|
|
|
|
implicitWidth: root.workspaceImplicitWidth
|
|
implicitHeight: root.workspaceImplicitHeight
|
|
color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor
|
|
radius: Theme.cornerRadius
|
|
border.width: 2
|
|
border.color: hoveredWhileDragging ? hoveredBorderColor : (shouldShowActiveIndicator ? root.activeBorderColor : "transparent")
|
|
|
|
StyledText {
|
|
anchors.centerIn: parent
|
|
text: workspaceValue
|
|
font.pixelSize: Theme.fontSizeXLarge * 6
|
|
font.weight: Font.DemiBold
|
|
color: Theme.withAlpha(Theme.surfaceText, workspaceExists ? 0.2 : 0.1)
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
|
|
MouseArea {
|
|
id: workspaceArea
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton
|
|
onClicked: {
|
|
if (root.draggingTargetWorkspace === -1) {
|
|
root.overviewOpen = false
|
|
Hyprland.dispatch(`workspace ${workspaceValue}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
DropArea {
|
|
anchors.fill: parent
|
|
onEntered: {
|
|
root.draggingTargetWorkspace = workspaceValue
|
|
if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return
|
|
hoveredWhileDragging = true
|
|
}
|
|
onExited: {
|
|
hoveredWhileDragging = false
|
|
if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: windowSpace
|
|
anchors.centerIn: parent
|
|
implicitWidth: workspaceColumnLayout.implicitWidth
|
|
implicitHeight: workspaceColumnLayout.implicitHeight
|
|
|
|
Repeater {
|
|
model: ScriptModel {
|
|
values: {
|
|
const workspaces = root.allWorkspaces
|
|
const minId = root.minWorkspaceId
|
|
const maxId = root.maxWorkspaceId
|
|
|
|
if (!workspaces || workspaces.length === 0) return []
|
|
|
|
try {
|
|
const result = []
|
|
for (const workspace of workspaces) {
|
|
const wsId = workspace?.id ?? -1
|
|
if (wsId >= minId && wsId <= maxId) {
|
|
const toplevels = workspace?.toplevels?.values || []
|
|
for (const toplevel of toplevels) {
|
|
result.push(toplevel)
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
} catch (e) {
|
|
console.error("OverviewWidget filter error:", e)
|
|
return []
|
|
}
|
|
}
|
|
}
|
|
delegate: OverviewWindow {
|
|
id: window
|
|
required property var modelData
|
|
|
|
overviewOpen: root.overviewOpen
|
|
readonly property int windowWorkspaceId: modelData?.workspace?.id ?? -1
|
|
|
|
function getWorkspaceIndex() {
|
|
if (!root.displayedWorkspaceIds || root.displayedWorkspaceIds.length === 0) return 0
|
|
if (!windowWorkspaceId || windowWorkspaceId < 0) return 0
|
|
try {
|
|
for (let i = 0; i < root.displayedWorkspaceIds.length; i++) {
|
|
if (root.displayedWorkspaceIds[i] === windowWorkspaceId) {
|
|
return i
|
|
}
|
|
}
|
|
return 0
|
|
} catch (e) {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
readonly property int workspaceIndex: getWorkspaceIndex()
|
|
readonly property int workspaceColIndex: workspaceIndex % root.effectiveColumns
|
|
readonly property int workspaceRowIndex: Math.floor(workspaceIndex / root.effectiveColumns)
|
|
|
|
toplevel: modelData
|
|
scale: root.scale
|
|
monitorDpr: root.dpr
|
|
availableWorkspaceWidth: root.workspaceImplicitWidth
|
|
availableWorkspaceHeight: root.workspaceImplicitHeight
|
|
widgetMonitorId: root.monitor.id
|
|
|
|
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
|
|
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex
|
|
|
|
z: atInitPosition ? root.windowZ : root.windowDraggingZ
|
|
property bool atInitPosition: (initX == x && initY == y)
|
|
|
|
Drag.hotSpot.x: width / 2
|
|
Drag.hotSpot.y: height / 2
|
|
|
|
MouseArea {
|
|
id: dragArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
onEntered: window.hovered = true
|
|
onExited: window.hovered = false
|
|
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
|
drag.target: parent
|
|
|
|
onPressed: (mouse) => {
|
|
root.draggingFromWorkspace = windowData?.workspace.id
|
|
window.pressed = true
|
|
window.Drag.active = true
|
|
window.Drag.source = window
|
|
window.Drag.hotSpot.x = mouse.x
|
|
window.Drag.hotSpot.y = mouse.y
|
|
}
|
|
|
|
onReleased: {
|
|
const targetWorkspace = root.draggingTargetWorkspace
|
|
window.pressed = false
|
|
window.Drag.active = false
|
|
root.draggingFromWorkspace = -1
|
|
root.draggingTargetWorkspace = -1
|
|
|
|
if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) {
|
|
Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace},address:${windowData?.address}`)
|
|
Qt.callLater(() => {
|
|
Hyprland.refreshToplevels()
|
|
Hyprland.refreshWorkspaces()
|
|
Qt.callLater(() => {
|
|
window.x = window.initX
|
|
window.y = window.initY
|
|
})
|
|
})
|
|
} else {
|
|
window.x = window.initX
|
|
window.y = window.initY
|
|
}
|
|
}
|
|
|
|
onClicked: (event) => {
|
|
if (!windowData || !windowData.address) return
|
|
|
|
if (event.button === Qt.LeftButton) {
|
|
root.overviewOpen = false
|
|
Hyprland.dispatch(`focuswindow address:${windowData.address}`)
|
|
event.accepted = true
|
|
} else if (event.button === Qt.MiddleButton) {
|
|
Hyprland.dispatch(`closewindow address:${windowData.address}`)
|
|
event.accepted = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: monitorLabelSpace
|
|
anchors.centerIn: parent
|
|
implicitWidth: workspaceColumnLayout.implicitWidth
|
|
implicitHeight: workspaceColumnLayout.implicitHeight
|
|
z: root.monitorLabelZ
|
|
|
|
Repeater {
|
|
model: root.effectiveRows
|
|
delegate: Item {
|
|
id: labelRow
|
|
property int rowIndex: index
|
|
y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex
|
|
width: parent.width
|
|
height: root.workspaceImplicitHeight
|
|
|
|
Repeater {
|
|
model: root.effectiveColumns
|
|
delegate: Item {
|
|
id: labelItem
|
|
property int colIndex: index
|
|
property int workspaceIndex: labelRow.rowIndex * root.effectiveColumns + colIndex
|
|
property int workspaceValue: (root.displayedWorkspaceIds && workspaceIndex < root.displayedWorkspaceIds.length) ? root.displayedWorkspaceIds[workspaceIndex] : -1
|
|
property bool workspaceExists: (root.allWorkspaceIds && workspaceValue > 0) ? root.allWorkspaceIds.includes(workspaceValue) : false
|
|
property string workspaceMonitorName: (workspaceValue > 0) ? root.getWorkspaceMonitorName(workspaceValue) : ""
|
|
|
|
x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex
|
|
width: root.workspaceImplicitWidth
|
|
height: root.workspaceImplicitHeight
|
|
|
|
Rectangle {
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
anchors.margins: Theme.spacingS
|
|
width: monitorNameText.contentWidth + Theme.spacingS * 2
|
|
height: monitorNameText.contentHeight + Theme.spacingXS * 2
|
|
radius: Theme.cornerRadius
|
|
color: Theme.surface
|
|
visible: labelItem.workspaceExists && labelItem.workspaceMonitorName !== ""
|
|
|
|
StyledText {
|
|
id: monitorNameText
|
|
anchors.centerIn: parent
|
|
text: labelItem.workspaceMonitorName
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: Font.Medium
|
|
color: Theme.surfaceText
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|