299 lines
15 KiB
QML
299 lines
15 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import Quickshell.Hyprland
|
|
import qs.Config
|
|
import qs.Modules
|
|
import qs.Components
|
|
import qs.Effects
|
|
|
|
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.overview.rows * Config.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.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: []
|
|
|
|
ShadowRect {
|
|
anchors.fill: 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.overview.rows
|
|
delegate: RowLayout {
|
|
id: row
|
|
property int rowIndex: index
|
|
spacing: workspaceSpacing
|
|
|
|
Repeater { // Workspace repeater
|
|
model: Config.overview.columns
|
|
Rectangle { // Workspace
|
|
id: workspace
|
|
property int colIndex: index
|
|
property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * Config.overview.columns + colIndex + 1
|
|
property color defaultWorkspaceColor: DynamicColors.tPalette.m3surfaceContainerLow
|
|
property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1)
|
|
property color hoveredBorderColor: DynamicColors.tPalette.m3surfaceContainer
|
|
property bool hoveredWhileDragging: false
|
|
|
|
implicitWidth: root.workspaceImplicitWidth
|
|
implicitHeight: root.workspaceImplicitHeight
|
|
color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor
|
|
radius: 8
|
|
border.width: 2
|
|
border.color: hoveredWhileDragging ? hoveredBorderColor : "transparent"
|
|
|
|
CustomText {
|
|
anchors.centerIn: parent
|
|
text: workspaceValue
|
|
color: DynamicColor.tPalette.m3onSurfaceVariant
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
|
|
MouseArea {
|
|
id: workspaceArea
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton
|
|
onClicked: {
|
|
if (root.draggingTargetWorkspace === -1) {
|
|
root.panelWindow.visible = 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 var overviewRoot: root.panelWindow
|
|
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.overview.columns
|
|
property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / Config.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
|
|
}
|
|
}
|
|
|
|
CustomTooltip {
|
|
extraVisibleCondition: false
|
|
alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active
|
|
text: `${windowData?.title ?? "Unknown"}\n[${windowData?.class ?? "unknown"}] ${windowData?.xwayland ? "[XWayland] " : ""}`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: focusedWorkspaceIndicator
|
|
property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown)
|
|
property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / Config.overview.columns)
|
|
property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % Config.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 {
|
|
Anim {}
|
|
}
|
|
Behavior on y {
|
|
Anim {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|