1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

workspaces: add color options, add focus follows monitor, remove

per-monitor option (was misleading)
relevant to #1207
This commit is contained in:
bbedward
2026-01-09 14:10:57 -05:00
parent 7f0181b310
commit 4b46d022af
23 changed files with 989 additions and 280 deletions

View File

@@ -39,7 +39,7 @@ Item {
function getRealWorkspaces() {
if (CompositorService.isNiri) {
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) {
if (!barWindow.screenName || SettingsData.workspaceFollowFocus) {
return NiriService.getCurrentOutputWorkspaceNumbers();
}
const workspaces = NiriService.allWorkspaces.filter(ws => ws.output === barWindow.screenName).map(ws => ws.idx + 1);
@@ -47,7 +47,7 @@ Item {
} else if (CompositorService.isHyprland) {
const workspaces = Hyprland.workspaces?.values || [];
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) {
if (!barWindow.screenName || SettingsData.workspaceFollowFocus) {
const sorted = workspaces.slice().sort((a, b) => a.id - b.id);
const filtered = sorted.filter(ws => ws.id > -1);
return filtered.length > 0 ? filtered : [
@@ -91,7 +91,7 @@ Item {
}
];
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) {
if (!barWindow.screenName || SettingsData.workspaceFollowFocus) {
return workspaces.slice().sort((a, b) => a.num - b.num);
}
@@ -107,7 +107,7 @@ Item {
function getCurrentWorkspace() {
if (CompositorService.isNiri) {
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) {
if (!barWindow.screenName || SettingsData.workspaceFollowFocus) {
return NiriService.getCurrentWorkspaceNumber();
}
const activeWs = NiriService.allWorkspaces.find(ws => ws.output === barWindow.screenName && ws.is_active);
@@ -125,7 +125,7 @@ Item {
const activeTags = DwlService.getActiveTags(barWindow.screenName);
return activeTags.length > 0 ? activeTags[0] : 0;
} else if (CompositorService.isSway || CompositorService.isScroll) {
if (!barWindow.screenName || !SettingsData.workspacesPerMonitor) {
if (!barWindow.screenName || SettingsData.workspaceFollowFocus) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
return focusedWs ? focusedWs.num : 1;
}

View File

@@ -24,6 +24,26 @@ Item {
return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
}
readonly property string effectiveScreenName: {
if (!SettingsData.workspaceFollowFocus)
return root.screenName;
switch (CompositorService.compositor) {
case "niri":
return NiriService.currentOutput || root.screenName;
case "hyprland":
return Hyprland.focusedWorkspace?.monitor?.name || root.screenName;
case "dwl":
return DwlService.activeOutput || root.screenName;
case "sway":
case "scroll":
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
return focusedWs?.monitor?.name || root.screenName;
default:
return root.screenName;
}
}
readonly property bool useExtWorkspace: DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway && !CompositorService.isScroll && ExtWorkspaceService.extWorkspaceAvailable)
Connections {
@@ -94,7 +114,7 @@ Item {
}
];
if (!root.screenName || !SettingsData.workspacesPerMonitor) {
if (!root.screenName || SettingsData.workspaceFollowFocus) {
return workspaces.slice().sort((a, b) => a.num - b.num);
}
@@ -107,7 +127,7 @@ Item {
}
function getSwayActiveWorkspace() {
if (!root.screenName || !SettingsData.workspacesPerMonitor) {
if (!root.screenName || SettingsData.workspaceFollowFocus) {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
return focusedWs ? focusedWs.num : 1;
}
@@ -137,7 +157,7 @@ Item {
];
}
if (!root.screenName || !SettingsData.workspacesPerMonitor) {
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);
@@ -163,7 +183,7 @@ Item {
}
function getHyprlandActiveWorkspace() {
if (!root.screenName || !SettingsData.workspacesPerMonitor) {
if (!root.screenName || SettingsData.workspaceFollowFocus) {
return Hyprland.focusedWorkspace?.id || 1;
}
@@ -183,7 +203,7 @@ Item {
if (wsNumber <= 0) {
return [];
}
const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNumber && w.output === root.screenName);
const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNumber && w.output === root.effectiveScreenName);
if (!workspace) {
return [];
}
@@ -211,7 +231,7 @@ Item {
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false;
} else if (CompositorService.isDwl) {
const output = DwlService.getOutputState(root.screenName);
const output = DwlService.getOutputState(root.effectiveScreenName);
if (output && output.tags) {
const tag = output.tags.find(t => t.tag === targetWorkspaceId);
isActiveWs = tag ? (tag.state === 1) : false;
@@ -308,7 +328,7 @@ Item {
}
let workspaces;
if (!root.screenName || !SettingsData.workspacesPerMonitor) {
if (!root.screenName || SettingsData.workspaceFollowFocus) {
workspaces = NiriService.getCurrentOutputWorkspaceNumbers();
} else {
const displayWorkspaces = NiriService.allWorkspaces.filter(ws => ws.output === root.screenName).map(ws => ws.idx + 1);
@@ -320,7 +340,7 @@ Item {
}
return workspaces.filter(wsNum => {
const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNum && w.output === root.screenName);
const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNum && w.output === root.effectiveScreenName);
if (!workspace)
return false;
if (workspace.is_active)
@@ -334,7 +354,7 @@ Item {
return 1;
}
if (!root.screenName || !SettingsData.workspacesPerMonitor) {
if (!root.screenName || SettingsData.workspaceFollowFocus) {
return NiriService.getCurrentWorkspaceNumber();
}
@@ -343,14 +363,13 @@ Item {
}
function getDwlTags() {
if (!DwlService.dwlAvailable) {
if (!DwlService.dwlAvailable)
return [];
}
const output = DwlService.getOutputState(root.screenName);
if (!output || !output.tags || output.tags.length === 0) {
const targetScreen = root.effectiveScreenName;
const output = DwlService.getOutputState(targetScreen);
if (!output || !output.tags || output.tags.length === 0)
return [];
}
if (SettingsData.dwlShowAllTags) {
return output.tags.map(tag => ({
@@ -361,7 +380,7 @@ Item {
}));
}
const visibleTagIndices = DwlService.getVisibleTags(root.screenName);
const visibleTagIndices = DwlService.getVisibleTags(targetScreen);
return visibleTagIndices.map(tagIndex => {
const tagData = output.tags.find(t => t.tag === tagIndex);
return {
@@ -374,12 +393,10 @@ Item {
}
function getDwlActiveTags() {
if (!DwlService.dwlAvailable) {
if (!DwlService.dwlAvailable)
return [];
}
const activeTags = DwlService.getActiveTags(root.screenName);
return activeTags;
return DwlService.getActiveTags(root.effectiveScreenName);
}
function getExtWorkspaceWorkspaces() {
@@ -790,6 +807,68 @@ Item {
readonly property real visualWidth: baseWidth + iconsExtraWidth
readonly property real visualHeight: baseHeight + iconsExtraHeight
readonly property color unfocusedColor: {
switch (SettingsData.workspaceUnfocusedColorMode) {
case "s":
return Theme.surface;
case "sc":
return Theme.surfaceContainer;
case "sch":
return Theme.surfaceContainerHigh;
default:
return Theme.surfaceTextAlpha;
}
}
readonly property color activeColor: {
switch (SettingsData.workspaceColorMode) {
case "s":
return Theme.surface;
case "sc":
return Theme.surfaceContainer;
case "sch":
return Theme.surfaceContainerHigh;
case "none":
return unfocusedColor;
default:
return Theme.primary;
}
}
readonly property color urgentColor: {
switch (SettingsData.workspaceUrgentColorMode) {
case "primary":
return Theme.primary;
case "secondary":
return Theme.secondary;
case "s":
return Theme.surface;
case "sc":
return Theme.surfaceContainer;
default:
return Theme.error;
}
}
readonly property color focusedBorderColor: {
switch (SettingsData.workspaceFocusedBorderColor) {
case "surfaceText":
return Theme.surfaceText;
case "secondary":
return Theme.secondary;
default:
return Theme.primary;
}
}
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)
MouseArea {
id: mouseArea
anchors.fill: parent
@@ -850,7 +929,7 @@ Item {
if (root.useExtWorkspace) {
wsData = modelData;
} else if (CompositorService.isNiri) {
wsData = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.screenName) || null;
wsData = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName) || null;
} else if (CompositorService.isHyprland) {
wsData = modelData;
} else if (CompositorService.isDwl) {
@@ -892,16 +971,61 @@ Item {
width: root.isVertical ? root.barThickness : visualWidth
height: root.isVertical ? visualHeight : root.barThickness
Rectangle {
id: focusedBorderRing
anchors.centerIn: parent
width: {
const borderWidth = (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0;
return delegateRoot.visualWidth + borderWidth * 2;
}
height: {
const borderWidth = (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0;
return delegateRoot.visualHeight + borderWidth * 2;
}
radius: Theme.cornerRadius
color: "transparent"
border.width: (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0
border.color: (SettingsData.workspaceFocusedBorderEnabled && 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
anchors.centerIn: parent
radius: Theme.cornerRadius
color: isActive ? Theme.primary : isUrgent ? Theme.error : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.withAlpha(Theme.surfaceText, 0.45) : Theme.surfaceTextAlpha
color: isActive ? activeColor : isUrgent ? urgentColor : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.withAlpha(unfocusedColor, 0.7) : unfocusedColor
border.width: isUrgent ? 2 : 0
border.color: isUrgent ? Theme.error : Theme.withAlpha(Theme.error, 0)
border.color: isUrgent ? urgentColor : "transparent"
Behavior on width {
NumberAnimation {
@@ -1025,7 +1149,7 @@ Item {
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: isActive ? Theme.primaryContainer : Theme.primary
colorizationColor: isActive ? quickshellIconActiveColor : quickshellIconInactiveColor
}
}
@@ -1128,7 +1252,7 @@ Item {
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: isActive ? Theme.primaryContainer : Theme.primary
colorizationColor: isActive ? quickshellIconActiveColor : quickshellIconInactiveColor
}
}

View File

@@ -1,4 +1,4 @@
pragma ComponentBehavior
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell

View File

@@ -114,12 +114,13 @@ Item {
}
SettingsToggleRow {
settingKey: "workspacesPerMonitor"
tags: ["workspace", "per-monitor", "multi-monitor"]
text: I18n.tr("Per-Monitor Workspaces")
description: I18n.tr("Show only workspaces belonging to each specific monitor.")
checked: SettingsData.workspacesPerMonitor
onToggled: checked => SettingsData.set("workspacesPerMonitor", checked)
settingKey: "workspaceFollowFocus"
tags: ["workspace", "focus", "follow", "monitor"]
text: I18n.tr("Follow Monitor Focus")
description: I18n.tr("Show workspaces of the currently focused monitor")
checked: SettingsData.workspaceFollowFocus
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll
onToggled: checked => SettingsData.set("workspaceFollowFocus", checked)
}
SettingsToggleRow {
@@ -153,6 +154,189 @@ Item {
}
}
SettingsCard {
width: parent.width
iconName: "palette"
title: I18n.tr("Workspace Appearance")
settingKey: "workspaceAppearance"
SettingsButtonGroupRow {
text: I18n.tr("Focused Color")
model: ["pri", "s", "sc", "sch", "none"]
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceColorMode) {
case "s":
return 1;
case "sc":
return 2;
case "sch":
return 3;
case "none":
return 4;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["default", "s", "sc", "sch", "none"];
SettingsData.set("workspaceColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
}
SettingsButtonGroupRow {
text: I18n.tr("Unfocused Color")
model: ["def", "s", "sc", "sch"]
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceUnfocusedColorMode) {
case "s":
return 1;
case "sc":
return 2;
case "sch":
return 3;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["default", "s", "sc", "sch"];
SettingsData.set("workspaceUnfocusedColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll
}
SettingsButtonGroupRow {
text: I18n.tr("Urgent Color")
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll
model: ["err", "pri", "sec", "s", "sc"]
buttonHeight: 22
minButtonWidth: 36
buttonPadding: Theme.spacingS
checkIconSize: Theme.iconSizeSmall - 2
textSize: Theme.fontSizeSmall - 1
spacing: 1
currentIndex: {
switch (SettingsData.workspaceUrgentColorMode) {
case "primary":
return 1;
case "secondary":
return 2;
case "s":
return 3;
case "sc":
return 4;
default:
return 0;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
const modes = ["default", "primary", "secondary", "s", "sc"];
SettingsData.set("workspaceUrgentColorMode", modes[index]);
}
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
}
SettingsToggleRow {
settingKey: "workspaceFocusedBorderEnabled"
tags: ["workspace", "border", "outline", "focused", "ring"]
text: I18n.tr("Focused Border")
description: I18n.tr("Show an outline ring around the focused workspace indicator")
checked: SettingsData.workspaceFocusedBorderEnabled
onToggled: checked => SettingsData.set("workspaceFocusedBorderEnabled", checked)
}
Column {
width: parent.width
spacing: Theme.spacingS
visible: SettingsData.workspaceFocusedBorderEnabled
leftPadding: Theme.spacingM
SettingsButtonGroupRow {
width: parent.width - parent.leftPadding
text: I18n.tr("Border Color")
model: [I18n.tr("Surface"), I18n.tr("Secondary"), I18n.tr("Primary")]
currentIndex: {
switch (SettingsData.workspaceFocusedBorderColor) {
case "surfaceText":
return 0;
case "secondary":
return 1;
case "primary":
return 2;
default:
return 2;
}
}
onSelectionChanged: (index, selected) => {
if (!selected)
return;
let newColor = "primary";
switch (index) {
case 0:
newColor = "surfaceText";
break;
case 1:
newColor = "secondary";
break;
case 2:
newColor = "primary";
break;
}
SettingsData.set("workspaceFocusedBorderColor", newColor);
}
}
SettingsSliderRow {
width: parent.width - parent.leftPadding
text: I18n.tr("Thickness")
value: SettingsData.workspaceFocusedBorderThickness
minimum: 1
maximum: 6
unit: "px"
defaultValue: 2
onSliderValueChanged: newValue => SettingsData.set("workspaceFocusedBorderThickness", newValue)
}
}
}
SettingsCard {
width: parent.width
iconName: "label"