aboutsummaryrefslogtreecommitdiffstats
path: root/config/quickshell/modules/overview/OverviewWidget.qml
blob: 05a15e106dea4d7f992a8c3e3c4519e490f2a9c3 (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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
import "root:/"
import "root:/services/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Hyprland

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: ConfigOptions.overview.numOfRows * ConfigOptions.overview.numOfCols
    readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown)
    property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id)
    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: ConfigOptions.overview.scale
    property color activeBorderColor: Appearance.m3colors.m3accentSecondary

    property real workspaceImplicitWidth: Math.max(100, (monitorData?.transform % 2 === 1) ? 
        ((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) :
        ((monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale))
    property real workspaceImplicitHeight: Math.max(60, (monitorData?.transform % 2 === 1) ? 
        ((monitor.width - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) :
        ((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale))

    property real workspaceNumberMargin: 80
    property real workspaceNumberSize: (ConfigOptions.overview.workspaceNumberSize > 0) 
        ? ConfigOptions.overview.workspaceNumberSize 
        : Math.min(workspaceImplicitHeight, workspaceImplicitWidth) * 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

    // Debug logging function
    function debugMultiMonitorInfo() {
        console.log("=== Multi-Monitor Debug Info ===")
        console.log("Current monitor ID:", root.monitor.id)
        console.log("Current monitor name:", root.monitor.name)
        console.log("Current monitor scale:", root.monitor.scale)
        console.log("Current monitor position:", root.monitor.x, root.monitor.y)
        console.log("Total monitors:", HyprlandData.monitors.length)
        
        HyprlandData.monitors.forEach((mon, idx) => {
            console.log(`Monitor ${idx}: ID=${mon.id}, Name=${mon.name}, Scale=${mon.scale}, Pos=(${mon.x},${mon.y}), Size=${mon.width}x${mon.height}, Reserved=[${mon.reserved.join(",")}]`)
        })
        
        console.log("Total windows:", windowAddresses.length)
        windowAddresses.forEach((address, idx) => {
            const win = windowByAddress[address]
            if (win) {
                console.log(`Window ${idx}: ${win.class} on monitor ${win.monitor}, workspace ${win.workspace?.id}, pos=(${win.at[0]},${win.at[1]})`)
            }
        })
        console.log("=== End Debug Info ===")
    }

    Component.onCompleted: {
        debugMultiMonitorInfo()
    }

    implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
    implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2

    property Component windowComponent: OverviewWindow {}
    property list<OverviewWindow> windowWidgets: []

    // Shared wallpaper image - loaded once and reused
    Image {
        id: sharedWallpaper
        source: Appearance.background_image || ""
        visible: false // Hidden as it's only used as a source
        cache: true
        asynchronous: true
        smooth: true
        opacity: Appearance.workpaceTransparency // Adds slight transparency (0.0 = fully transparent, 1.0 = fully opaque)
    }

    StyledRectangularShadow {
        target: overviewBackground
    }
    Rectangle { // Background
        id: overviewBackground
        property real padding: 10
        anchors.fill: parent
        anchors.margins: Appearance.sizes.elevationMargin
        border.color : ColorUtils.transparentize(Appearance.m3colors.m3borderPrimary, 0.2)
        border.width : 2

        implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2
        implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2
        radius: Appearance.rounding.screenRounding * root.scale + padding
        color: Appearance.colors.colLayer0


        ColumnLayout { // Workspaces
            id: workspaceColumnLayout

            z: root.workspaceZ
            anchors.centerIn: parent
            spacing: workspaceSpacing
            Repeater {
                model: ConfigOptions.overview.numOfRows
                delegate: RowLayout {
                    id: row
                    property int rowIndex: index
                    spacing: workspaceSpacing

                    Repeater { // Workspace repeater
                        model: ConfigOptions.overview.numOfCols
                        Rectangle { // Workspace
                            id: workspace
                            property int colIndex: index
                            property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.numOfCols + 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
                            readonly property int padding: ConfigOptions.overview.windowPadding

                            Layout.preferredWidth: root.workspaceImplicitWidth
                            Layout.preferredHeight: root.workspaceImplicitHeight
                            Layout.minimumWidth: 100
                            Layout.minimumHeight: 60
                            
                            width: root.workspaceImplicitWidth
                            height: root.workspaceImplicitHeight
                            color: "transparent"
                            radius: Appearance.rounding.screenRounding * root.scale
                            clip: true
                            opacity: Appearance.workpaceTransparency // Adds slight transparency (0.0 = fully transparent, 1.0 = fully opaque)


                            // Efficient wallpaper using ShaderEffectSource
                            Rectangle {
                                id: wallpaperContainer
                                anchors.fill: parent
                                anchors.margins: 2 // Leave space for border
                                radius: workspace.radius - 2
                                color: workspace.defaultWorkspaceColor // Fallback color
                                clip: true
                                
                                ShaderEffectSource {
                                    id: wallpaperSource
                                    anchors.fill: parent
                                    sourceItem: sharedWallpaper
                                    visible: sharedWallpaper.status === Image.Ready
                                    smooth: true
                                    
                                    // Scale to fill while preserving aspect ratio
                                    transform: Scale {
                                        property real aspectRatio: sharedWallpaper.implicitWidth / Math.max(1, sharedWallpaper.implicitHeight)
                                        property real containerRatio: wallpaperContainer.width / Math.max(1, wallpaperContainer.height)
                                        
                                        xScale: aspectRatio > containerRatio ? 
                                            wallpaperContainer.height * aspectRatio / wallpaperContainer.width : 1
                                        yScale: aspectRatio > containerRatio ? 
                                            1 : wallpaperContainer.width / (wallpaperContainer.height * aspectRatio)
                                        
                                        origin.x: wallpaperContainer.width / 2
                                        origin.y: wallpaperContainer.height / 2
                                    }
                                }
                                
                                // Fallback when image fails to load or isn't ready
                                Rectangle {
                                    anchors.fill: parent
                                    color: workspace.defaultWorkspaceColor
                                    visible: sharedWallpaper.status !== Image.Ready
                                }
                                
                                // Optional: Add overlay for better text readability and hover effects
                                Rectangle {
                                    anchors.fill: parent
                                    color: hoveredWhileDragging ? hoveredWorkspaceColor : "black"
                                    opacity: hoveredWhileDragging ? 0.3 : 0.1
                                }
                            }

                            // Border overlay - on top of wallpaper
                            Rectangle {
                                anchors.fill: parent
                                color: "transparent"
                                radius: parent.radius
                                border.width: 1
                                border.color: hoveredWhileDragging ? hoveredBorderColor : ColorUtils.transparentize(Appearance.m3colors.m3borderPrimary, 0.6)
                                z: 10 // Ensure it's on top
                            }

                            StyledText {
                                // Position in top-left corner with padding
                                anchors.top: parent.top
                                anchors.left: parent.left
                                anchors.topMargin: 12  // Padding from top edge
                                anchors.leftMargin: 12 // Padding from left edge
                                
                                text: workspaceValue
                                font.pixelSize: root.workspaceNumberSize * root.scale
                                font.weight: Font.DemiBold
                                color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8)
                                horizontalAlignment: Text.AlignLeft
                                verticalAlignment: Text.AlignTop
                                z: 15 // Above border
                            }

                            MouseArea {
                                id: workspaceArea
                                anchors.fill: parent
                                acceptedButtons: Qt.LeftButton
                                z: 20 // Above all visual elements
                                onClicked: {
                                    if (root.draggingTargetWorkspace === -1) {
                                        GlobalStates.overviewOpen = false
                                        Hyprland.dispatch(`workspace ${workspaceValue}`)
                                    }
                                }
                            }

                            DropArea {
                                anchors.fill: parent
                                z: 20 // Same level as MouseArea
                                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: windowAddresses.filter((address) => {
                        var win = windowByAddress[address]
                        if (!win) return false
                        
                        // Filter by workspace group AND monitor if configured
                        const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && 
                            win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
                        const inMonitor = ConfigOptions.overview.showAllMonitors || win.monitor === root.monitor.id
                        
                        return inWorkspaceGroup && inMonitor
                    })
                }
                delegate: OverviewWindow {
                    id: window
                    windowData: windowByAddress[modelData]
                    monitorData: root.monitorData
                    scale: root.scale
                    availableWorkspaceWidth: root.workspaceImplicitWidth
                    availableWorkspaceHeight: root.workspaceImplicitHeight

                    property bool atInitPosition: (initX == x && initY == y)
                    restrictToWorkspace: Drag.active || atInitPosition

                    property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.numOfCols
                    property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.numOfCols)
                    xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
                    yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex

                    Timer {
                        id: updateWindowPosition
                        interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
                        repeat: false
                        running: false
                        onTriggered: {
                            window.x = Math.max((windowData?.at[0] - monitorData?.reserved[0] - monitorData?.x) * root.scale, 0) + xOffset
                            window.y = Math.max((windowData?.at[1] - monitorData?.reserved[1] - monitorData?.y) * root.scale, 0) + yOffset
                        }
                    }

                    z: atInitPosition ? root.windowZ : 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: {
                            root.draggingFromWorkspace = windowData?.workspace.id
                            window.pressed = true
                            window.Drag.active = true
                            window.Drag.source = window
                        }
                        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
                            content: `${windowData.title}\n[${windowData.class}] ${windowData.xwayland ? "[XWayland] " : ""}\n`
                        }
                    }
                }
            }

            Rectangle { // Focused workspace indicator
                id: focusedWorkspaceIndicator
                property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown)
                property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / ConfigOptions.overview.numOfCols)
                property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % ConfigOptions.overview.numOfCols
                x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex
                y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex
                z: root.windowZ
                width: Math.max(100, root.workspaceImplicitWidth)
                height: Math.max(60, root.workspaceImplicitHeight)
                color: "transparent"
                radius: Appearance.rounding.screenRounding * root.scale
                border.width: 2
                border.color: root.activeBorderColor
                visible: width > 0 && height > 0 && activeWorkspaceInGroup > 0
                Behavior on x {
                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
                }
                Behavior on y {
                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
                }
            }
        }
    }
}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage