blob: 7defa64dfd44ba2b5a4a1bb9e27e984e75971122 (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
|
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import "../../common"
import "../../common/functions"
import "../../common/widgets"
import "../../services"
import "."
Item {
id: root
required property var panelWindow
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
readonly property var toplevels: ToplevelManager.toplevels
readonly property int workspacesShown: Config.options.overview.rows * Config.options.overview.columns
readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown)
property bool monitorIsFocused: (Hyprland.focusedMonitor?.name == monitor.name)
property var windows: HyprlandData.windowList
property var windowByAddress: HyprlandData.windowByAddress
property var windowAddresses: HyprlandData.addresses
property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor?.id)
property real scale: Config.options.overview.scale
property color activeBorderColor: Appearance.colors.colSecondary
property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ?
((monitor.height / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale) :
((monitor.width / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale)
property real workspaceImplicitHeight: (monitorData?.transform % 2 === 1) ?
((monitor.width / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale) :
((monitor.height / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale)
property real workspaceNumberMargin: 80
property real workspaceNumberSize: 250 * monitor.scale
property int workspaceZ: 0
property int windowZ: 1
property int windowDraggingZ: 99999
property real workspaceSpacing: 5
property int draggingFromWorkspace: -1
property int draggingTargetWorkspace: -1
implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2
property Component windowComponent: OverviewWindow {}
property list<OverviewWindow> windowWidgets: []
StyledRectangularShadow {
target: overviewBackground
}
Rectangle { // Background
id: overviewBackground
property real padding: 10
anchors.fill: parent
anchors.margins: Appearance.sizes.elevationMargin
implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2
implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2
radius: Appearance.rounding.screenRounding * root.scale + padding
color: Appearance.colors.colLayer0
border.width: 1
border.color: Appearance.colors.colLayer0Border
ColumnLayout { // Workspaces
id: workspaceColumnLayout
z: root.workspaceZ
anchors.centerIn: parent
spacing: workspaceSpacing
Repeater {
model: Config.options.overview.rows
delegate: RowLayout {
id: row
property int rowIndex: index
spacing: workspaceSpacing
Repeater { // Workspace repeater
model: Config.options.overview.columns
Rectangle { // Workspace
id: workspace
property int colIndex: index
property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * Config.options.overview.columns + colIndex + 1
property color defaultWorkspaceColor: Appearance.colors.colLayer1
property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1)
property color hoveredBorderColor: Appearance.colors.colLayer2Hover
property bool hoveredWhileDragging: false
implicitWidth: root.workspaceImplicitWidth
implicitHeight: root.workspaceImplicitHeight
color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor
radius: Appearance.rounding.screenRounding * root.scale
border.width: 2
border.color: hoveredWhileDragging ? hoveredBorderColor : "transparent"
StyledText {
anchors.centerIn: parent
text: workspaceValue
font {
pixelSize: root.workspaceNumberSize * root.scale
weight: Font.DemiBold
family: Appearance.font.family.expressive
}
color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8)
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
MouseArea {
id: workspaceArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
if (root.draggingTargetWorkspace === -1) {
GlobalStates.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 { // Windows & focused workspace indicator
id: windowSpace
anchors.centerIn: parent
implicitWidth: workspaceColumnLayout.implicitWidth
implicitHeight: workspaceColumnLayout.implicitHeight
Repeater { // Window repeater
model: ScriptModel {
values: {
return ToplevelManager.toplevels.values.filter((toplevel) => {
const address = `0x${toplevel.HyprlandToplevel.address}`
var win = windowByAddress[address]
const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
return inWorkspaceGroup;
}).sort((a, b) => {
// Proper stacking order based on Hyprland's window properties
const addrA = `0x${a.HyprlandToplevel.address}`
const addrB = `0x${b.HyprlandToplevel.address}`
const winA = windowByAddress[addrA]
const winB = windowByAddress[addrB]
// 1. Pinned windows are always on top
if (winA?.pinned !== winB?.pinned) {
return winA?.pinned ? 1 : -1
}
// 2. Floating windows above tiled windows
if (winA?.floating !== winB?.floating) {
return winA?.floating ? 1 : -1
}
// 3. Within same category, sort by focus history
// Lower focusHistoryID = more recently focused = higher in stack
return (winB?.focusHistoryID ?? 0) - (winA?.focusHistoryID ?? 0)
})
}
}
delegate: OverviewWindow {
id: window
required property var modelData
required property int index
property int monitorId: windowData?.monitor
property var monitor: HyprlandData.monitors.find(m => m.id === monitorId)
property var address: `0x${modelData.HyprlandToplevel.address}`
windowData: windowByAddress[address]
toplevel: modelData
monitorData: monitor
// Calculate scale relative to window's source monitor
property real sourceMonitorWidth: (monitor?.transform % 2 === 1) ?
(monitor?.height ?? 1920) / (monitor?.scale ?? 1) - (monitor?.reserved?.[0] ?? 0) - (monitor?.reserved?.[2] ?? 0) :
(monitor?.width ?? 1920) / (monitor?.scale ?? 1) - (monitor?.reserved?.[0] ?? 0) - (monitor?.reserved?.[2] ?? 0)
property real sourceMonitorHeight: (monitor?.transform % 2 === 1) ?
(monitor?.width ?? 1080) / (monitor?.scale ?? 1) - (monitor?.reserved?.[1] ?? 0) - (monitor?.reserved?.[3] ?? 0) :
(monitor?.height ?? 1080) / (monitor?.scale ?? 1) - (monitor?.reserved?.[1] ?? 0) - (monitor?.reserved?.[3] ?? 0)
// Scale windows to fit the workspace size, accounting for different monitor sizes
scale: Math.min(
root.workspaceImplicitWidth / sourceMonitorWidth,
root.workspaceImplicitHeight / sourceMonitorHeight
)
availableWorkspaceWidth: root.workspaceImplicitWidth
availableWorkspaceHeight: root.workspaceImplicitHeight
widgetMonitorId: root.monitor.id
property bool atInitPosition: (initX == x && initY == y)
property int workspaceColIndex: (windowData?.workspace.id - 1) % Config.options.overview.columns
property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / Config.options.overview.columns)
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex
Timer {
id: updateWindowPosition
interval: Config.options.hacks.arbitraryRaceConditionDelay
repeat: false
running: false
onTriggered: {
window.x = Math.round(Math.max((windowData?.at[0] - (monitor?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * root.scale, 0) + xOffset)
window.y = Math.round(Math.max((windowData?.at[1] - (monitor?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale, 0) + yOffset)
}
}
z: atInitPosition ? (root.windowZ + index) : root.windowDraggingZ
Drag.hotSpot.x: targetWindowWidth / 2
Drag.hotSpot.y: targetWindowHeight / 2
MouseArea {
id: dragArea
anchors.fill: parent
hoverEnabled: true
onEntered: hovered = true
onExited: 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
if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) {
Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace}, address:${window.windowData?.address}`)
updateWindowPosition.restart()
}
else {
window.x = window.initX
window.y = window.initY
}
}
onClicked: (event) => {
if (!windowData) return;
if (event.button === Qt.LeftButton) {
GlobalStates.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
}
}
StyledToolTip {
extraVisibleCondition: false
alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active
text: `${windowData?.title ?? "Unknown"}\n[${windowData?.class ?? "unknown"}] ${windowData?.xwayland ? "[XWayland] " : ""}`
}
}
}
}
Rectangle { // Focused workspace indicator
id: focusedWorkspaceIndicator
property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown)
property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / Config.options.overview.columns)
property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % Config.options.overview.columns
x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex
y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex
z: root.windowZ
width: root.workspaceImplicitWidth
height: root.workspaceImplicitHeight
color: "transparent"
radius: Appearance.rounding.screenRounding * root.scale
border.width: 2
border.color: root.activeBorderColor
Behavior on x {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on y {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
}
}
}
|