mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-27 05:25:19 -04:00
48f6a0c632
* feat(bar): separate workspace appearance for unfocused displays Add a toggle in the Workspace Appearance card to style workspace indicators independently on displays that aren't currently focused. The card is split into Focused Display and Unfocused Display(s) tabs. When the new master toggle is off (default), unfocused displays inherit the focused settings, preserving existing behavior. When on, unfocused displays use their own colors (focused/occupied/unfocused/urgent) and focused border (enable, color, thickness). WorkspaceSwitcher resolves the focused monitor per compositor and routes color/border resolution through isFocusedMonitor/useUnfocusedAppearance. * refactor(bar): address review feedback for unfocused workspace appearance - Consolidate focused-monitor lookup into BarWidgetService.getFocusedScreenName(), adding Mango support, and drop the duplicate compositor switches in WorkspaceSwitcher (effectiveScreenName + isFocusedMonitor now use the helper). - Extract the shared color and border options into reusable WorkspaceAppearanceColorOptions and WorkspaceAppearanceBorderFields components so the focused and unfocused tabs no longer duplicate the layout. - Gate the unfocused-display options on BarWidgetService.focusedScreenDetectionSupported and show an explanatory note on compositors without focus detection (e.g. labwc), instead of silently no-opping.
2031 lines
95 KiB
QML
2031 lines
95 KiB
QML
import QtQuick
|
|
import QtQuick.Effects
|
|
import Quickshell
|
|
import Quickshell.Widgets
|
|
import Quickshell.Hyprland
|
|
import Quickshell.I3
|
|
import Quickshell.WindowManager
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property bool isVertical: axis?.isVertical ?? false
|
|
property var axis: null
|
|
property string screenName: ""
|
|
property real widgetHeight: 30
|
|
property real barThickness: 48
|
|
property var barConfig: null
|
|
property var blurBarWindow: null
|
|
property var hyprlandOverviewLoader: null
|
|
property var parentScreen: null
|
|
|
|
readonly property bool isMango: CompositorService.isMango
|
|
|
|
readonly property real _leftMargin: {
|
|
if (isVertical)
|
|
return 0;
|
|
root.x;
|
|
if (!root.parent)
|
|
return 0;
|
|
const gap = root.mapToItem(null, 0, 0).x;
|
|
return (gap > 0 && gap < 30) ? gap + 5 : 0;
|
|
}
|
|
readonly property real _rightMargin: {
|
|
if (isVertical)
|
|
return 0;
|
|
root.x;
|
|
root.width;
|
|
if (!root.parent || !blurBarWindow)
|
|
return 0;
|
|
const gap = blurBarWindow.width - root.mapToItem(null, root.width, 0).x;
|
|
return (gap > 0 && gap < 30) ? gap + 5 : 0;
|
|
}
|
|
readonly property real _topMargin: {
|
|
if (!isVertical)
|
|
return 0;
|
|
root.y;
|
|
if (!root.parent)
|
|
return 0;
|
|
const gap = root.mapToItem(null, 0, 0).y;
|
|
return (gap > 0 && gap < 30) ? gap + 5 : 0;
|
|
}
|
|
readonly property real _bottomMargin: {
|
|
if (!isVertical)
|
|
return 0;
|
|
root.y;
|
|
root.height;
|
|
if (!root.parent || !blurBarWindow)
|
|
return 0;
|
|
const gap = blurBarWindow.height - root.mapToItem(null, 0, root.height).y;
|
|
return (gap > 0 && gap < 30) ? gap + 5 : 0;
|
|
}
|
|
|
|
property int _desktopEntriesUpdateTrigger: 0
|
|
readonly property var sortedToplevels: {
|
|
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
|
|
}
|
|
|
|
readonly property string effectiveScreenName: {
|
|
if (!SettingsData.workspaceFollowFocus)
|
|
return root.screenName;
|
|
return BarWidgetService.getFocusedScreenName() || root.screenName;
|
|
}
|
|
readonly property bool mangoOverviewActive: CompositorService.isMango && MangoService.isOutputInOverview(effectiveScreenName)
|
|
|
|
readonly property bool isFocusedMonitor: {
|
|
const focused = BarWidgetService.getFocusedScreenName();
|
|
return focused === "" || root.screenName === "" || focused === root.screenName;
|
|
}
|
|
readonly property bool useUnfocusedAppearance: !isFocusedMonitor && SettingsData.workspaceUnfocusedMonitorSeparateAppearance && BarWidgetService.focusedScreenDetectionSupported
|
|
|
|
readonly property var extProjection: (useExtWorkspace && parentScreen) ? WindowManager.screenProjection(parentScreen) : null
|
|
readonly property bool useExtWorkspace: {
|
|
if (Quickshell.env("DMS_FORCE_EXTWS") === "1")
|
|
return (WindowManager.windowsets?.length ?? 0) > 0;
|
|
if (!CompositorService.compositorDetected)
|
|
return false;
|
|
switch (CompositorService.compositor) {
|
|
case "niri":
|
|
case "hyprland":
|
|
case "mango":
|
|
case "sway":
|
|
case "scroll":
|
|
case "miracle":
|
|
return false;
|
|
default:
|
|
return (WindowManager.windowsets?.length ?? 0) > 0;
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: DesktopEntries
|
|
function onApplicationsChanged() {
|
|
_desktopEntriesUpdateTrigger++;
|
|
}
|
|
}
|
|
|
|
property var currentWorkspace: {
|
|
if (useExtWorkspace)
|
|
return getExtWorkspaceActiveWorkspace();
|
|
|
|
switch (CompositorService.compositor) {
|
|
case "niri":
|
|
return getNiriActiveWorkspace();
|
|
case "hyprland":
|
|
return getHyprlandActiveWorkspace();
|
|
case "mango":
|
|
const activeTags = getDwlActiveTags();
|
|
return activeTags.length > 0 ? activeTags[0] : -1;
|
|
case "sway":
|
|
case "scroll":
|
|
case "miracle":
|
|
return getSwayActiveWorkspace();
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
property var dwlActiveTags: {
|
|
if (root.isMango) {
|
|
return getDwlActiveTags();
|
|
}
|
|
return [];
|
|
}
|
|
property var workspaceList: {
|
|
if (useExtWorkspace) {
|
|
const baseList = getExtWorkspaceWorkspaces();
|
|
return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList;
|
|
}
|
|
|
|
let baseList;
|
|
switch (CompositorService.compositor) {
|
|
case "niri":
|
|
baseList = getNiriWorkspaces();
|
|
break;
|
|
case "hyprland":
|
|
baseList = getHyprlandWorkspaces();
|
|
break;
|
|
case "mango":
|
|
if (root.mangoOverviewActive)
|
|
return [];
|
|
baseList = getDwlTags();
|
|
break;
|
|
case "sway":
|
|
case "scroll":
|
|
case "miracle":
|
|
baseList = getSwayWorkspaces();
|
|
break;
|
|
default:
|
|
return [1];
|
|
}
|
|
return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList;
|
|
}
|
|
|
|
function getSwayWorkspaces() {
|
|
const workspaces = I3.workspaces?.values || [];
|
|
if (workspaces.length === 0)
|
|
return [
|
|
{
|
|
"num": 1
|
|
}
|
|
];
|
|
|
|
function mapWorkspace(ws) {
|
|
return {
|
|
"num": ws.number,
|
|
"name": ws.name,
|
|
"focused": ws.focused,
|
|
"active": ws.active,
|
|
"urgent": ws.urgent,
|
|
"monitor": ws.monitor
|
|
};
|
|
}
|
|
|
|
if (!root.screenName || SettingsData.workspaceFollowFocus) {
|
|
return workspaces.slice().sort((a, b) => a.num - b.num).map(mapWorkspace);
|
|
}
|
|
|
|
const monitorWorkspaces = workspaces.filter(ws => ws.monitor?.name === root.screenName);
|
|
return monitorWorkspaces.length > 0 ? monitorWorkspaces.sort((a, b) => a.num - b.num).map(mapWorkspace) : [
|
|
{
|
|
"num": 1
|
|
}
|
|
];
|
|
}
|
|
|
|
function getSwayActiveWorkspace() {
|
|
if (!root.screenName || SettingsData.workspaceFollowFocus) {
|
|
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
|
return focusedWs ? focusedWs.num : 1;
|
|
}
|
|
|
|
const focusedWs = I3.workspaces?.values?.find(ws => ws.monitor?.name === root.screenName && ws.focused === true);
|
|
return focusedWs ? focusedWs.num : 1;
|
|
}
|
|
|
|
function getHyprlandWorkspaces() {
|
|
const workspaces = Hyprland.workspaces?.values || [];
|
|
if (workspaces.length === 0) {
|
|
return [
|
|
{
|
|
id: 1,
|
|
name: "1"
|
|
}
|
|
];
|
|
}
|
|
|
|
let filtered = workspaces.filter(ws => ws.id > -1);
|
|
if (filtered.length === 0) {
|
|
return [
|
|
{
|
|
id: 1,
|
|
name: "1"
|
|
}
|
|
];
|
|
}
|
|
|
|
if (!root.screenName || SettingsData.workspaceFollowFocus) {
|
|
filtered = filtered.slice().sort((a, b) => a.id - b.id);
|
|
} else {
|
|
const monitorWorkspaces = filtered.filter(ws => ws.monitor?.name === root.screenName);
|
|
filtered = monitorWorkspaces.length > 0 ? monitorWorkspaces.sort((a, b) => a.id - b.id) : [
|
|
{
|
|
id: 1,
|
|
name: "1"
|
|
}
|
|
];
|
|
}
|
|
|
|
if (!SettingsData.showOccupiedWorkspacesOnly) {
|
|
return filtered;
|
|
}
|
|
|
|
const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []);
|
|
const activeWsId = root.currentWorkspace;
|
|
return filtered.filter(ws => {
|
|
if (ws.id === activeWsId)
|
|
return true;
|
|
return hyprlandToplevels.some(tl => tl.workspace?.id === ws.id);
|
|
});
|
|
}
|
|
|
|
function getHyprlandActiveWorkspace() {
|
|
if (!root.screenName || SettingsData.workspaceFollowFocus) {
|
|
return Hyprland.focusedWorkspace?.id || 1;
|
|
}
|
|
|
|
const monitor = Hyprland.monitors?.values?.find(m => m.name === root.screenName);
|
|
return monitor?.activeWorkspace?.id || 1;
|
|
}
|
|
|
|
function getWorkspaceIcons(ws) {
|
|
_desktopEntriesUpdateTrigger;
|
|
if (!SettingsData.showWorkspaceApps || !ws) {
|
|
return [];
|
|
}
|
|
|
|
let targetWorkspaceId;
|
|
if (CompositorService.isNiri) {
|
|
if (!ws || typeof ws !== "object") {
|
|
const wsNumber = typeof ws === "number" ? ws : -1;
|
|
if (wsNumber <= 0) {
|
|
return [];
|
|
}
|
|
const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNumber && w.output === root.effectiveScreenName);
|
|
if (!workspace) {
|
|
return [];
|
|
}
|
|
targetWorkspaceId = workspace.id;
|
|
} else {
|
|
if (ws.id === undefined || ws.id === -1 || ws.idx === -1) {
|
|
return [];
|
|
}
|
|
targetWorkspaceId = ws.id;
|
|
}
|
|
} else if (CompositorService.isHyprland) {
|
|
targetWorkspaceId = ws.id !== undefined ? ws.id : ws;
|
|
} else if (root.isMango) {
|
|
if (typeof ws !== "object" || ws.tag === undefined) {
|
|
return [];
|
|
}
|
|
targetWorkspaceId = ws.tag;
|
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
|
targetWorkspaceId = ws.num !== undefined ? ws.num : ws;
|
|
} else {
|
|
return [];
|
|
}
|
|
|
|
const wins = CompositorService.isNiri ? (NiriService.windows || []) : CompositorService.sortedToplevels;
|
|
|
|
const byApp = {};
|
|
let isActiveWs = false;
|
|
if (CompositorService.isNiri) {
|
|
isActiveWs = NiriService.allWorkspaces.some(ws => ws.id === targetWorkspaceId && ws.is_active);
|
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
|
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
|
isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false;
|
|
} else if (root.isMango) {
|
|
const output = MangoService.getOutputState(root.effectiveScreenName);
|
|
if (output && output.tags) {
|
|
const tag = output.tags.find(t => t.tag === targetWorkspaceId);
|
|
isActiveWs = tag ? (tag.state === 1) : false;
|
|
}
|
|
} else {
|
|
isActiveWs = targetWorkspaceId === root.currentWorkspace;
|
|
}
|
|
|
|
wins.forEach((w, i) => {
|
|
if (!w) {
|
|
return;
|
|
}
|
|
|
|
if (CompositorService.isMango) {
|
|
// mangoTags are 1-based; targetWorkspaceId is 0-based.
|
|
if (!(w.mangoTags || []).includes(targetWorkspaceId + 1))
|
|
return;
|
|
} else {
|
|
let winWs = null;
|
|
if (CompositorService.isNiri) {
|
|
winWs = w.workspace_id;
|
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
|
winWs = w.workspace?.num;
|
|
} else {
|
|
const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []);
|
|
const hyprToplevel = hyprlandToplevels.find(ht => ht.wayland === w);
|
|
winWs = hyprToplevel?.workspace?.id;
|
|
}
|
|
|
|
if (winWs === undefined || winWs === null || winWs !== targetWorkspaceId) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const keyBase = (w.app_id || w.appId || w.class || w.windowClass || "unknown");
|
|
const moddedId = Paths.moddedAppId(keyBase);
|
|
const groupThisWs = SettingsData.groupWorkspaceApps && (!isActiveWs || SettingsData.groupActiveWorkspaceApps);
|
|
const key = groupThisWs ? moddedId : `${moddedId}_${i}`;
|
|
|
|
if (!byApp[key]) {
|
|
const isQuickshell = keyBase === "org.quickshell" || keyBase === "com.danklinux.dms";
|
|
const isSteamApp = Paths.isSteamApp(moddedId);
|
|
const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
|
|
const icon = Paths.getAppIcon(moddedId, desktopEntry);
|
|
const appName = Paths.getAppName(moddedId, desktopEntry);
|
|
byApp[key] = {
|
|
"type": "icon",
|
|
"icon": icon,
|
|
"isQuickshell": isQuickshell,
|
|
"isSteamApp": isSteamApp,
|
|
"active": !!((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)),
|
|
"count": 1,
|
|
"windowId": w.address || w.id,
|
|
"fallbackText": appName || ""
|
|
};
|
|
} else {
|
|
byApp[key].count++;
|
|
if ((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)) {
|
|
byApp[key].active = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
return Object.values(byApp);
|
|
}
|
|
|
|
function padWorkspaces(list) {
|
|
const padded = list.slice();
|
|
let placeholder;
|
|
if (useExtWorkspace) {
|
|
placeholder = {
|
|
"id": "",
|
|
"name": "",
|
|
"active": false,
|
|
"_placeholder": true
|
|
};
|
|
} else if (CompositorService.isNiri) {
|
|
placeholder = {
|
|
"id": -1,
|
|
"idx": -1,
|
|
"name": ""
|
|
};
|
|
} else if (CompositorService.isHyprland) {
|
|
placeholder = {
|
|
"id": -1,
|
|
"name": ""
|
|
};
|
|
} else if (root.isMango) {
|
|
placeholder = {
|
|
"tag": -1
|
|
};
|
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
|
placeholder = {
|
|
"num": -1
|
|
};
|
|
} else {
|
|
placeholder = -1;
|
|
}
|
|
while (padded.length < 3) {
|
|
padded.push(placeholder);
|
|
}
|
|
return padded;
|
|
}
|
|
|
|
function getNiriWorkspaces() {
|
|
if (NiriService.allWorkspaces.length === 0) {
|
|
return [
|
|
{
|
|
"id": 1,
|
|
"idx": 0,
|
|
"name": ""
|
|
},
|
|
{
|
|
"id": 2,
|
|
"idx": 1,
|
|
"name": ""
|
|
}
|
|
];
|
|
}
|
|
|
|
const fallbackWorkspaces = [
|
|
{
|
|
"id": 1,
|
|
"idx": 0,
|
|
"name": ""
|
|
},
|
|
{
|
|
"id": 2,
|
|
"idx": 1,
|
|
"name": ""
|
|
}
|
|
];
|
|
|
|
let workspaces;
|
|
if (!root.screenName || SettingsData.workspaceFollowFocus) {
|
|
const currentWorkspaces = NiriService.getCurrentOutputWorkspaces();
|
|
workspaces = currentWorkspaces.length > 0 ? currentWorkspaces : fallbackWorkspaces;
|
|
} else {
|
|
const displayWorkspaces = NiriService.allWorkspaces.filter(ws => ws.output === root.screenName);
|
|
workspaces = displayWorkspaces.length > 0 ? displayWorkspaces : fallbackWorkspaces;
|
|
}
|
|
|
|
workspaces = workspaces.slice().sort((a, b) => a.idx - b.idx);
|
|
|
|
if (!SettingsData.showOccupiedWorkspacesOnly) {
|
|
return workspaces;
|
|
}
|
|
|
|
return workspaces.filter(ws => {
|
|
if (ws.is_active)
|
|
return true;
|
|
return NiriService.windows?.some(win => win.workspace_id === ws.id) ?? false;
|
|
});
|
|
}
|
|
|
|
function getNiriActiveWorkspace() {
|
|
if (NiriService.allWorkspaces.length === 0) {
|
|
return 1;
|
|
}
|
|
|
|
if (!root.screenName || SettingsData.workspaceFollowFocus) {
|
|
return NiriService.getCurrentWorkspaceNumber();
|
|
}
|
|
|
|
const activeWs = NiriService.allWorkspaces.find(ws => ws.output === root.screenName && ws.is_active);
|
|
return activeWs ? activeWs.idx : 1;
|
|
}
|
|
|
|
function getDwlTags() {
|
|
if (!MangoService.available)
|
|
return [];
|
|
|
|
const targetScreen = root.effectiveScreenName;
|
|
const output = MangoService.getOutputState(targetScreen);
|
|
if (!output || !output.tags || output.tags.length === 0)
|
|
return [];
|
|
|
|
if (SettingsData.dwlShowAllTags) {
|
|
return output.tags.map(tag => ({
|
|
"tag": tag.tag,
|
|
"state": tag.state,
|
|
"clients": tag.clients,
|
|
"focused": tag.focused
|
|
}));
|
|
}
|
|
|
|
const visibleTagIndices = MangoService.getVisibleTags(targetScreen);
|
|
return visibleTagIndices.map(tagIndex => {
|
|
const tagData = output.tags.find(t => t.tag === tagIndex);
|
|
return {
|
|
"tag": tagIndex,
|
|
"state": tagData?.state ?? 0,
|
|
"clients": tagData?.clients ?? 0,
|
|
"focused": tagData?.focused ?? false
|
|
};
|
|
});
|
|
}
|
|
|
|
function getDwlActiveTags() {
|
|
if (!MangoService.available)
|
|
return [];
|
|
|
|
return MangoService.getActiveTags(root.effectiveScreenName);
|
|
}
|
|
|
|
function getExtWorkspaceWorkspaces() {
|
|
const fallback = [
|
|
{
|
|
"id": "1",
|
|
"name": "1",
|
|
"active": false
|
|
}
|
|
];
|
|
if (!extProjection)
|
|
return fallback;
|
|
|
|
let visible = extProjection.windowsets.filter(ws => ws.shouldDisplay);
|
|
|
|
const hasValidCoordinates = visible.some(ws => ws.coordinates && ws.coordinates.length > 0);
|
|
if (hasValidCoordinates) {
|
|
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 getExtWorkspaceActiveWorkspace() {
|
|
if (!extProjection)
|
|
return "";
|
|
const activeWs = extProjection.windowsets.find(ws => ws.active);
|
|
return activeWs ? (activeWs.id || activeWs.name || "") : "";
|
|
}
|
|
|
|
readonly property real dpr: parentScreen ? CompositorService.getScreenScale(parentScreen) : 1
|
|
readonly property real padding: (root.barConfig?.removeWidgetPadding ?? false) ? 0 : Theme.snap((root.barConfig?.widgetPadding ?? 12) * (widgetHeight / 30), dpr)
|
|
readonly property real visualWidth: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
|
|
readonly property real visualHeight: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
|
|
readonly property real appIconSize: Theme.barIconSize(barThickness, -6 + SettingsData.workspaceAppIconSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
|
|
|
function getRealWorkspaces() {
|
|
return root.workspaceList.filter(ws => {
|
|
if (useExtWorkspace)
|
|
return ws && !ws._placeholder;
|
|
if (CompositorService.isNiri)
|
|
return ws && ws.idx !== -1;
|
|
if (CompositorService.isHyprland)
|
|
return ws && ws.id !== -1;
|
|
if (root.isMango)
|
|
return ws && ws.tag !== -1;
|
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
|
return ws && ws.num !== -1;
|
|
return ws !== -1;
|
|
});
|
|
}
|
|
|
|
function switchToWorkspaceByModelData(data) {
|
|
if (!data)
|
|
return;
|
|
|
|
if (root.useExtWorkspace) {
|
|
if (typeof data.activate === "function")
|
|
data.activate();
|
|
return;
|
|
}
|
|
|
|
switch (CompositorService.compositor) {
|
|
case "niri":
|
|
if (data.id !== undefined)
|
|
NiriService.switchToWorkspace(data.id);
|
|
break;
|
|
case "hyprland":
|
|
if (data.id) {
|
|
HyprlandService.focusWorkspace(data.id);
|
|
}
|
|
break;
|
|
case "mango":
|
|
if (data.tag !== undefined)
|
|
MangoService.switchToTag(root.screenName, data.tag);
|
|
break;
|
|
case "sway":
|
|
case "scroll":
|
|
case "miracle":
|
|
if (data.num)
|
|
try {
|
|
I3.dispatch(`workspace number ${data.num}`);
|
|
} catch (_) {}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function findClosestWorkspaceIndex(localX, localY) {
|
|
if (workspaceRepeater.count === 0)
|
|
return -1;
|
|
|
|
let closestIdx = -1;
|
|
let closestDist = Infinity;
|
|
|
|
for (let i = 0; i < workspaceRepeater.count; i++) {
|
|
const item = workspaceRepeater.itemAt(i);
|
|
if (!item)
|
|
continue;
|
|
const center = item.mapToItem(root, item.width / 2, item.height / 2);
|
|
const dist = isVertical ? Math.abs(localY - center.y) : Math.abs(localX - center.x);
|
|
if (dist < closestDist) {
|
|
closestDist = dist;
|
|
closestIdx = i;
|
|
}
|
|
}
|
|
return closestIdx;
|
|
}
|
|
|
|
function switchWorkspace(direction) {
|
|
if (useExtWorkspace) {
|
|
const realWorkspaces = getRealWorkspaces();
|
|
if (realWorkspaces.length < 2) {
|
|
return;
|
|
}
|
|
|
|
const currentIndex = realWorkspaces.findIndex(ws => (ws.id || ws.name) === root.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();
|
|
} else if (CompositorService.isNiri) {
|
|
const realWorkspaces = getRealWorkspaces();
|
|
if (realWorkspaces.length < 2) {
|
|
return;
|
|
}
|
|
|
|
const currentIndex = realWorkspaces.findIndex(ws => ws && ws.idx === root.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 (!nextWorkspace || nextWorkspace.id === undefined) {
|
|
return;
|
|
}
|
|
NiriService.switchToWorkspace(nextWorkspace.id);
|
|
} else if (CompositorService.isHyprland) {
|
|
const realWorkspaces = getRealWorkspaces();
|
|
if (realWorkspaces.length < 2) {
|
|
return;
|
|
}
|
|
|
|
const currentIndex = realWorkspaces.findIndex(ws => ws.id === root.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;
|
|
}
|
|
|
|
HyprlandService.focusWorkspace(realWorkspaces[nextIndex].id);
|
|
} else if (root.isMango) {
|
|
const realWorkspaces = getRealWorkspaces();
|
|
if (realWorkspaces.length < 2) {
|
|
return;
|
|
}
|
|
|
|
const currentIndex = realWorkspaces.findIndex(ws => ws.tag === root.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;
|
|
}
|
|
|
|
MangoService.switchToTag(root.screenName, realWorkspaces[nextIndex].tag);
|
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
|
const realWorkspaces = getRealWorkspaces();
|
|
if (realWorkspaces.length < 2) {
|
|
return;
|
|
}
|
|
|
|
const currentIndex = realWorkspaces.findIndex(ws => ws.num === root.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;
|
|
}
|
|
|
|
try {
|
|
I3.dispatch(`workspace number ${realWorkspaces[nextIndex].num}`);
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
function getWorkspaceIndexFallback(modelData, index) {
|
|
if (root.useExtWorkspace)
|
|
return index + 1;
|
|
if (CompositorService.isNiri)
|
|
return (modelData?.idx !== undefined && modelData?.idx !== -1) ? modelData.idx : "";
|
|
if (CompositorService.isHyprland)
|
|
return modelData?.id || "";
|
|
if (root.isMango)
|
|
return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "";
|
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
|
return modelData?.num || "";
|
|
return modelData - 1;
|
|
}
|
|
|
|
function getWorkspaceIndex(modelData, index) {
|
|
let isPlaceholder;
|
|
if (root.useExtWorkspace) {
|
|
isPlaceholder = modelData?.hidden === true;
|
|
} else if (CompositorService.isNiri) {
|
|
isPlaceholder = modelData?.idx === -1;
|
|
} else if (CompositorService.isHyprland) {
|
|
isPlaceholder = modelData?.id === -1;
|
|
} else if (root.isMango) {
|
|
isPlaceholder = modelData?.tag === -1;
|
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
|
isPlaceholder = modelData?.num === -1;
|
|
} else {
|
|
isPlaceholder = modelData === -1;
|
|
}
|
|
|
|
if (isPlaceholder)
|
|
return index + 1;
|
|
|
|
let workspaceName = "";
|
|
if (SettingsData.showWorkspaceName) {
|
|
workspaceName = modelData?.name ?? "";
|
|
|
|
if (workspaceName && workspaceName !== "") {
|
|
if (root.isVertical) {
|
|
workspaceName = workspaceName.charAt(0);
|
|
}
|
|
} else {
|
|
workspaceName = "";
|
|
}
|
|
}
|
|
|
|
if (workspaceName) {
|
|
if (SettingsData.showWorkspaceIndex) {
|
|
const indexLabel = getWorkspaceIndexFallback(modelData, index);
|
|
return indexLabel ? `${indexLabel}: ${workspaceName}` : workspaceName;
|
|
}
|
|
return workspaceName;
|
|
}
|
|
|
|
return getWorkspaceIndexFallback(modelData, index);
|
|
}
|
|
|
|
readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || root.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
|
|
readonly property bool hasWorkspaces: getRealWorkspaces().length > 0
|
|
readonly property bool shouldShow: hasNativeWorkspaceSupport || (useExtWorkspace && hasWorkspaces)
|
|
|
|
width: shouldShow ? (isVertical ? barThickness : visualWidth) : 0
|
|
height: shouldShow ? (isVertical ? visualHeight : barThickness) : 0
|
|
visible: shouldShow
|
|
|
|
Item {
|
|
id: visualBackground
|
|
width: root.visualWidth
|
|
height: root.visualHeight
|
|
anchors.centerIn: parent
|
|
|
|
Rectangle {
|
|
id: outline
|
|
anchors.centerIn: parent
|
|
width: {
|
|
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
|
|
return parent.width + borderWidth * 2;
|
|
}
|
|
height: {
|
|
const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
|
|
return parent.height + borderWidth * 2;
|
|
}
|
|
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
|
|
color: "transparent"
|
|
border.width: {
|
|
if (barConfig?.widgetOutlineEnabled ?? false) {
|
|
return barConfig?.widgetOutlineThickness ?? 1;
|
|
}
|
|
return 0;
|
|
}
|
|
border.color: {
|
|
if (!(barConfig?.widgetOutlineEnabled ?? false)) {
|
|
return "transparent";
|
|
}
|
|
const colorOption = barConfig?.widgetOutlineColor || "primary";
|
|
const opacity = barConfig?.widgetOutlineOpacity ?? 1.0;
|
|
switch (colorOption) {
|
|
case "surfaceText":
|
|
return Theme.withAlpha(Theme.surfaceText, opacity);
|
|
case "secondary":
|
|
return Theme.withAlpha(Theme.secondary, opacity);
|
|
case "primary":
|
|
return Theme.withAlpha(Theme.primary, opacity);
|
|
default:
|
|
return Theme.withAlpha(Theme.primary, opacity);
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: background
|
|
anchors.fill: parent
|
|
radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
|
|
color: {
|
|
if ((barConfig?.noBackground ?? false))
|
|
return "transparent";
|
|
const baseColor = Theme.widgetBaseBackgroundColor;
|
|
const transparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
|
|
if (Theme.widgetBackgroundHasAlpha) {
|
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency);
|
|
}
|
|
return Theme.withAlpha(baseColor, transparency);
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: edgeMouseArea
|
|
z: -1
|
|
x: -root._leftMargin
|
|
y: -root._topMargin
|
|
width: root.width + root._leftMargin + root._rightMargin
|
|
height: root.height + root._topMargin + root._bottomMargin
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
|
|
property real touchpadAccumulator: 0
|
|
property real mouseAccumulator: 0
|
|
property bool scrollInProgress: false
|
|
|
|
Timer {
|
|
id: scrollCooldown
|
|
interval: 100
|
|
onTriggered: parent.scrollInProgress = false
|
|
}
|
|
|
|
onClicked: mouse => {
|
|
const rootPos = edgeMouseArea.mapToItem(root, mouse.x, mouse.y);
|
|
switch (mouse.button) {
|
|
case Qt.RightButton:
|
|
if (CompositorService.isNiri) {
|
|
NiriService.toggleOverview();
|
|
} else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
|
|
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen;
|
|
}
|
|
break;
|
|
case Qt.LeftButton:
|
|
const idx = root.findClosestWorkspaceIndex(rootPos.x, rootPos.y);
|
|
if (idx >= 0)
|
|
root.switchToWorkspaceByModelData(root.workspaceList[idx]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
onWheel: wheel => {
|
|
if (Math.abs(wheel.angleDelta.x) > Math.abs(wheel.angleDelta.y)) {
|
|
wheel.accepted = false;
|
|
return;
|
|
}
|
|
|
|
if (scrollInProgress)
|
|
return;
|
|
|
|
const delta = wheel.angleDelta.y;
|
|
const isTouchpad = wheel.pixelDelta && wheel.pixelDelta.y !== 0;
|
|
const reverse = SettingsData.reverseScrolling ? -1 : 1;
|
|
|
|
if (isTouchpad) {
|
|
touchpadAccumulator += delta;
|
|
if (Math.abs(touchpadAccumulator) < 500)
|
|
return;
|
|
const direction = touchpadAccumulator * reverse < 0 ? 1 : -1;
|
|
root.switchWorkspace(direction);
|
|
scrollInProgress = true;
|
|
scrollCooldown.restart();
|
|
touchpadAccumulator = 0;
|
|
return;
|
|
}
|
|
|
|
mouseAccumulator += delta;
|
|
if (Math.abs(mouseAccumulator) < 120)
|
|
return;
|
|
const direction = mouseAccumulator * reverse < 0 ? 1 : -1;
|
|
root.switchWorkspace(direction);
|
|
scrollInProgress = true;
|
|
scrollCooldown.restart();
|
|
mouseAccumulator = 0;
|
|
}
|
|
}
|
|
|
|
property int dragSourceIndex: -1
|
|
property int dragTargetIndex: -1
|
|
property bool suppressShiftAnimation: false
|
|
|
|
onWorkspaceListChanged: {
|
|
if (dragSourceIndex >= 0) {
|
|
dragSourceIndex = -1;
|
|
dragTargetIndex = -1;
|
|
suppressShiftAnimation = false;
|
|
}
|
|
}
|
|
|
|
Flow {
|
|
id: workspaceRow
|
|
|
|
x: isVertical ? visualBackground.x : (parent.width - implicitWidth) / 2
|
|
y: isVertical ? (parent.height - implicitHeight) / 2 : visualBackground.y
|
|
spacing: Theme.spacingS
|
|
flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight
|
|
|
|
// mango reports active_tags=0 while the overview is open; surface it as a pill
|
|
Item {
|
|
id: overviewPill
|
|
visible: CompositorService.isMango && MangoService.inOverview
|
|
width: root.isVertical ? root.widgetHeight : overviewBg.width
|
|
height: root.isVertical ? overviewBg.height : root.widgetHeight
|
|
|
|
readonly property real labelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
|
|
Rectangle {
|
|
id: overviewBg
|
|
anchors.centerIn: parent
|
|
width: root.isVertical ? Math.max(root.widgetHeight * 0.7, overviewContent.implicitWidth + Theme.spacingS) : (overviewContent.implicitWidth + Theme.spacingS * 2)
|
|
height: Math.max(root.widgetHeight * 0.5, overviewContent.implicitHeight + Theme.spacingXS)
|
|
radius: Theme.cornerRadius
|
|
color: Theme.withAlpha(Theme.primary, 0.18)
|
|
|
|
Row {
|
|
id: overviewContent
|
|
anchors.centerIn: parent
|
|
spacing: Theme.spacingXS
|
|
|
|
DankIcon {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
name: "grid_view"
|
|
size: overviewPill.labelSize + 2
|
|
color: Theme.primary
|
|
}
|
|
|
|
StyledText {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: !root.isVertical
|
|
text: I18n.tr("Overview")
|
|
color: Theme.primary
|
|
font.pixelSize: overviewPill.labelSize
|
|
font.weight: Font.DemiBold
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: Quickshell.execDetached(["mmsg", "dispatch", "toggleoverview"])
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
id: workspaceRepeater
|
|
model: ScriptModel {
|
|
values: root.workspaceList
|
|
}
|
|
|
|
Item {
|
|
id: delegateRoot
|
|
|
|
property bool isDropTarget: root.dragTargetIndex === index
|
|
|
|
z: dragHandler.dragging ? 1000 : 1
|
|
|
|
property real shiftOffset: {
|
|
if (root.dragSourceIndex < 0 || index === root.dragSourceIndex)
|
|
return 0;
|
|
const dragIdx = root.dragSourceIndex;
|
|
const dropIdx = root.dragTargetIndex;
|
|
if (dropIdx < 0)
|
|
return 0;
|
|
const shiftAmount = delegateRoot.width + Theme.spacingS;
|
|
if (dragIdx < dropIdx && index > dragIdx && index <= dropIdx)
|
|
return -shiftAmount;
|
|
if (dragIdx > dropIdx && index >= dropIdx && index < dragIdx)
|
|
return shiftAmount;
|
|
return 0;
|
|
}
|
|
|
|
transform: Translate {
|
|
x: root.isVertical ? 0 : delegateRoot.shiftOffset
|
|
y: root.isVertical ? delegateRoot.shiftOffset : 0
|
|
Behavior on x {
|
|
enabled: !root.suppressShiftAnimation
|
|
NumberAnimation {
|
|
duration: 150
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
}
|
|
Behavior on y {
|
|
enabled: !root.suppressShiftAnimation
|
|
NumberAnimation {
|
|
duration: 150
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
}
|
|
}
|
|
|
|
property bool isActive: {
|
|
if (root.useExtWorkspace)
|
|
return (modelData?.id || modelData?.name) === root.currentWorkspace;
|
|
if (CompositorService.isNiri)
|
|
return !!(modelData && modelData.idx === root.currentWorkspace);
|
|
if (CompositorService.isHyprland)
|
|
return !!(modelData && modelData.id === root.currentWorkspace);
|
|
if (root.isMango)
|
|
return !!(modelData && root.dwlActiveTags.includes(modelData.tag));
|
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
|
return !!(modelData && modelData.num === root.currentWorkspace);
|
|
return modelData === root.currentWorkspace;
|
|
}
|
|
property bool isOccupied: {
|
|
if (CompositorService.isHyprland)
|
|
return Array.from(Hyprland.toplevels?.values || []).some(tl => tl.workspace?.id === modelData?.id);
|
|
if (root.isMango)
|
|
return modelData.clients > 0;
|
|
if (CompositorService.isNiri) {
|
|
const workspace = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName);
|
|
return workspace ? (NiriService.windows?.some(win => win.workspace_id === workspace.id) ?? false) : false;
|
|
}
|
|
return false;
|
|
}
|
|
property bool isPlaceholder: {
|
|
if (root.useExtWorkspace)
|
|
return !!(modelData && modelData._placeholder);
|
|
if (CompositorService.isNiri)
|
|
return !!(modelData && modelData.idx === -1);
|
|
if (CompositorService.isHyprland)
|
|
return !!(modelData && modelData.id === -1);
|
|
if (root.isMango)
|
|
return !!(modelData && modelData.tag === -1);
|
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
|
return !!(modelData && modelData.num === -1);
|
|
return modelData === -1;
|
|
}
|
|
property bool isHovered: mouseArea.containsMouse
|
|
|
|
property var loadedWorkspaceData: null
|
|
property bool loadedIsUrgent: false
|
|
property bool isUrgent: {
|
|
if (root.useExtWorkspace)
|
|
return modelData?.urgent ?? false;
|
|
if (CompositorService.isHyprland)
|
|
return modelData?.urgent ?? false;
|
|
if (CompositorService.isNiri)
|
|
return loadedIsUrgent;
|
|
if (root.isMango)
|
|
return modelData?.state === 2;
|
|
if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
|
return loadedIsUrgent;
|
|
return false;
|
|
}
|
|
readonly property var loadedIconData: {
|
|
if (isPlaceholder)
|
|
return null;
|
|
const name = modelData?.name;
|
|
if (!name)
|
|
return null;
|
|
return SettingsData.getWorkspaceNameIcon(name);
|
|
}
|
|
readonly property bool loadedHasIcon: loadedIconData !== null
|
|
property var loadedIcons: []
|
|
|
|
readonly property int stableIconCount: {
|
|
if (!SettingsData.showWorkspaceApps || isPlaceholder)
|
|
return 0;
|
|
|
|
let targetWorkspaceId;
|
|
if (root.useExtWorkspace) {
|
|
targetWorkspaceId = modelData?.id || modelData?.name;
|
|
} else if (CompositorService.isNiri) {
|
|
targetWorkspaceId = modelData?.id;
|
|
} else if (CompositorService.isHyprland) {
|
|
targetWorkspaceId = modelData?.id;
|
|
} else if (root.isMango) {
|
|
targetWorkspaceId = modelData?.tag;
|
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
|
targetWorkspaceId = modelData?.num;
|
|
}
|
|
if (targetWorkspaceId === undefined || targetWorkspaceId === null)
|
|
return 0;
|
|
|
|
const wins = CompositorService.isNiri ? (NiriService.windows || []) : CompositorService.sortedToplevels;
|
|
const seen = {};
|
|
let groupedCount = 0;
|
|
let totalCount = 0;
|
|
|
|
for (let i = 0; i < wins.length; i++) {
|
|
const w = wins[i];
|
|
if (!w)
|
|
continue;
|
|
|
|
let winWs = null;
|
|
if (CompositorService.isNiri) {
|
|
winWs = w.workspace_id;
|
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
|
winWs = w.workspace?.num;
|
|
} else if (CompositorService.isHyprland) {
|
|
const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []);
|
|
const hyprToplevel = hyprlandToplevels.find(ht => ht.wayland === w);
|
|
winWs = hyprToplevel?.workspace?.id;
|
|
}
|
|
|
|
if (CompositorService.isMango) {
|
|
if (!(w.mangoTags || []).includes(targetWorkspaceId + 1))
|
|
continue;
|
|
} else if (winWs !== targetWorkspaceId) {
|
|
continue;
|
|
}
|
|
totalCount++;
|
|
|
|
const appKey = w.app_id || w.appId || w.class || w.windowClass || "unknown";
|
|
if (!seen[appKey]) {
|
|
seen[appKey] = true;
|
|
groupedCount++;
|
|
}
|
|
}
|
|
|
|
return (SettingsData.groupWorkspaceApps && (!isActive || SettingsData.groupActiveWorkspaceApps)) ? groupedCount : totalCount;
|
|
}
|
|
|
|
readonly property real baseWidth: root.isVertical ? (SettingsData.showWorkspaceApps ? Math.max(widgetHeight * 0.7, root.appIconSize + Theme.spacingXS * 2) : widgetHeight * 0.5) : (isActive ? Math.max(root.widgetHeight * 1.05, root.appIconSize * 1.6) : Math.max(root.widgetHeight * 0.7, root.appIconSize * 1.2))
|
|
readonly property real baseHeight: root.isVertical ? (isActive ? Math.max(root.widgetHeight * 1.05, root.appIconSize * 1.6) : Math.max(root.widgetHeight * 0.7, root.appIconSize * 1.2)) : (SettingsData.showWorkspaceApps ? Math.max(widgetHeight * 0.7, root.appIconSize + Theme.spacingXS * 2) : widgetHeight * 0.5)
|
|
readonly property bool hasWorkspaceName: SettingsData.showWorkspaceName && modelData?.name && modelData.name !== ""
|
|
readonly property bool workspaceNamesEnabled: SettingsData.showWorkspaceName && (CompositorService.isNiri || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
|
readonly property real contentImplicitWidth: appIconsLoader.item?.contentWidth ?? 0
|
|
readonly property real contentImplicitHeight: appIconsLoader.item?.contentHeight ?? 0
|
|
|
|
readonly property real iconsExtraWidth: {
|
|
if (!root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) {
|
|
const numIcons = Math.min(stableIconCount, SettingsData.maxWorkspaceIcons);
|
|
return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
|
|
}
|
|
return 0;
|
|
}
|
|
readonly property real iconsExtraHeight: {
|
|
if (root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) {
|
|
const numIcons = Math.min(stableIconCount, SettingsData.maxWorkspaceIcons);
|
|
return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
readonly property real visualWidth: {
|
|
if (contentImplicitWidth <= 0)
|
|
return baseWidth + iconsExtraWidth;
|
|
const padding = root.isVertical ? Theme.spacingXS : Theme.spacingS;
|
|
return Math.max(baseWidth + iconsExtraWidth, contentImplicitWidth + padding);
|
|
}
|
|
readonly property real visualHeight: {
|
|
if (contentImplicitHeight <= 0)
|
|
return baseHeight + iconsExtraHeight;
|
|
const padding = root.isVertical ? Theme.spacingS : Theme.spacingXS;
|
|
return Math.max(baseHeight + iconsExtraHeight, contentImplicitHeight + padding);
|
|
}
|
|
|
|
function colorFromMode(mode, fallbackColor, customColor, customFallbackColor) {
|
|
switch (mode) {
|
|
case "primary":
|
|
case "pri":
|
|
return Theme.primary;
|
|
case "primaryContainer":
|
|
return Theme.primaryContainer;
|
|
case "secondary":
|
|
case "sec":
|
|
return Theme.secondary;
|
|
case "secondaryContainer":
|
|
return Theme.secondaryContainer;
|
|
case "tertiary":
|
|
case "ter":
|
|
return Theme.tertiary;
|
|
case "tertiaryContainer":
|
|
return Theme.tertiaryContainer;
|
|
case "surfaceText":
|
|
return Theme.surfaceText;
|
|
case "s":
|
|
return Theme.surface;
|
|
case "sc":
|
|
return Theme.surfaceContainer;
|
|
case "sch":
|
|
return Theme.surfaceContainerHigh;
|
|
case "schh":
|
|
return Theme.surfaceContainerHighest;
|
|
case "error":
|
|
case "err":
|
|
return Theme.error;
|
|
case "custom":
|
|
return Theme.safeColor(customColor, customFallbackColor);
|
|
default:
|
|
return fallbackColor;
|
|
}
|
|
}
|
|
|
|
function effectiveColorMode(focusedMode, unfocusedMode) {
|
|
return root.useUnfocusedAppearance ? unfocusedMode : focusedMode;
|
|
}
|
|
|
|
function effectiveCustomColor(focusedCustom, unfocusedCustom) {
|
|
return root.useUnfocusedAppearance ? unfocusedCustom : focusedCustom;
|
|
}
|
|
|
|
readonly property color unfocusedColor: colorFromMode(effectiveColorMode(SettingsData.workspaceUnfocusedColorMode, SettingsData.workspaceUnfocusedMonitorUnfocusedColorMode), Theme.surfaceTextAlpha, effectiveCustomColor(SettingsData.workspaceUnfocusedCustomColor, SettingsData.workspaceUnfocusedMonitorUnfocusedCustomColor), Theme.surfaceTextAlpha)
|
|
|
|
readonly property color activeColor: {
|
|
const mode = effectiveColorMode(SettingsData.workspaceColorMode, SettingsData.workspaceUnfocusedMonitorColorMode);
|
|
if (mode === "none")
|
|
return unfocusedColor;
|
|
return colorFromMode(mode, Theme.primary, effectiveCustomColor(SettingsData.workspaceFocusedCustomColor, SettingsData.workspaceUnfocusedMonitorFocusedCustomColor), Theme.primary);
|
|
}
|
|
|
|
readonly property color occupiedColor: {
|
|
const mode = effectiveColorMode(SettingsData.workspaceOccupiedColorMode, SettingsData.workspaceUnfocusedMonitorOccupiedColorMode);
|
|
if (mode === "none")
|
|
return unfocusedColor;
|
|
return colorFromMode(mode, unfocusedColor, effectiveCustomColor(SettingsData.workspaceOccupiedCustomColor, SettingsData.workspaceUnfocusedMonitorOccupiedCustomColor), Theme.secondary);
|
|
}
|
|
|
|
readonly property color urgentColor: colorFromMode(effectiveColorMode(SettingsData.workspaceUrgentColorMode, SettingsData.workspaceUnfocusedMonitorUrgentColorMode), Theme.error, effectiveCustomColor(SettingsData.workspaceUrgentCustomColor, SettingsData.workspaceUnfocusedMonitorUrgentCustomColor), Theme.error)
|
|
|
|
readonly property color focusedBorderColor: colorFromMode(effectiveColorMode(SettingsData.workspaceFocusedBorderColor, SettingsData.workspaceUnfocusedMonitorBorderColor), Theme.primary, effectiveCustomColor(SettingsData.workspaceFocusedBorderCustomColor, SettingsData.workspaceUnfocusedMonitorBorderCustomColor), Theme.primary)
|
|
|
|
readonly property bool focusedBorderEnabledForMonitor: root.useUnfocusedAppearance ? SettingsData.workspaceUnfocusedMonitorBorderEnabled : SettingsData.workspaceFocusedBorderEnabled
|
|
readonly property int focusedBorderThicknessForMonitor: root.useUnfocusedAppearance ? SettingsData.workspaceUnfocusedMonitorBorderThickness : SettingsData.workspaceFocusedBorderThickness
|
|
|
|
function getContrastingIconColor(bgColor) {
|
|
const luminance = 0.299 * bgColor.r + 0.587 * bgColor.g + 0.114 * bgColor.b;
|
|
return luminance > 0.4 ? Qt.rgba(0.15, 0.15, 0.15, 1) : Qt.rgba(0.8, 0.8, 0.8, 1);
|
|
}
|
|
|
|
readonly property color quickshellIconActiveColor: getContrastingIconColor(activeColor)
|
|
readonly property color quickshellIconInactiveColor: getContrastingIconColor(unfocusedColor)
|
|
|
|
Item {
|
|
id: dragHandler
|
|
anchors.fill: parent
|
|
property bool dragging: false
|
|
property point dragStartPos: Qt.point(0, 0)
|
|
property real dragAxisOffset: 0
|
|
|
|
Connections {
|
|
target: root
|
|
function onWorkspaceListChanged() {
|
|
if (dragHandler.dragging) {
|
|
dragHandler.dragging = false;
|
|
dragHandler.dragAxisOffset = 0;
|
|
mouseArea.mousePressed = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: mouseArea
|
|
anchors.fill: parent
|
|
hoverEnabled: !isPlaceholder
|
|
cursorShape: isPlaceholder ? Qt.ArrowCursor : (dragHandler.dragging ? Qt.ClosedHandCursor : Qt.PointingHandCursor)
|
|
enabled: !isPlaceholder
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
|
|
property bool mousePressed: false
|
|
|
|
onPressed: mouse => {
|
|
if (mouse.button === Qt.LeftButton && CompositorService.isNiri && SettingsData.workspaceDragReorder && !isPlaceholder) {
|
|
mousePressed = true;
|
|
dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
|
|
}
|
|
}
|
|
|
|
onPositionChanged: mouse => {
|
|
if (!mousePressed || !CompositorService.isNiri || !SettingsData.workspaceDragReorder || isPlaceholder)
|
|
return;
|
|
|
|
if (!dragHandler.dragging) {
|
|
const distance = root.isVertical ? Math.abs(mouse.y - dragHandler.dragStartPos.y) : Math.abs(mouse.x - dragHandler.dragStartPos.x);
|
|
if (distance > 5) {
|
|
dragHandler.dragging = true;
|
|
root.dragSourceIndex = index;
|
|
root.dragTargetIndex = index;
|
|
}
|
|
}
|
|
|
|
if (!dragHandler.dragging)
|
|
return;
|
|
|
|
const rawAxisOffset = root.isVertical ? (mouse.y - dragHandler.dragStartPos.y) : (mouse.x - dragHandler.dragStartPos.x);
|
|
|
|
const itemSize = (root.isVertical ? delegateRoot.height : delegateRoot.width) + Theme.spacingS;
|
|
const maxOffsetPositive = (root.workspaceList.length - 1 - index) * itemSize;
|
|
const maxOffsetNegative = -index * itemSize;
|
|
const axisOffset = Math.max(maxOffsetNegative, Math.min(maxOffsetPositive, rawAxisOffset));
|
|
dragHandler.dragAxisOffset = axisOffset;
|
|
|
|
const slotOffset = Math.round(axisOffset / itemSize);
|
|
const newTargetIndex = Math.max(0, Math.min(root.workspaceList.length - 1, index + slotOffset));
|
|
|
|
if (newTargetIndex !== root.dragTargetIndex) {
|
|
root.dragTargetIndex = newTargetIndex;
|
|
}
|
|
}
|
|
|
|
onReleased: mouse => {
|
|
const wasDragging = dragHandler.dragging;
|
|
const didReorder = wasDragging && root.dragTargetIndex >= 0 && root.dragTargetIndex !== root.dragSourceIndex;
|
|
|
|
if (didReorder) {
|
|
const sourceWs = root.workspaceList[root.dragSourceIndex];
|
|
const targetWs = root.workspaceList[root.dragTargetIndex];
|
|
|
|
if (sourceWs && targetWs && sourceWs.id !== undefined && targetWs.idx !== undefined) {
|
|
root.suppressShiftAnimation = true;
|
|
NiriService.moveWorkspaceToIndex(sourceWs.id, targetWs.idx);
|
|
Qt.callLater(() => root.suppressShiftAnimation = false);
|
|
}
|
|
}
|
|
|
|
mousePressed = false;
|
|
dragHandler.dragging = false;
|
|
dragHandler.dragAxisOffset = 0;
|
|
root.dragSourceIndex = -1;
|
|
root.dragTargetIndex = -1;
|
|
|
|
if (wasDragging || isPlaceholder)
|
|
return;
|
|
|
|
if (mouse.button === Qt.LeftButton) {
|
|
if (root.useExtWorkspace) {
|
|
if (typeof modelData?.activate === "function")
|
|
modelData.activate();
|
|
} else if (CompositorService.isNiri) {
|
|
if (modelData && modelData.id !== undefined) {
|
|
NiriService.switchToWorkspace(modelData.id);
|
|
}
|
|
} else if (CompositorService.isHyprland && modelData?.id) {
|
|
HyprlandService.focusWorkspace(modelData.id);
|
|
} else if (root.isMango && modelData?.tag !== undefined) {
|
|
MangoService.switchToTag(root.screenName, modelData.tag);
|
|
} else if ((CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) && modelData?.num) {
|
|
try {
|
|
I3.dispatch(`workspace number ${modelData.num}`);
|
|
} catch (_) {}
|
|
}
|
|
} else if (mouse.button === Qt.RightButton) {
|
|
if (CompositorService.isNiri) {
|
|
NiriService.toggleOverview();
|
|
} else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
|
|
root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen;
|
|
} else if (root.isMango && modelData?.tag !== undefined) {
|
|
MangoService.toggleTag(root.screenName, modelData.tag);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: dataUpdateTimer
|
|
interval: 50
|
|
onTriggered: {
|
|
if (isPlaceholder) {
|
|
delegateRoot.loadedWorkspaceData = null;
|
|
delegateRoot.loadedIcons = [];
|
|
delegateRoot.loadedIsUrgent = false;
|
|
return;
|
|
}
|
|
|
|
var wsData = null;
|
|
if (root.useExtWorkspace) {
|
|
wsData = modelData;
|
|
} else if (CompositorService.isNiri) {
|
|
wsData = modelData || null;
|
|
} else if (CompositorService.isHyprland) {
|
|
wsData = modelData;
|
|
} else if (root.isMango) {
|
|
wsData = modelData;
|
|
} else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
|
wsData = modelData;
|
|
}
|
|
delegateRoot.loadedWorkspaceData = wsData;
|
|
if (CompositorService.isNiri) {
|
|
const workspaceId = wsData?.id;
|
|
delegateRoot.loadedIsUrgent = workspaceId ? NiriService.windows.some(w => w.workspace_id === workspaceId && w.is_urgent) : false;
|
|
} else {
|
|
delegateRoot.loadedIsUrgent = wsData?.urgent ?? false;
|
|
}
|
|
|
|
if (SettingsData.showWorkspaceApps) {
|
|
if (root.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
|
|
delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData);
|
|
} else if (CompositorService.isNiri) {
|
|
delegateRoot.loadedIcons = root.getWorkspaceIcons(isPlaceholder ? null : modelData);
|
|
} else {
|
|
delegateRoot.loadedIcons = root.getWorkspaceIcons(CompositorService.isHyprland ? modelData : (modelData === -1 ? null : modelData));
|
|
}
|
|
} else {
|
|
delegateRoot.loadedIcons = [];
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateAllData() {
|
|
dataUpdateTimer.restart();
|
|
}
|
|
|
|
width: root.isVertical ? root.widgetHeight : visualWidth
|
|
height: root.isVertical ? visualHeight : root.widgetHeight
|
|
|
|
Behavior on width {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Behavior on height {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: focusedBorderRing
|
|
x: root.isVertical ? (root.widgetHeight - width) / 2 : (parent.width - width) / 2
|
|
y: root.isVertical ? (parent.height - height) / 2 : (root.widgetHeight - height) / 2
|
|
width: {
|
|
const borderWidth = (delegateRoot.focusedBorderEnabledForMonitor && isActive && !isPlaceholder) ? delegateRoot.focusedBorderThicknessForMonitor : 0;
|
|
return delegateRoot.visualWidth + borderWidth * 2;
|
|
}
|
|
height: {
|
|
const borderWidth = (delegateRoot.focusedBorderEnabledForMonitor && isActive && !isPlaceholder) ? delegateRoot.focusedBorderThicknessForMonitor : 0;
|
|
return delegateRoot.visualHeight + borderWidth * 2;
|
|
}
|
|
radius: Theme.cornerRadius
|
|
color: "transparent"
|
|
border.width: (delegateRoot.focusedBorderEnabledForMonitor && isActive && !isPlaceholder) ? delegateRoot.focusedBorderThicknessForMonitor : 0
|
|
border.color: (delegateRoot.focusedBorderEnabledForMonitor && isActive && !isPlaceholder) ? focusedBorderColor : "transparent"
|
|
|
|
Behavior on width {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Behavior on height {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Behavior on border.width {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Behavior on border.color {
|
|
ColorAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: visualContent
|
|
width: delegateRoot.visualWidth
|
|
height: delegateRoot.visualHeight
|
|
x: root.isVertical ? (root.widgetHeight - width) / 2 : (parent.width - width) / 2
|
|
y: root.isVertical ? (parent.height - height) / 2 : (root.widgetHeight - height) / 2
|
|
radius: Theme.cornerRadius
|
|
color: isActive ? activeColor : isUrgent ? urgentColor : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.withAlpha(unfocusedColor, 0.7) : isOccupied ? occupiedColor : unfocusedColor
|
|
opacity: dragHandler.dragging ? 0.8 : 1.0
|
|
|
|
border.width: dragHandler.dragging ? 2 : (isUrgent ? 2 : (isDropTarget ? 2 : 0))
|
|
border.color: dragHandler.dragging ? Theme.primary : (isUrgent ? urgentColor : (isDropTarget ? Theme.primary : "transparent"))
|
|
|
|
transform: Translate {
|
|
x: root.isVertical ? 0 : (dragHandler.dragging ? dragHandler.dragAxisOffset : 0)
|
|
y: root.isVertical ? (dragHandler.dragging ? dragHandler.dragAxisOffset : 0) : 0
|
|
}
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Theme.shortDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Behavior on width {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Behavior on height {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Behavior on color {
|
|
ColorAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Behavior on border.width {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Behavior on border.color {
|
|
ColorAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: appIconsLoader
|
|
anchors.fill: parent
|
|
active: SettingsData.showWorkspaceApps || SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName || loadedHasIcon
|
|
sourceComponent: Item {
|
|
id: contentRoot
|
|
readonly property real contentWidth: contentRow.item?.implicitWidth ?? 0
|
|
readonly property real contentHeight: contentRow.item?.implicitHeight ?? 0
|
|
|
|
Loader {
|
|
id: contentRow
|
|
anchors.centerIn: parent
|
|
sourceComponent: root.isVertical ? columnLayout : rowLayout
|
|
}
|
|
|
|
Component {
|
|
id: rowLayout
|
|
Row {
|
|
spacing: 4
|
|
visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName || loadedHasIcon
|
|
|
|
Item {
|
|
visible: loadedHasIcon && loadedIconData?.type === "icon"
|
|
width: wsIcon.width
|
|
height: root.appIconSize
|
|
|
|
DankIcon {
|
|
id: wsIcon
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
name: loadedIconData?.value ?? ""
|
|
size: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
|
|
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
|
weight: (isActive && !isPlaceholder) ? 500 : 400
|
|
}
|
|
}
|
|
|
|
Item {
|
|
visible: loadedHasIcon && loadedIconData?.type === "text"
|
|
width: wsText.implicitWidth
|
|
height: root.appIconSize
|
|
|
|
StyledText {
|
|
id: wsText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: loadedIconData?.value ?? ""
|
|
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
|
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
|
|
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
|
}
|
|
}
|
|
|
|
Item {
|
|
visible: ((SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon) || (loadedHasIcon && SettingsData.showWorkspaceName && hasWorkspaceName)
|
|
width: wsIndexText.implicitWidth
|
|
height: root.appIconSize
|
|
|
|
StyledText {
|
|
id: wsIndexText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: loadedHasIcon ? (modelData?.name ?? "") : root.getWorkspaceIndex(modelData, index)
|
|
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
|
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
|
|
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: ScriptModel {
|
|
values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
|
|
}
|
|
delegate: Item {
|
|
width: root.appIconSize
|
|
height: root.appIconSize
|
|
readonly property bool appHighlightActive: SettingsData.workspaceActiveAppHighlightEnabled && modelData.active
|
|
readonly property color appBorderColor: appHighlightActive ? focusedBorderColor : Theme.primarySelected
|
|
readonly property color appGlyphColor: appHighlightActive ? focusedBorderColor : Theme.primary
|
|
readonly property real appOpacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
|
|
|
IconImage {
|
|
id: rowAppIcon
|
|
anchors.fill: parent
|
|
source: modelData.icon || ""
|
|
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
|
visible: !modelData.isQuickshell && !modelData.isSteamApp && status === Image.Ready
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
visible: !modelData.isQuickshell && !modelData.isSteamApp && rowAppIcon.status !== Image.Ready
|
|
color: Theme.surfaceContainer
|
|
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
|
border.width: 1
|
|
border.color: appBorderColor
|
|
opacity: appOpacity
|
|
|
|
StyledText {
|
|
anchors.centerIn: parent
|
|
text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
|
|
font.pixelSize: parent.width * 0.45
|
|
color: appGlyphColor
|
|
font.weight: Font.Bold
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
visible: !modelData.isQuickshell && modelData.isSteamApp && rowSteamIcon.status !== Image.Ready
|
|
color: Theme.surfaceContainer
|
|
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
|
border.width: 1
|
|
border.color: appBorderColor
|
|
opacity: appOpacity
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
size: parent.width * 0.7
|
|
name: "sports_esports"
|
|
color: appGlyphColor
|
|
}
|
|
}
|
|
|
|
IconImage {
|
|
anchors.fill: parent
|
|
source: modelData.icon
|
|
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
|
visible: modelData.isQuickshell
|
|
layer.enabled: true
|
|
layer.effect: MultiEffect {
|
|
saturation: 0
|
|
colorization: 1
|
|
colorizationColor: appHighlightActive ? focusedBorderColor : (isActive ? quickshellIconActiveColor : quickshellIconInactiveColor)
|
|
}
|
|
}
|
|
|
|
IconImage {
|
|
id: rowSteamIcon
|
|
anchors.fill: parent
|
|
source: modelData.icon
|
|
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
|
visible: modelData.isSteamApp && modelData.icon
|
|
}
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
size: root.appIconSize
|
|
name: "sports_esports"
|
|
color: appHighlightActive ? focusedBorderColor : Theme.widgetTextColor
|
|
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
|
|
visible: modelData.isSteamApp && !modelData.icon
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
visible: (rowAppIcon.visible || rowSteamIcon.visible || modelData.isQuickshell) && appHighlightActive
|
|
color: "transparent"
|
|
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
|
border.width: 1
|
|
border.color: focusedBorderColor
|
|
z: 1
|
|
}
|
|
|
|
MouseArea {
|
|
id: rowAppMouseArea
|
|
anchors.fill: parent
|
|
enabled: isActive
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
const winId = modelData.windowId;
|
|
if (!winId)
|
|
return;
|
|
if (CompositorService.isHyprland) {
|
|
HyprlandService.focusWindow(winId);
|
|
} else if (CompositorService.isNiri) {
|
|
NiriService.focusWindow(winId);
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
visible: modelData.count > 1 && !isActive
|
|
width: root.appIconSize * 0.67
|
|
height: root.appIconSize * 0.67
|
|
radius: root.appIconSize * 0.33
|
|
color: "black"
|
|
border.color: "white"
|
|
border.width: 1
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
z: 2
|
|
|
|
StyledText {
|
|
anchors.centerIn: parent
|
|
text: modelData.count
|
|
font.pixelSize: root.appIconSize * 0.44
|
|
color: "white"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: columnLayout
|
|
Column {
|
|
spacing: 4
|
|
visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName || loadedHasIcon
|
|
|
|
DankIcon {
|
|
visible: loadedHasIcon && loadedIconData?.type === "icon"
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
name: loadedIconData?.value ?? ""
|
|
size: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
|
|
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
|
weight: (isActive && !isPlaceholder) ? 500 : 400
|
|
}
|
|
|
|
StyledText {
|
|
visible: loadedHasIcon && loadedIconData?.type === "text"
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: loadedIconData?.value ?? ""
|
|
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
|
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
|
|
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
|
}
|
|
|
|
StyledText {
|
|
visible: ((SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon) || (loadedHasIcon && SettingsData.showWorkspaceName && hasWorkspaceName)
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: loadedHasIcon ? (root.isVertical ? (modelData?.name ?? "").charAt(0) : (modelData?.name ?? "")) : root.getWorkspaceIndex(modelData, index)
|
|
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
|
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
|
|
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
|
}
|
|
|
|
Repeater {
|
|
model: ScriptModel {
|
|
values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
|
|
}
|
|
delegate: Item {
|
|
width: root.appIconSize
|
|
height: root.appIconSize
|
|
readonly property bool appHighlightActive: SettingsData.workspaceActiveAppHighlightEnabled && modelData.active
|
|
readonly property color appBorderColor: appHighlightActive ? focusedBorderColor : Theme.primarySelected
|
|
readonly property color appGlyphColor: appHighlightActive ? focusedBorderColor : Theme.primary
|
|
readonly property real appOpacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
|
|
|
IconImage {
|
|
id: colAppIcon
|
|
anchors.fill: parent
|
|
source: modelData.icon || ""
|
|
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
|
visible: !modelData.isQuickshell && !modelData.isSteamApp && status === Image.Ready
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
visible: !modelData.isQuickshell && !modelData.isSteamApp && colAppIcon.status !== Image.Ready
|
|
color: Theme.surfaceContainer
|
|
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
|
border.width: 1
|
|
border.color: appBorderColor
|
|
opacity: appOpacity
|
|
|
|
StyledText {
|
|
anchors.centerIn: parent
|
|
text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
|
|
font.pixelSize: parent.width * 0.45
|
|
color: appGlyphColor
|
|
font.weight: Font.Bold
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
visible: !modelData.isQuickshell && modelData.isSteamApp && colSteamIcon.status !== Image.Ready
|
|
color: Theme.surfaceContainer
|
|
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
|
border.width: 1
|
|
border.color: appBorderColor
|
|
opacity: appOpacity
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
size: parent.width * 0.7
|
|
name: "sports_esports"
|
|
color: appGlyphColor
|
|
}
|
|
}
|
|
|
|
IconImage {
|
|
anchors.fill: parent
|
|
source: modelData.icon
|
|
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
|
visible: modelData.isQuickshell
|
|
layer.enabled: true
|
|
layer.effect: MultiEffect {
|
|
saturation: 0
|
|
colorization: 1
|
|
colorizationColor: appHighlightActive ? focusedBorderColor : (isActive ? quickshellIconActiveColor : quickshellIconInactiveColor)
|
|
}
|
|
}
|
|
|
|
IconImage {
|
|
id: colSteamIcon
|
|
anchors.fill: parent
|
|
source: modelData.icon
|
|
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
|
visible: modelData.isSteamApp && modelData.icon
|
|
}
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
size: root.appIconSize
|
|
name: "sports_esports"
|
|
color: appHighlightActive ? focusedBorderColor : Theme.widgetTextColor
|
|
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
|
|
visible: modelData.isSteamApp && !modelData.icon
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
visible: (colAppIcon.visible || colSteamIcon.visible || modelData.isQuickshell) && appHighlightActive
|
|
color: "transparent"
|
|
radius: Theme.cornerRadius * (root.appIconSize / 40)
|
|
border.width: 1
|
|
border.color: focusedBorderColor
|
|
z: 1
|
|
}
|
|
|
|
MouseArea {
|
|
id: colAppMouseArea
|
|
anchors.fill: parent
|
|
enabled: isActive
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
const winId = modelData.windowId;
|
|
if (!winId)
|
|
return;
|
|
if (CompositorService.isHyprland) {
|
|
HyprlandService.focusWindow(winId);
|
|
} else if (CompositorService.isNiri) {
|
|
NiriService.focusWindow(winId);
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
visible: modelData.count > 1 && !isActive
|
|
width: root.appIconSize * 0.67
|
|
height: root.appIconSize * 0.67
|
|
radius: root.appIconSize * 0.33
|
|
color: "black"
|
|
border.color: "white"
|
|
border.width: 1
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
z: 2
|
|
|
|
StyledText {
|
|
anchors.centerIn: parent
|
|
text: modelData.count
|
|
font.pixelSize: root.appIconSize * 0.44
|
|
color: "white"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: updateAllData()
|
|
|
|
Connections {
|
|
target: CompositorService
|
|
function onSortedToplevelsChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
}
|
|
Connections {
|
|
target: root
|
|
function onCurrentWorkspaceChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
}
|
|
Connections {
|
|
target: NiriService
|
|
enabled: CompositorService.isNiri
|
|
function onAllWorkspacesChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
function onWindowUrgentChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
function onWindowsChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
}
|
|
Connections {
|
|
target: SettingsData
|
|
function onShowWorkspaceAppsChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
function onWorkspaceNameIconsChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
function onAppIdSubstitutionsChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
function onGroupWorkspaceAppsChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
function onGroupActiveWorkspaceAppsChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
}
|
|
Connections {
|
|
target: MangoService
|
|
enabled: root.isMango
|
|
function onStateChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
}
|
|
Connections {
|
|
target: Hyprland.workspaces
|
|
enabled: CompositorService.isHyprland
|
|
function onValuesChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
}
|
|
Connections {
|
|
target: I3.workspaces
|
|
enabled: (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
|
|
function onValuesChanged() {
|
|
delegateRoot.updateAllData();
|
|
}
|
|
}
|
|
property var _extWindowsetsTrigger: root.useExtWorkspace ? WindowManager.windowsets : null
|
|
on_ExtWindowsetsTriggerChanged: {
|
|
if (root.useExtWorkspace)
|
|
delegateRoot.updateAllData();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
_updateBlurRegistration();
|
|
}
|
|
|
|
property bool _blurRegistered: false
|
|
readonly property bool _shouldBlur: BlurService.enabled && blurBarWindow && blurBarWindow.registerBlurWidget && !(barConfig?.noBackground ?? false) && root.visible && root.width > 0
|
|
|
|
on_ShouldBlurChanged: _updateBlurRegistration()
|
|
|
|
function _updateBlurRegistration() {
|
|
if (_shouldBlur && !_blurRegistered) {
|
|
blurBarWindow.registerBlurWidget(visualBackground);
|
|
_blurRegistered = true;
|
|
} else if (!_shouldBlur && _blurRegistered) {
|
|
if (blurBarWindow && blurBarWindow.unregisterBlurWidget)
|
|
blurBarWindow.unregisterBlurWidget(visualBackground);
|
|
_blurRegistered = false;
|
|
}
|
|
}
|
|
|
|
Component.onDestruction: {
|
|
if (_blurRegistered && blurBarWindow && blurBarWindow.unregisterBlurWidget)
|
|
blurBarWindow.unregisterBlurWidget(visualBackground);
|
|
}
|
|
}
|