1216 lines
68 KiB
QML
1216 lines
68 KiB
QML
import QtQuick
|
|
import QtQuick.Effects
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import Quickshell.Hyprland
|
|
import "../../common"
|
|
import "../../common/functions"
|
|
import "../../common/widgets"
|
|
import "../../services"
|
|
import "../.."
|
|
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 effectiveActiveWorkspaceId: Math.max(1, Math.min(100, monitor?.activeWorkspace?.id ?? 1))
|
|
readonly property int workspacesShown: Config.options.overview.rows * Config.options.overview.columns
|
|
readonly property bool useWorkspaceMap: Config.options.overview.useWorkspaceMap
|
|
readonly property var workspaceMap: Config.options.overview.workspaceMap
|
|
readonly property int workspaceOffset: useWorkspaceMap ? Number(workspaceMap[root.monitor?.id] ?? 0) : 0
|
|
readonly property int workspaceGroup: Math.floor((effectiveActiveWorkspaceId - workspaceOffset - 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 workspaceIds: HyprlandData.workspaceIds
|
|
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: Math.round((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: Math.round((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: Config.options.overview.workspaceNumberBaseSize * monitor.scale
|
|
property int workspaceZ: 0
|
|
property int windowZ: 1
|
|
property int windowDraggingZ: 99999
|
|
property real workspaceSpacing: Config.options.overview.workspaceSpacing
|
|
property string emptyWorkspaceWallpaperPath: Config.options.overview.emptyWorkspaceWallpaper
|
|
property string specialEmptyWorkspaceWallpaperPath: Config.options.overview.specialEmptyWorkspaceWallpaper
|
|
property bool showSpecialWorkspaces: Config.options.overview.showSpecialWorkspaces
|
|
property var configuredSpecialWorkspaces: Config.options.overview.specialWorkspaces ?? []
|
|
property int specialWorkspaceColumns: Math.max(1, Config.options.overview.specialWorkspaceColumns)
|
|
property real panelOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.panelOpacity))
|
|
property real workspaceOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.workspaceOpacity))
|
|
property real emptyWorkspaceWallpaperOverlayOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.emptyWorkspaceWallpaperOverlayOpacity))
|
|
property bool glassMode: Config.options.overview.effects.glassMode
|
|
property real glassTintStrength: Math.max(0, Math.min(1, Config.options.overview.effects.glassTintStrength))
|
|
property real glassBorderOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.glassBorderOpacity))
|
|
property real glassShineOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.glassShineOpacity))
|
|
property real effectivePanelOpacity: glassMode ? Math.min(panelOpacity, 0.72) : panelOpacity
|
|
property real effectiveWorkspaceOpacity: glassMode ? Math.min(workspaceOpacity, 0.62) : workspaceOpacity
|
|
|
|
property int draggingFromWorkspace: -1
|
|
property int draggingTargetWorkspace: -1
|
|
property string draggingTargetSpecialWorkspace: ""
|
|
property int previewRecaptureToken: 0
|
|
property var allWorkspaces: HyprlandData.allWorkspaces
|
|
property bool previewsEnabled: Config.options.overview.previewsEnabled
|
|
property string previewModeRaw: Config.options.overview.previewMode
|
|
property string previewMode: {
|
|
const mode = `${previewModeRaw ?? "live"}`.trim().toLowerCase();
|
|
return (mode === "event" || mode === "snapshot") ? "event" : "live";
|
|
}
|
|
property bool useEventPreviewRefresh: previewsEnabled && previewMode === "event"
|
|
|
|
readonly property var monitorSpecialWorkspaceNames: {
|
|
const names = [];
|
|
for (const ws of (allWorkspaces ?? [])) {
|
|
const name = `${ws?.name ?? ""}`;
|
|
if (!name.startsWith("special:"))
|
|
continue;
|
|
if (`${ws?.monitor ?? ""}` !== `${root.monitor?.name ?? ""}`)
|
|
continue;
|
|
names.push(name.slice(8));
|
|
}
|
|
return names;
|
|
}
|
|
|
|
readonly property var specialWorkspaceNamesFromWindows: {
|
|
const names = [];
|
|
for (const addr in windowByAddress) {
|
|
const win = windowByAddress[addr];
|
|
if ((win?.monitor ?? -1) !== (root.monitor?.id ?? -1))
|
|
continue;
|
|
const wsName = `${win?.workspace?.name ?? ""}`;
|
|
if (!wsName.startsWith("special:"))
|
|
continue;
|
|
names.push(wsName.slice(8));
|
|
}
|
|
return names;
|
|
}
|
|
|
|
readonly property var visibleSpecialWorkspaces: {
|
|
if (!showSpecialWorkspaces)
|
|
return [];
|
|
|
|
const out = [];
|
|
const pushUnique = (value) => {
|
|
const cleaned = `${value ?? ""}`.trim();
|
|
if (cleaned.length === 0 || out.includes(cleaned))
|
|
return;
|
|
out.push(cleaned);
|
|
};
|
|
|
|
for (const configured of configuredSpecialWorkspaces ?? [])
|
|
pushUnique(configured);
|
|
for (const name of monitorSpecialWorkspaceNames)
|
|
pushUnique(name);
|
|
for (const name of specialWorkspaceNamesFromWindows)
|
|
pushUnique(name);
|
|
|
|
return out;
|
|
}
|
|
|
|
readonly property bool hasSpecialWorkspaceSection: visibleSpecialWorkspaces.length > 0
|
|
readonly property bool hasEmptyWorkspaceWallpaper: `${emptyWorkspaceWallpaperPath ?? ""}`.trim().length > 0
|
|
readonly property bool hasSpecialEmptyWorkspaceWallpaper: `${specialEmptyWorkspaceWallpaperPath ?? ""}`.trim().length > 0
|
|
readonly property string createSpecialWorkspaceTarget: "__create_special_workspace__"
|
|
readonly property real specialWorkspaceTileHeight: root.workspaceImplicitHeight
|
|
readonly property real specialStripGap: workspaceSpacing * 1.8
|
|
readonly property real specialStripPadding: Math.max(8, 12 * root.scale)
|
|
readonly property real specialStripTitleHeight: Math.max(14, Appearance.font.pixelSize.small * root.scale)
|
|
readonly property real specialStripTitleGap: Math.max(6, 8 * root.scale)
|
|
readonly property int totalSpecialTiles: visibleSpecialWorkspaces.length + 1
|
|
readonly property real specialSectionWidth: workspaceColumnLayout.implicitWidth
|
|
readonly property real specialGridInnerWidth: Math.max(0, root.specialSectionWidth - root.specialStripPadding * 2)
|
|
readonly property int effectiveSpecialColumns: Math.max(1, Math.min(root.specialWorkspaceColumns, root.totalSpecialTiles))
|
|
readonly property int specialWorkspaceRows: Math.ceil(root.totalSpecialTiles / root.effectiveSpecialColumns)
|
|
readonly property real specialWorkspaceAspectCap: {
|
|
let maxAspect = Math.max(1, root.workspaceImplicitWidth / Math.max(1, root.workspaceImplicitHeight));
|
|
for (const name of visibleSpecialWorkspaces) {
|
|
const geometry = root.specialWorkspaceGeometry(name, root.monitor?.id);
|
|
const width = geometry?.width;
|
|
const height = geometry?.height;
|
|
if (!Number.isFinite(width) || !Number.isFinite(height) || height <= 0)
|
|
continue;
|
|
maxAspect = Math.max(maxAspect, width / height);
|
|
}
|
|
return maxAspect;
|
|
}
|
|
readonly property real specialWorkspaceTileWidth: {
|
|
const gaps = Math.max(0, root.effectiveSpecialColumns - 1);
|
|
const rawWidth = (root.specialGridInnerWidth - gaps * workspaceSpacing) / root.effectiveSpecialColumns;
|
|
const aspectWidth = root.specialWorkspaceTileHeight * root.specialWorkspaceAspectCap;
|
|
const cappedWidth = Math.min(rawWidth, aspectWidth);
|
|
return Math.max(80 * root.scale, cappedWidth);
|
|
}
|
|
readonly property real specialGridUsedWidth: root.effectiveSpecialColumns * root.specialWorkspaceTileWidth + Math.max(0, root.effectiveSpecialColumns - 1) * workspaceSpacing
|
|
readonly property real specialGridOffsetX: root.specialStripPadding + Math.max(0, (root.specialGridInnerWidth - root.specialGridUsedWidth) / 2)
|
|
readonly property real specialStripTop: workspaceColumnLayout.implicitHeight + workspaceSpacing + root.specialStripGap
|
|
readonly property real specialStripTilesTop: root.specialStripTop + root.specialStripPadding + root.specialStripTitleHeight + root.specialStripTitleGap
|
|
readonly property real specialGridHeight: root.specialWorkspaceRows * root.specialWorkspaceTileHeight + Math.max(0, root.specialWorkspaceRows - 1) * workspaceSpacing
|
|
readonly property real specialStripHeight: root.specialStripPadding * 2 + root.specialStripTitleHeight + root.specialStripTitleGap + root.specialGridHeight
|
|
|
|
function getWorkspaceRow(workspaceId) {
|
|
if (!Number.isFinite(workspaceId))
|
|
return 0;
|
|
const adjusted = workspaceId - workspaceOffset;
|
|
const normalRow = Math.floor((adjusted - 1) / Config.options.overview.columns) % Config.options.overview.rows;
|
|
return Config.options.overview.orderBottomUp ? (Config.options.overview.rows - normalRow - 1) : normalRow;
|
|
}
|
|
|
|
function getWorkspaceColumn(workspaceId) {
|
|
if (!Number.isFinite(workspaceId))
|
|
return 0;
|
|
const adjusted = workspaceId - workspaceOffset;
|
|
const normalCol = (adjusted - 1) % Config.options.overview.columns;
|
|
return Config.options.overview.orderRightLeft ? (Config.options.overview.columns - normalCol - 1) : normalCol;
|
|
}
|
|
|
|
function getWorkspaceInCell(rowIndex, colIndex) {
|
|
const mappedRow = Config.options.overview.orderBottomUp ? (Config.options.overview.rows - rowIndex - 1) : rowIndex;
|
|
const mappedCol = Config.options.overview.orderRightLeft ? (Config.options.overview.columns - colIndex - 1) : colIndex;
|
|
return (workspaceGroup * workspacesShown) + (mappedRow * Config.options.overview.columns) + mappedCol + 1 + workspaceOffset;
|
|
}
|
|
|
|
function getVisibleRowPosition(rowIndex) {
|
|
if (!Number.isFinite(rowIndex) || rowIndex < 0)
|
|
return 0;
|
|
if (!Config.options.overview.hideEmptyRows || !(rowsWithContent instanceof Set))
|
|
return rowIndex;
|
|
|
|
let visibleRow = 0;
|
|
for (let i = 0; i < rowIndex; i += 1) {
|
|
if (rowsWithContent.has(i))
|
|
visibleRow += 1;
|
|
}
|
|
return visibleRow;
|
|
}
|
|
|
|
function stepWorkspace(delta) {
|
|
if (!Number.isFinite(delta) || delta === 0)
|
|
return;
|
|
|
|
const currentId = monitor?.activeWorkspace?.id ?? effectiveActiveWorkspaceId;
|
|
const minWorkspaceId = workspaceOffset + 1;
|
|
let maxWorkspaceId = minWorkspaceId + workspacesShown - 1;
|
|
for (const workspaceId of (workspaceIds ?? [])) {
|
|
if (Number.isFinite(workspaceId) && workspaceId >= minWorkspaceId) {
|
|
maxWorkspaceId = Math.max(maxWorkspaceId, workspaceId);
|
|
}
|
|
}
|
|
maxWorkspaceId = Math.max(maxWorkspaceId, currentId);
|
|
|
|
let targetId = currentId + delta;
|
|
if (targetId < minWorkspaceId) {
|
|
targetId = maxWorkspaceId;
|
|
} else if (targetId > maxWorkspaceId) {
|
|
targetId = minWorkspaceId;
|
|
}
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.focus({workspace = '${targetId}'})`);
|
|
} else {
|
|
Hyprland.dispatch(`workspace ${targetId}`);
|
|
}
|
|
}
|
|
|
|
function isSpecialWorkspace(windowData) {
|
|
const wsName = `${windowData?.workspace?.name ?? ""}`;
|
|
return wsName.startsWith("special:");
|
|
}
|
|
|
|
function specialWorkspaceName(windowData) {
|
|
const wsName = `${windowData?.workspace?.name ?? ""}`;
|
|
return wsName.startsWith("special:") ? wsName.slice(8) : "";
|
|
}
|
|
|
|
function specialWorkspaceIndex(name) {
|
|
return visibleSpecialWorkspaces.indexOf(`${name ?? ""}`);
|
|
}
|
|
|
|
function specialWorkspaceLabel(name) {
|
|
const raw = `${name ?? ""}`.trim();
|
|
if (raw.length === 0)
|
|
return "Special";
|
|
return raw.replace(/[-_]+/g, " ");
|
|
}
|
|
|
|
function nextSpecialWorkspaceName() {
|
|
const taken = new Set();
|
|
for (const name of visibleSpecialWorkspaces)
|
|
taken.add(`${name ?? ""}`.trim().toLowerCase());
|
|
|
|
const base = "stash";
|
|
if (!taken.has(base))
|
|
return base;
|
|
|
|
let index = 2;
|
|
while (taken.has(`${base}-${index}`))
|
|
index += 1;
|
|
|
|
return `${base}-${index}`;
|
|
}
|
|
|
|
function wallpaperSource(path) {
|
|
const trimmed = `${path ?? ""}`.trim();
|
|
if (trimmed.length === 0)
|
|
return "";
|
|
if (trimmed.startsWith("file:/") || trimmed.startsWith("qrc:/") || trimmed.startsWith("image://") || trimmed.startsWith("http://") || trimmed.startsWith("https://"))
|
|
return trimmed;
|
|
if (trimmed.startsWith("/"))
|
|
return `file://${trimmed}`;
|
|
return trimmed;
|
|
}
|
|
|
|
function workspaceHasWindows(workspaceId) {
|
|
if (!Number.isFinite(workspaceId))
|
|
return false;
|
|
|
|
for (const addr in windowByAddress) {
|
|
const win = windowByAddress[addr];
|
|
if (root.isSpecialWorkspace(win))
|
|
continue;
|
|
if ((win?.workspace?.id ?? -1) === workspaceId)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function specialWindowZ(win) {
|
|
const pinned = win?.pinned ? 200000 : 0;
|
|
const floating = win?.floating ? 100000 : 0;
|
|
const focus = 10000 - (win?.focusHistoryID ?? 9999);
|
|
return pinned + floating + focus;
|
|
}
|
|
|
|
function specialWorkspaceGeometry(name, monitorId) {
|
|
const trimmedName = `${name ?? ""}`.trim();
|
|
const currentMonitorId = monitorId ?? -1;
|
|
let minX = null;
|
|
let minY = null;
|
|
let maxX = null;
|
|
let maxY = null;
|
|
|
|
for (const addr in windowByAddress) {
|
|
const win = windowByAddress[addr];
|
|
if ((win?.monitor ?? -1) !== currentMonitorId)
|
|
continue;
|
|
if (root.specialWorkspaceName(win) !== trimmedName)
|
|
continue;
|
|
|
|
const atX = win?.at?.[0];
|
|
const atY = win?.at?.[1];
|
|
const width = win?.size?.[0];
|
|
const height = win?.size?.[1];
|
|
if (!Number.isFinite(atX) || !Number.isFinite(atY))
|
|
continue;
|
|
if (!Number.isFinite(width) || !Number.isFinite(height))
|
|
continue;
|
|
|
|
minX = minX === null ? atX : Math.min(minX, atX);
|
|
minY = minY === null ? atY : Math.min(minY, atY);
|
|
maxX = maxX === null ? (atX + width) : Math.max(maxX, atX + width);
|
|
maxY = maxY === null ? (atY + height) : Math.max(maxY, atY + height);
|
|
}
|
|
|
|
return {
|
|
x: minX,
|
|
y: minY,
|
|
width: (minX !== null && maxX !== null) ? Math.max(1, maxX - minX) : null,
|
|
height: (minY !== null && maxY !== null) ? Math.max(1, maxY - minY) : null
|
|
};
|
|
}
|
|
|
|
// Calculate which rows have windows or current workspace
|
|
property var rowsWithContent: {
|
|
if (!Config.options.overview.hideEmptyRows) return null;
|
|
|
|
let rows = new Set();
|
|
const firstWorkspace = root.workspaceGroup * root.workspacesShown + 1 + workspaceOffset;
|
|
const lastWorkspace = (root.workspaceGroup + 1) * root.workspacesShown + workspaceOffset;
|
|
|
|
// Add row containing current workspace
|
|
const currentWorkspace = effectiveActiveWorkspaceId;
|
|
if (currentWorkspace >= firstWorkspace && currentWorkspace <= lastWorkspace) {
|
|
rows.add(getWorkspaceRow(currentWorkspace));
|
|
}
|
|
|
|
// Add rows with windows
|
|
for (let addr in windowByAddress) {
|
|
const win = windowByAddress[addr];
|
|
const wsId = win?.workspace?.id;
|
|
if (wsId >= firstWorkspace && wsId <= lastWorkspace) {
|
|
const rowIndex = getWorkspaceRow(wsId);
|
|
rows.add(rowIndex);
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
|
|
implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2
|
|
|
|
property Component windowComponent: OverviewWindow {}
|
|
property list<OverviewWindow> windowWidgets: []
|
|
|
|
Connections {
|
|
target: Hyprland
|
|
function onRawEvent(event) {
|
|
if (!GlobalStates.overviewOpen || !root.useEventPreviewRefresh)
|
|
return;
|
|
|
|
const eventName = `${event?.name ?? event?.event ?? event?.type ?? ""}`;
|
|
if (eventName === "closewindow" || eventName === "openwindow" || eventName === "movewindow") {
|
|
root.previewRecaptureToken += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledRectangularShadow {
|
|
target: overviewBackground
|
|
}
|
|
Rectangle { // Background
|
|
id: overviewBackground
|
|
property real padding: Config.options.overview.backgroundPadding
|
|
anchors.fill: parent
|
|
anchors.margins: Appearance.sizes.elevationMargin
|
|
|
|
implicitWidth: contentLayout.implicitWidth + padding * 2
|
|
implicitHeight: contentLayout.implicitHeight + padding * 2
|
|
radius: Appearance.rounding.screenRounding * root.scale + (Appearance.rounding.screenRounding !== 0 ? padding : 0)
|
|
clip: true
|
|
color: ColorUtils.applyAlpha(
|
|
root.glassMode
|
|
? ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colLayer1, 0.78 - root.glassTintStrength * 0.35)
|
|
: Appearance.colors.colLayer0,
|
|
root.effectivePanelOpacity
|
|
)
|
|
border.width: 1
|
|
border.color: ColorUtils.applyAlpha(
|
|
root.glassMode
|
|
? ColorUtils.mix(Appearance.colors.colLayer0Border, Appearance.m3colors.m3outline, 0.52)
|
|
: Appearance.colors.colLayer0Border,
|
|
root.glassMode ? root.glassBorderOpacity : root.effectivePanelOpacity
|
|
)
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
onPressed: mouse => mouse.accepted = true
|
|
}
|
|
|
|
Rectangle {
|
|
visible: root.glassMode
|
|
anchors.fill: parent
|
|
radius: parent.radius
|
|
color: "transparent"
|
|
gradient: Gradient {
|
|
GradientStop { position: 0.0; color: ColorUtils.applyAlpha("#FFFFFF", root.glassShineOpacity * 0.35) }
|
|
GradientStop { position: 0.42; color: ColorUtils.applyAlpha("#FFFFFF", 0.0) }
|
|
GradientStop { position: 1.0; color: ColorUtils.applyAlpha("#000000", root.glassShineOpacity * 0.22) }
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
visible: root.glassMode
|
|
anchors.fill: parent
|
|
anchors.margins: 1
|
|
radius: Math.max(parent.radius - 1, 0)
|
|
color: "transparent"
|
|
border.width: 1
|
|
border.color: ColorUtils.applyAlpha("#FFFFFF", root.glassBorderOpacity * 0.20)
|
|
}
|
|
|
|
ColumnLayout { // Workspaces
|
|
id: contentLayout
|
|
|
|
z: root.workspaceZ
|
|
anchors.centerIn: parent
|
|
spacing: workspaceSpacing
|
|
ColumnLayout {
|
|
id: workspaceColumnLayout
|
|
spacing: workspaceSpacing
|
|
|
|
Repeater {
|
|
model: Config.options.overview.rows
|
|
delegate: RowLayout {
|
|
id: row
|
|
property int rowIndex: index
|
|
spacing: workspaceSpacing
|
|
visible: !Config.options.overview.hideEmptyRows ||
|
|
(root.rowsWithContent && root.rowsWithContent.has(rowIndex))
|
|
height: visible ? implicitHeight : 0
|
|
|
|
Repeater { // Workspace repeater
|
|
model: Config.options.overview.columns
|
|
Rectangle { // Workspace
|
|
id: workspace
|
|
property int colIndex: index
|
|
property int workspaceValue: root.getWorkspaceInCell(rowIndex, colIndex)
|
|
property bool showWallpaper: root.hasEmptyWorkspaceWallpaper
|
|
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: showWallpaper ? "transparent" : ColorUtils.applyAlpha(
|
|
root.glassMode
|
|
? ColorUtils.mix(hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor, Appearance.colors.colLayer0, 0.46)
|
|
: (hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor),
|
|
root.effectiveWorkspaceOpacity
|
|
)
|
|
radius: Appearance.rounding.screenRounding * root.scale
|
|
border.width: 2
|
|
border.color: hoveredWhileDragging
|
|
? ColorUtils.applyAlpha(hoveredBorderColor, root.glassMode ? root.glassBorderOpacity : 1)
|
|
: "transparent"
|
|
|
|
Image {
|
|
id: workspaceWallpaper
|
|
visible: workspace.showWallpaper
|
|
anchors.fill: parent
|
|
source: root.wallpaperSource(root.emptyWorkspaceWallpaperPath)
|
|
fillMode: Image.PreserveAspectCrop
|
|
asynchronous: true
|
|
cache: true
|
|
smooth: true
|
|
mipmap: true
|
|
layer.enabled: workspace.showWallpaper
|
|
layer.smooth: true
|
|
layer.effect: MultiEffect {
|
|
maskEnabled: true
|
|
maskSource: workspaceWallpaperMask
|
|
maskThresholdMin: 0.5
|
|
maskSpreadAtMin: 1.0
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: workspaceWallpaperMask
|
|
anchors.fill: parent
|
|
visible: false
|
|
layer.enabled: true
|
|
layer.smooth: true
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: workspace.radius
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
visible: workspace.showWallpaper
|
|
anchors.fill: parent
|
|
radius: parent.radius
|
|
color: ColorUtils.applyAlpha(
|
|
root.glassMode
|
|
? ColorUtils.mix(workspace.hoveredWhileDragging ? workspace.hoveredWorkspaceColor : workspace.defaultWorkspaceColor, Appearance.colors.colLayer0, 0.40)
|
|
: (workspace.hoveredWhileDragging ? workspace.hoveredWorkspaceColor : workspace.defaultWorkspaceColor),
|
|
workspace.hoveredWhileDragging
|
|
? Math.min(0.28, root.emptyWorkspaceWallpaperOverlayOpacity + 0.08)
|
|
: root.emptyWorkspaceWallpaperOverlayOpacity
|
|
)
|
|
}
|
|
|
|
Rectangle {
|
|
visible: root.glassMode
|
|
anchors.fill: parent
|
|
radius: parent.radius
|
|
color: "transparent"
|
|
gradient: Gradient {
|
|
GradientStop { position: 0.0; color: ColorUtils.applyAlpha("#FFFFFF", root.glassShineOpacity * 0.22) }
|
|
GradientStop { position: 0.46; color: ColorUtils.applyAlpha("#FFFFFF", 0.0) }
|
|
GradientStop { position: 1.0; color: ColorUtils.applyAlpha("#000000", root.glassShineOpacity * 0.14) }
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
visible: root.glassMode
|
|
anchors.fill: parent
|
|
anchors.margins: 1
|
|
radius: Math.max(parent.radius - 1, 0)
|
|
color: "transparent"
|
|
border.width: 1
|
|
border.color: ColorUtils.applyAlpha("#FFFFFF", root.glassBorderOpacity * 0.16)
|
|
}
|
|
|
|
StyledText {
|
|
anchors.centerIn: parent
|
|
visible: !workspace.showWallpaper
|
|
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
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.focus({workspace = '${workspaceValue}'})`);
|
|
} else {
|
|
Hyprland.dispatch(`workspace ${workspaceValue}`)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DropArea {
|
|
anchors.fill: parent
|
|
onEntered: {
|
|
root.draggingTargetWorkspace = workspaceValue
|
|
root.draggingTargetSpecialWorkspace = ""
|
|
if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return;
|
|
hoveredWhileDragging = true
|
|
}
|
|
onExited: {
|
|
hoveredWhileDragging = false
|
|
if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
visible: root.showSpecialWorkspaces
|
|
implicitWidth: 1
|
|
implicitHeight: root.specialStripGap
|
|
}
|
|
|
|
Item {
|
|
id: specialWorkspaceSection
|
|
visible: root.showSpecialWorkspaces
|
|
implicitWidth: root.specialSectionWidth
|
|
implicitHeight: root.specialStripHeight
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Appearance.rounding.normal * root.scale
|
|
color: ColorUtils.applyAlpha(
|
|
root.glassMode
|
|
? ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colLayer1, 0.70)
|
|
: Appearance.colors.colLayer1,
|
|
root.glassMode ? Math.min(0.74, root.effectivePanelOpacity) : root.effectiveWorkspaceOpacity
|
|
)
|
|
border.width: 1
|
|
border.color: ColorUtils.applyAlpha(Appearance.colors.colLayer2Border, root.glassMode ? root.glassBorderOpacity : 0.65)
|
|
|
|
Rectangle {
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
height: Math.max(18 * root.scale, root.specialStripPadding + root.specialStripTitleHeight * 0.8)
|
|
radius: parent.radius
|
|
color: ColorUtils.applyAlpha(Appearance.colors.colSecondary, root.glassMode ? 0.12 : 0.08)
|
|
}
|
|
|
|
StyledText {
|
|
anchors.left: parent.left
|
|
anchors.top: parent.top
|
|
anchors.leftMargin: root.specialStripPadding
|
|
anchors.topMargin: root.specialStripPadding
|
|
text: "Special Workspaces"
|
|
font.family: Appearance.font.family.title
|
|
font.pixelSize: root.specialStripTitleHeight
|
|
font.weight: Font.DemiBold
|
|
color: ColorUtils.applyAlpha(Appearance.colors.colOnLayer1, 0.84)
|
|
}
|
|
|
|
Grid {
|
|
id: specialWorkspaceGrid
|
|
x: root.specialGridOffsetX
|
|
y: root.specialStripPadding + root.specialStripTitleHeight + root.specialStripTitleGap
|
|
width: root.specialGridUsedWidth
|
|
columns: root.effectiveSpecialColumns
|
|
rowSpacing: workspaceSpacing
|
|
columnSpacing: workspaceSpacing
|
|
|
|
Repeater {
|
|
model: root.visibleSpecialWorkspaces
|
|
delegate: Rectangle {
|
|
id: specialWorkspaceTile
|
|
required property string modelData
|
|
property string specialName: modelData
|
|
property var specialGeometry: root.specialWorkspaceGeometry(specialName, root.monitor?.id)
|
|
property color baseColor: ColorUtils.mix(Appearance.colors.colLayer1, Appearance.colors.colLayer0, 0.52)
|
|
property bool hasRenderableGeometry: Number.isFinite(specialGeometry?.width)
|
|
&& Number.isFinite(specialGeometry?.height)
|
|
&& specialGeometry.width > 0
|
|
&& specialGeometry.height > 0
|
|
property bool showWallpaper: root.hasSpecialEmptyWorkspaceWallpaper
|
|
property real geometryWidth: hasRenderableGeometry ? specialGeometry.width : Math.max(1, root.workspaceImplicitWidth / root.scale)
|
|
property real geometryHeight: hasRenderableGeometry ? specialGeometry.height : Math.max(1, root.workspaceImplicitHeight / root.scale)
|
|
property real fitScale: hasRenderableGeometry ? Math.min(width / geometryWidth, height / geometryHeight) : root.scale
|
|
property real contentWidth: hasRenderableGeometry ? (geometryWidth * fitScale) : width
|
|
property real contentHeight: hasRenderableGeometry ? (geometryHeight * fitScale) : height
|
|
property real contentOffsetX: Math.max(0, (width - contentWidth) / 2)
|
|
property real contentOffsetY: Math.max(0, (height - contentHeight) / 2)
|
|
implicitWidth: root.specialWorkspaceTileWidth
|
|
implicitHeight: root.specialWorkspaceTileHeight
|
|
radius: Appearance.rounding.screenRounding * root.scale
|
|
clip: true
|
|
color: showWallpaper ? "transparent" : ColorUtils.applyAlpha(
|
|
root.glassMode
|
|
? ColorUtils.mix(baseColor, Appearance.colors.colLayer0, 0.44)
|
|
: baseColor,
|
|
root.effectiveWorkspaceOpacity
|
|
)
|
|
border.width: 1
|
|
border.color: ColorUtils.applyAlpha(Appearance.colors.colLayer2Border, root.glassMode ? root.glassBorderOpacity : 0.75)
|
|
|
|
Image {
|
|
visible: specialWorkspaceTile.showWallpaper
|
|
anchors.fill: parent
|
|
source: root.wallpaperSource(root.specialEmptyWorkspaceWallpaperPath)
|
|
fillMode: Image.PreserveAspectCrop
|
|
asynchronous: true
|
|
cache: true
|
|
smooth: true
|
|
mipmap: true
|
|
}
|
|
|
|
Rectangle {
|
|
visible: specialWorkspaceTile.showWallpaper
|
|
anchors.fill: parent
|
|
radius: parent.radius
|
|
color: ColorUtils.applyAlpha(
|
|
root.glassMode
|
|
? ColorUtils.mix(specialWorkspaceTile.baseColor, Appearance.colors.colLayer0, 0.40)
|
|
: specialWorkspaceTile.baseColor,
|
|
root.emptyWorkspaceWallpaperOverlayOpacity
|
|
)
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton
|
|
onClicked: {
|
|
if (root.draggingTargetWorkspace === -1 && !root.draggingTargetSpecialWorkspace) {
|
|
GlobalStates.overviewOpen = false;
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.workspace.toggle_special('${specialWorkspaceTile.specialName}')`);
|
|
} else {
|
|
Hyprland.dispatch(`togglespecialworkspace ${specialWorkspaceTile.specialName}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DropArea {
|
|
anchors.fill: parent
|
|
onEntered: {
|
|
root.draggingTargetWorkspace = -1;
|
|
root.draggingTargetSpecialWorkspace = specialWorkspaceTile.specialName;
|
|
}
|
|
onExited: {
|
|
if (root.draggingTargetSpecialWorkspace === specialWorkspaceTile.specialName)
|
|
root.draggingTargetSpecialWorkspace = "";
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: specialWorkspaceContent
|
|
x: specialWorkspaceTile.contentOffsetX
|
|
y: specialWorkspaceTile.contentOffsetY
|
|
width: specialWorkspaceTile.contentWidth
|
|
height: specialWorkspaceTile.contentHeight
|
|
clip: true
|
|
|
|
Repeater {
|
|
model: ScriptModel {
|
|
values: {
|
|
if (!specialWorkspaceTile.hasRenderableGeometry)
|
|
return [];
|
|
return ToplevelManager.toplevels.values.filter((toplevel) => {
|
|
const address = `0x${toplevel.HyprlandToplevel.address}`;
|
|
const win = windowByAddress[address];
|
|
if ((win?.monitor ?? -1) !== (root.monitor?.id ?? -1))
|
|
return false;
|
|
return root.specialWorkspaceName(win) === specialWorkspaceTile.specialName;
|
|
}).sort((a, b) => {
|
|
const addrA = `0x${a.HyprlandToplevel.address}`;
|
|
const addrB = `0x${b.HyprlandToplevel.address}`;
|
|
return addrA.localeCompare(addrB);
|
|
});
|
|
}
|
|
}
|
|
delegate: OverviewWindow {
|
|
id: specialWindow
|
|
required property var modelData
|
|
required property int index
|
|
property var address: `0x${modelData.HyprlandToplevel.address}`
|
|
property int monitorId: windowData?.monitor
|
|
property var monitor: HyprlandData.monitors.find(m => m.id === monitorId)
|
|
property Item homeParent: specialWorkspaceContent
|
|
windowData: windowByAddress[address]
|
|
toplevel: modelData
|
|
monitorData: monitor
|
|
widgetMonitorData: root.monitorData
|
|
scale: root.scale
|
|
availableWorkspaceWidth: specialWorkspaceContent.width
|
|
availableWorkspaceHeight: specialWorkspaceContent.height
|
|
positionBaseX: Number.isFinite(specialWorkspaceTile.specialGeometry?.x) ? specialWorkspaceTile.specialGeometry.x : ((monitor?.x ?? 0) + (monitor?.reserved?.[0] ?? 0))
|
|
positionBaseY: Number.isFinite(specialWorkspaceTile.specialGeometry?.y) ? specialWorkspaceTile.specialGeometry.y : ((monitor?.y ?? 0) + (monitor?.reserved?.[1] ?? 0))
|
|
geometryScaleX: specialWorkspaceTile.fitScale / root.scale
|
|
geometryScaleY: specialWorkspaceTile.fitScale / root.scale
|
|
xOffset: 0
|
|
yOffset: 0
|
|
widgetMonitorId: root.monitor.id
|
|
recaptureToken: root.previewRecaptureToken
|
|
restrictToWorkspace: false
|
|
animateSize: false
|
|
z: root.specialWindowZ(windowData)
|
|
|
|
function moveToDragLayer() {
|
|
const mapped = specialWindow.mapToItem(specialWindowDragLayer, 0, 0);
|
|
specialWindow.suspendPositionAnimation = true;
|
|
specialWindow.parent = specialWindowDragLayer;
|
|
specialWindow.x = mapped.x;
|
|
specialWindow.y = mapped.y;
|
|
specialWindow.z = root.windowDraggingZ + 1;
|
|
Qt.callLater(() => specialWindow.suspendPositionAnimation = false);
|
|
}
|
|
|
|
function returnToHomeParent() {
|
|
specialWindow.suspendPositionAnimation = true;
|
|
specialWindow.parent = homeParent;
|
|
specialWindow.z = root.specialWindowZ(windowData);
|
|
Qt.callLater(() => specialWindow.suspendPositionAnimation = false);
|
|
}
|
|
|
|
MouseArea {
|
|
id: specialDragArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
onEntered: hovered = true
|
|
onExited: hovered = false
|
|
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
|
drag.target: parent
|
|
onPressed: (mouse) => {
|
|
root.draggingFromWorkspace = -1
|
|
root.draggingTargetSpecialWorkspace = ""
|
|
specialWindow.pressed = true
|
|
specialWindow.dragInProgress = true
|
|
specialWindow.Drag.source = specialWindow
|
|
specialWindow.Drag.hotSpot.x = mouse.x
|
|
specialWindow.Drag.hotSpot.y = mouse.y
|
|
specialWindow.moveToDragLayer()
|
|
specialWindow.Drag.active = true
|
|
}
|
|
onReleased: {
|
|
const targetWorkspace = root.draggingTargetWorkspace
|
|
const targetSpecialWorkspace = root.draggingTargetSpecialWorkspace
|
|
specialWindow.pressed = false
|
|
specialWindow.Drag.active = false
|
|
specialWindow.dragInProgress = false
|
|
root.draggingFromWorkspace = -1
|
|
root.draggingTargetWorkspace = -1
|
|
root.draggingTargetSpecialWorkspace = ""
|
|
if (targetSpecialWorkspace === root.createSpecialWorkspaceTarget) {
|
|
const createdName = root.nextSpecialWorkspaceName()
|
|
//INFO: From special TO NEW special
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.window.move({workspace = 'special:${createdName}', follow = false, window = 'address:${specialWindow.windowData?.address}'})`);
|
|
} else {
|
|
Hyprland.dispatch(`movetoworkspacesilent special:${createdName}, address:${specialWindow.windowData?.address}`)
|
|
}
|
|
specialWindow.returnToHomeParent()
|
|
specialWindow.x = specialWindow.initX
|
|
specialWindow.y = specialWindow.initY
|
|
}
|
|
else if (targetSpecialWorkspace && targetSpecialWorkspace !== specialWorkspaceTile.specialName) {
|
|
// Idk
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.window.move({workspace = 'special:${targetSpecialWorkspace}', follow = false, window = 'address:${specialWindow.windowData?.address}'})`);
|
|
} else {
|
|
Hyprland.dispatch(`movetoworkspacesilent special:${targetSpecialWorkspace}, address:${specialWindow.windowData?.address}`)
|
|
}
|
|
specialWindow.returnToHomeParent()
|
|
specialWindow.x = specialWindow.initX
|
|
specialWindow.y = specialWindow.initY
|
|
}
|
|
else if (targetWorkspace !== -1) {
|
|
//INFO: From Special TO Normal workspace
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.window.move({workspace = '${targetWorkspace}', follow = false, window = 'address:${specialWindow.windowData?.address}'})`);
|
|
} else {
|
|
Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace}, address:${specialWindow.windowData?.address}`)
|
|
}
|
|
specialWindow.returnToHomeParent()
|
|
specialWindow.x = specialWindow.initX
|
|
specialWindow.y = specialWindow.initY
|
|
}
|
|
else {
|
|
specialWindow.returnToHomeParent()
|
|
specialWindow.x = specialWindow.initX
|
|
specialWindow.y = specialWindow.initY
|
|
}
|
|
}
|
|
onClicked: (event) => {
|
|
if (!windowData)
|
|
return;
|
|
if (event.button === Qt.LeftButton) {
|
|
GlobalStates.overviewOpen = false;
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.focus({ window = 'address:${windowData.address}' })`);
|
|
} else {
|
|
Hyprland.dispatch(`focuswindow address:${windowData.address}`);
|
|
}
|
|
event.accepted = true;
|
|
} else if (event.button === Qt.MiddleButton) {
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.window.close('address:${windowData.address}')`);
|
|
} else {
|
|
Hyprland.dispatch(`closewindow address:${windowData.address}`);
|
|
}
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
|
|
StyledToolTip {
|
|
extraVisibleCondition: false
|
|
alternativeVisibleCondition: specialDragArea.containsMouse && !specialWindow.Drag.active
|
|
text: `${windowData?.title ?? "Unknown"}\n[${windowData?.class ?? "unknown"}] ${windowData?.xwayland ? "[XWayland] " : ""}`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Rectangle {
|
|
id: createSpecialWorkspaceTile
|
|
property bool showWallpaper: root.hasSpecialEmptyWorkspaceWallpaper
|
|
implicitWidth: root.specialWorkspaceTileWidth
|
|
implicitHeight: root.specialWorkspaceTileHeight
|
|
radius: Appearance.rounding.screenRounding * root.scale
|
|
color: showWallpaper ? "transparent" : ColorUtils.applyAlpha(
|
|
root.glassMode
|
|
? ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer1, 0.58)
|
|
: ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer1, 0.55),
|
|
root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget ? 0.90 : root.effectiveWorkspaceOpacity
|
|
)
|
|
border.width: 1
|
|
border.color: root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget
|
|
? ColorUtils.applyAlpha(root.activeBorderColor, 0.96)
|
|
: ColorUtils.applyAlpha(Appearance.colors.colSecondary, 0.46)
|
|
|
|
Image {
|
|
visible: createSpecialWorkspaceTile.showWallpaper
|
|
anchors.fill: parent
|
|
source: root.wallpaperSource(root.specialEmptyWorkspaceWallpaperPath)
|
|
fillMode: Image.PreserveAspectCrop
|
|
asynchronous: true
|
|
cache: true
|
|
smooth: true
|
|
mipmap: true
|
|
}
|
|
|
|
Rectangle {
|
|
visible: createSpecialWorkspaceTile.showWallpaper
|
|
anchors.fill: parent
|
|
radius: parent.radius
|
|
color: ColorUtils.applyAlpha(
|
|
root.glassMode
|
|
? ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer0, 0.40)
|
|
: ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer1, 0.55),
|
|
root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget
|
|
? Math.min(0.28, root.emptyWorkspaceWallpaperOverlayOpacity + 0.08)
|
|
: root.emptyWorkspaceWallpaperOverlayOpacity
|
|
)
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
anchors.margins: 1
|
|
radius: Math.max(parent.radius - 1, 0)
|
|
color: "transparent"
|
|
border.width: 1
|
|
border.color: ColorUtils.applyAlpha("#FFFFFF", root.glassMode ? 0.12 : 0.08)
|
|
}
|
|
|
|
Column {
|
|
anchors.centerIn: parent
|
|
spacing: 0
|
|
|
|
StyledText {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
visible: !createSpecialWorkspaceTile.showWallpaper
|
|
text: root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget ? "Release" : "+"
|
|
font.family: Appearance.font.family.expressive
|
|
font.pixelSize: root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget
|
|
? Appearance.font.pixelSize.larger * root.scale
|
|
: Appearance.font.pixelSize.huge * 1.25 * root.scale
|
|
font.weight: Font.DemiBold
|
|
color: ColorUtils.applyAlpha(Appearance.colors.colOnLayer1, 0.92)
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton
|
|
onClicked: {
|
|
const createdName = root.nextSpecialWorkspaceName();
|
|
GlobalStates.overviewOpen = false;
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.workspace.toggle_special('${createdName}')`);
|
|
} else {
|
|
Hyprland.dispatch(`togglespecialworkspace ${createdName}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
DropArea {
|
|
anchors.fill: parent
|
|
onEntered: {
|
|
root.draggingTargetWorkspace = -1;
|
|
root.draggingTargetSpecialWorkspace = root.createSpecialWorkspaceTarget;
|
|
}
|
|
onExited: {
|
|
if (root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget)
|
|
root.draggingTargetSpecialWorkspace = "";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item { // Windows & focused workspace indicator
|
|
id: windowSpace
|
|
anchors.centerIn: parent
|
|
implicitWidth: contentLayout.implicitWidth
|
|
implicitHeight: contentLayout.implicitHeight
|
|
|
|
WheelHandler {
|
|
target: null
|
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
|
onWheel: event => {
|
|
const deltaY = event.angleDelta.y;
|
|
if (!deltaY)
|
|
return;
|
|
root.stepWorkspace(deltaY > 0 ? -1 : 1);
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
|
|
Repeater { // Window repeater
|
|
model: ScriptModel {
|
|
values: {
|
|
return ToplevelManager.toplevels.values.filter((toplevel) => {
|
|
const address = `0x${toplevel.HyprlandToplevel.address}`
|
|
var win = windowByAddress[address]
|
|
if (root.isSpecialWorkspace(win))
|
|
return false;
|
|
const minWorkspace = root.workspaceGroup * root.workspacesShown + 1 + workspaceOffset;
|
|
const maxWorkspace = (root.workspaceGroup + 1) * root.workspacesShown + workspaceOffset;
|
|
const inWorkspaceGroup = (minWorkspace <= win?.workspace?.id && win?.workspace?.id <= maxWorkspace)
|
|
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
|
|
widgetMonitorData: root.monitorData
|
|
scale: root.scale
|
|
availableWorkspaceWidth: root.workspaceImplicitWidth
|
|
availableWorkspaceHeight: root.workspaceImplicitHeight
|
|
widgetMonitorId: root.monitor.id
|
|
recaptureToken: root.previewRecaptureToken
|
|
|
|
property bool atInitPosition: (initX == x && initY == y)
|
|
|
|
property int workspaceColIndex: root.getWorkspaceColumn(windowData?.workspace.id)
|
|
property int workspaceRowIndex: root.getWorkspaceRow(windowData?.workspace.id)
|
|
property int visibleWorkspaceRowIndex: root.getVisibleRowPosition(workspaceRowIndex)
|
|
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
|
|
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * visibleWorkspaceRowIndex
|
|
|
|
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 * window.widthRatio, 0) + xOffset)
|
|
window.y = Math.round(Math.max((windowData?.at[1] - (monitor?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale * window.heightRatio, 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
|
|
root.draggingTargetSpecialWorkspace = ""
|
|
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
|
|
const targetSpecialWorkspace = root.draggingTargetSpecialWorkspace
|
|
window.pressed = false
|
|
window.Drag.active = false
|
|
root.draggingFromWorkspace = -1
|
|
root.draggingTargetWorkspace = -1
|
|
root.draggingTargetSpecialWorkspace = ""
|
|
if (targetSpecialWorkspace === root.createSpecialWorkspaceTarget) {
|
|
const createdName = root.nextSpecialWorkspaceName()
|
|
//INFO: From normal TO special
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.window.move({workspace = 'special:${createdName}', follow = false, window = 'address:${window.windowData?.address}'})`);
|
|
} else {
|
|
Hyprland.dispatch(`movetoworkspacesilent special:${createdName}, address:${window.windowData?.address}`)
|
|
}
|
|
updateWindowPosition.restart()
|
|
}
|
|
else if (targetSpecialWorkspace && targetSpecialWorkspace !== root.specialWorkspaceName(windowData)) {
|
|
// Idk
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.window.move({workspace = 'special:${targetSpecialWorkspace}', follow = false, window = 'address:${window.windowData?.address}'})`);
|
|
} else {
|
|
Hyprland.dispatch(`movetoworkspacesilent special:${targetSpecialWorkspace}, address:${window.windowData?.address}`)
|
|
}
|
|
updateWindowPosition.restart()
|
|
}
|
|
else if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) {
|
|
//INFO: From normal TO normal
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.window.move({workspace = '${targetWorkspace}', follow = false, window = 'address:${window.windowData?.address}'})`);
|
|
} else {
|
|
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
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.focus({ window = 'address:${windowData.address}' })`);
|
|
} else {
|
|
Hyprland.dispatch(`focuswindow address:${windowData.address}`)
|
|
}
|
|
event.accepted = true
|
|
} else if (event.button === Qt.MiddleButton) {
|
|
if (Hyprland.usingLua) {
|
|
Hyprland.dispatch(`hl.dsp.window.close('address:${windowData.address}')`);
|
|
} else {
|
|
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 activeWorkspaceRowIndex: root.getWorkspaceRow(root.effectiveActiveWorkspaceId)
|
|
property int visibleActiveWorkspaceRowIndex: root.getVisibleRowPosition(activeWorkspaceRowIndex)
|
|
property int activeWorkspaceColIndex: root.getWorkspaceColumn(root.effectiveActiveWorkspaceId)
|
|
x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex
|
|
y: (root.workspaceImplicitHeight + workspaceSpacing) * visibleActiveWorkspaceRowIndex
|
|
z: root.windowDraggingZ - 1
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: specialWindowDragLayer
|
|
anchors.fill: parent
|
|
z: root.windowDraggingZ + 1
|
|
}
|
|
}
|
|
}
|