mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-16 16:24:00 -04:00
240 lines
8.8 KiB
QML
240 lines
8.8 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.WindowManager
|
|
import qs.Common
|
|
import qs.Services
|
|
import "../Common/WorkspaceModel.js" as WorkspaceModel
|
|
import "../DCommon/Common/WheelInput.js" as WheelInput
|
|
|
|
// Workspace list, focus and switching for one screen, shared by every host that draws workspace indicators.
|
|
QtObject {
|
|
id: root
|
|
|
|
property string screenName: ""
|
|
property var screen: null
|
|
property bool followFocus: false
|
|
property bool occupiedOnly: false
|
|
property bool showAllTags: false
|
|
property bool showPadding: false
|
|
property int paddingCount: 0
|
|
property bool showSpecial: false
|
|
property bool reverseScrolling: false
|
|
|
|
readonly property bool useAqueous: CompositorService.isAqueous && AqueousService.available && Quickshell.env("DMS_FORCE_EXTWS") !== "1"
|
|
readonly property bool useExtWorkspace: {
|
|
if (useAqueous)
|
|
return false;
|
|
if (Quickshell.env("DMS_FORCE_EXTWS") === "1")
|
|
return (WindowManager.windowsets?.length ?? 0) > 0;
|
|
if (!CompositorService.compositorDetected || CompositorService.hasWorkspaceIpc)
|
|
return false;
|
|
return (WindowManager.windowsets?.length ?? 0) > 0;
|
|
}
|
|
readonly property bool useNativeWorkspaces: useAqueous || (!useExtWorkspace && CompositorService.hasWorkspaceIpc)
|
|
readonly property var extProjection: (useExtWorkspace && screen) ? WindowManager.screenProjection(CompositorService.isAqueous ? Quickshell.screens.find(s => s.name === effectiveScreenName) || screen : screen) : null
|
|
|
|
readonly property string effectiveScreenName: followFocus ? (BarWidgetService.getFocusedScreenName() || screenName) : screenName
|
|
readonly property bool hiddenByOverview: CompositorService.workspacesHiddenByOverview(effectiveScreenName)
|
|
readonly property string compositorName: CompositorService.compositor
|
|
|
|
readonly property var currentWorkspace: {
|
|
if (useExtWorkspace)
|
|
return extProjection?.windowsets.find(ws => ws.active) ?? null;
|
|
if (!useNativeWorkspaces)
|
|
return 1;
|
|
return CompositorService.currentWorkspaceKey(screenName, followFocus);
|
|
}
|
|
|
|
readonly property var workspaceList: {
|
|
if (useExtWorkspace) {
|
|
const baseList = extWorkspaces();
|
|
return showPadding ? padWorkspaces(baseList) : baseList;
|
|
}
|
|
if (!useNativeWorkspaces)
|
|
return [1];
|
|
if (hiddenByOverview)
|
|
return [];
|
|
|
|
const baseList = CompositorService.workspacesForScreen(screenName, followFocus, {
|
|
"occupiedOnly": occupiedOnly,
|
|
"showAllTags": showAllTags,
|
|
"minCount": showPadding ? paddingCount : 0,
|
|
"showSpecial": showSpecial
|
|
});
|
|
if (CompositorService.ephemeralWorkspaces)
|
|
return hyprlandSlotList(baseList);
|
|
if (!showPadding || CompositorService.supportsPersistentWorkspaces || (useAqueous && baseList.length === 0))
|
|
return baseList;
|
|
return padWorkspaces(baseList);
|
|
}
|
|
|
|
readonly property var realWorkspaces: workspaceList.filter(ws => ws && !recordOf(ws).placeholder)
|
|
readonly property bool hasWorkspaces: realWorkspaces.length > 0
|
|
readonly property bool available: useNativeWorkspaces || (useExtWorkspace && hasWorkspaces)
|
|
|
|
onCompositorNameChanged: {
|
|
_placeholderPool = [];
|
|
_hyprSlotPool = {};
|
|
}
|
|
|
|
// Hyprland creates/destroys workspaces on empty enter/leave; slots keyed by id keep delegate identity so pills animate instead of popping
|
|
property var _hyprSlotPool: ({})
|
|
readonly property Component _hyprSlotComponent: Component {
|
|
QtObject {
|
|
property var ws: null
|
|
}
|
|
}
|
|
|
|
function recordOf(entry) {
|
|
if (!entry || entry.ws === undefined)
|
|
return entry;
|
|
return entry.ws;
|
|
}
|
|
|
|
function _hyprSlot(key, ws) {
|
|
let slot = _hyprSlotPool[key];
|
|
if (!slot) {
|
|
slot = _hyprSlotComponent.createObject(root);
|
|
_hyprSlotPool[key] = slot;
|
|
}
|
|
if (slot.ws !== ws)
|
|
slot.ws = ws;
|
|
return slot;
|
|
}
|
|
|
|
function hyprlandSlotList(raw) {
|
|
return raw.map(ws => _hyprSlot(ws.id > 0 ? ws.id : (ws.special ? "special:" : "name:") + (ws.name ?? ""), ws));
|
|
}
|
|
|
|
// Stable placeholder instances so ScriptModel reuses padding delegates instead of recreating them on workspace churn
|
|
property var _placeholderPool: []
|
|
|
|
function padWorkspaces(list) {
|
|
const padded = list.slice();
|
|
let slot = 0;
|
|
while (padded.length < paddingCount) {
|
|
if (_placeholderPool.length <= slot)
|
|
_placeholderPool.push(WorkspaceModel.placeholder());
|
|
padded.push(_placeholderPool[slot]);
|
|
slot++;
|
|
}
|
|
return padded;
|
|
}
|
|
|
|
function extWorkspaces() {
|
|
const fallback = [
|
|
{
|
|
"id": "1",
|
|
"name": "1",
|
|
"active": false
|
|
}
|
|
];
|
|
if (!extProjection)
|
|
return fallback;
|
|
|
|
let visible = extProjection.windowsets.filter(ws => ws.shouldDisplay);
|
|
if (visible.some(ws => ws.coordinates && ws.coordinates.length > 0)) {
|
|
visible = visible.slice().sort((a, b) => {
|
|
const coordsA = a.coordinates || [0, 0];
|
|
const coordsB = b.coordinates || [0, 0];
|
|
if (coordsA[0] !== coordsB[0])
|
|
return coordsA[0] - coordsB[0];
|
|
return coordsA[1] - coordsB[1];
|
|
});
|
|
}
|
|
return visible.length > 0 ? visible : fallback;
|
|
}
|
|
|
|
function isActive(entry) {
|
|
if (useExtWorkspace || !useNativeWorkspaces)
|
|
return entry === currentWorkspace;
|
|
return CompositorService.isCurrentWorkspace(recordOf(entry), currentWorkspace);
|
|
}
|
|
|
|
function isOccupied(entry) {
|
|
return useNativeWorkspaces && CompositorService.workspaceOccupied(recordOf(entry));
|
|
}
|
|
|
|
function isPlaceholder(entry) {
|
|
return !!(recordOf(entry)?.placeholder);
|
|
}
|
|
|
|
function switchTo(entry) {
|
|
const data = recordOf(entry);
|
|
if (!data || data.placeholder)
|
|
return;
|
|
if (useNativeWorkspaces) {
|
|
CompositorService.switchToWorkspace(data, effectiveScreenName);
|
|
return;
|
|
}
|
|
if (useExtWorkspace && typeof data.activate === "function")
|
|
data.activate();
|
|
}
|
|
|
|
function step(direction) {
|
|
if (useAqueous) {
|
|
const index = realWorkspaces.findIndex(w => w.id === currentWorkspace);
|
|
const next = Math.max(0, Math.min(realWorkspaces.length - 1, index + (direction > 0 ? 1 : -1)));
|
|
if (next !== index)
|
|
CompositorService.switchToWorkspace(realWorkspaces[next]);
|
|
return;
|
|
}
|
|
if (useExtWorkspace) {
|
|
if (realWorkspaces.length < 2)
|
|
return;
|
|
const currentIndex = realWorkspaces.findIndex(ws => ws === currentWorkspace);
|
|
const validIndex = currentIndex === -1 ? 0 : currentIndex;
|
|
const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);
|
|
if (nextIndex === validIndex)
|
|
return;
|
|
const nextWorkspace = realWorkspaces[nextIndex];
|
|
if (typeof nextWorkspace.activate === "function")
|
|
nextWorkspace.activate();
|
|
return;
|
|
}
|
|
if (!useNativeWorkspaces)
|
|
return;
|
|
// specials are overlays you toggle, not positions you scroll to
|
|
CompositorService.stepWorkspace(realWorkspaces.map(ws => recordOf(ws)).filter(ws => ws.special !== true), currentWorkspace, direction);
|
|
}
|
|
|
|
function secondaryAction(entry) {
|
|
CompositorService.workspaceSecondaryAction(entry ? recordOf(entry) : null, effectiveScreenName);
|
|
}
|
|
|
|
property real _touchpadAccumulator: 0
|
|
property real _mouseAccumulator: 0
|
|
property bool _scrollInProgress: false
|
|
readonly property Timer _scrollCooldown: Timer {
|
|
interval: 100
|
|
onTriggered: root._scrollInProgress = false
|
|
}
|
|
|
|
function handleWheel(wheel) {
|
|
if (_scrollInProgress)
|
|
return;
|
|
const delta = wheel.angleDelta.y;
|
|
const isTouchpad = WheelInput.isTouchpad(wheel);
|
|
const reverse = reverseScrolling ? -1 : 1;
|
|
|
|
if (isTouchpad) {
|
|
_touchpadAccumulator += delta;
|
|
if (Math.abs(_touchpadAccumulator) < 500)
|
|
return;
|
|
step(_touchpadAccumulator * reverse < 0 ? 1 : -1);
|
|
_scrollInProgress = true;
|
|
_scrollCooldown.restart();
|
|
_touchpadAccumulator = 0;
|
|
return;
|
|
}
|
|
|
|
_mouseAccumulator += delta;
|
|
if (Math.abs(_mouseAccumulator) < 120)
|
|
return;
|
|
step(_mouseAccumulator * reverse < 0 ? 1 : -1);
|
|
_scrollInProgress = true;
|
|
_scrollCooldown.restart();
|
|
_mouseAccumulator = 0;
|
|
}
|
|
}
|