1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-27 13:35:18 -04:00

feat(bar): separate workspace appearance for unfocused displays (#2687)

* 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.
This commit is contained in:
Maddison Cohodas
2026-06-26 20:34:24 -07:00
committed by GitHub
parent 03d86f78f4
commit 48f6a0c632
8 changed files with 432 additions and 126 deletions
@@ -1,6 +1,7 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Settings.Widgets
SettingsCard {
@@ -9,6 +10,7 @@ SettingsCard {
iconName: "palette"
title: I18n.tr("Workspace Appearance")
settingKey: "workspaceAppearance"
tags: ["workspace", "focused", "color", "custom"]
collapsible: true
expanded: false
@@ -184,120 +186,182 @@ SettingsCard {
readonly property bool workspaceStateColorsVisible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
readonly property bool urgentWorkspaceColorsVisible: workspaceStateColorsVisible || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
ColorDropdownRow {
text: I18n.tr("Focused Color")
settingKey: "workspaceColorMode"
tags: ["workspace", "focused", "color", "custom"]
options: root.focusedColorOptions
currentMode: SettingsData.workspaceColorMode
customColor: SettingsData.workspaceFocusedCustomColor || "#6750A4"
onModeSelected: mode => SettingsData.set("workspaceColorMode", mode)
onCustomColorSelected: selectedColor => SettingsData.set("workspaceFocusedCustomColor", selectedColor.toString())
function isFocusedAppearanceSection(section) {
return ["workspaceAppearance", "workspaceColorMode", "workspaceOccupiedColorMode", "workspaceUnfocusedColorMode", "workspaceUrgentColorMode", "workspaceFocusedBorderEnabled", "workspaceFocusedBorderColor", "workspaceFocusedBorderThickness"].includes(section);
}
Rectangle {
Item {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
}
height: workspaceTabBar.height + Theme.spacingM
ColorDropdownRow {
text: I18n.tr("Occupied Color")
settingKey: "workspaceOccupiedColorMode"
tags: ["workspace", "occupied", "color", "custom"]
visible: root.workspaceStateColorsVisible
options: root.occupiedColorOptions
currentMode: SettingsData.workspaceOccupiedColorMode
customColor: SettingsData.workspaceOccupiedCustomColor || "#625B71"
onModeSelected: mode => SettingsData.set("workspaceOccupiedColorMode", mode)
onCustomColorSelected: selectedColor => SettingsData.set("workspaceOccupiedCustomColor", selectedColor.toString())
}
DankTabBar {
id: workspaceTabBar
width: parent.width
tabHeight: 44
showIcons: false
model: [({
"text": I18n.tr("Focused Display", "workspace appearance tab")
}), ({
"text": I18n.tr("Unfocused Display(s)", "workspace appearance tab")
})]
onTabClicked: index => currentIndex = index
Component.onCompleted: Qt.callLater(updateIndicator)
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
}
Connections {
target: SettingsSearchService
ColorDropdownRow {
text: I18n.tr("Unfocused Color")
settingKey: "workspaceUnfocusedColorMode"
tags: ["workspace", "unfocused", "color", "custom"]
options: root.unfocusedColorOptions
defaultColor: Theme.surfaceText
currentMode: SettingsData.workspaceUnfocusedColorMode
customColor: SettingsData.workspaceUnfocusedCustomColor || "#49454E"
onModeSelected: mode => SettingsData.set("workspaceUnfocusedColorMode", mode)
onCustomColorSelected: selectedColor => SettingsData.set("workspaceUnfocusedCustomColor", selectedColor.toString())
}
function onTargetSectionChanged() {
const section = SettingsSearchService.targetSection;
if (!section)
return;
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
}
if (section.startsWith("workspaceUnfocusedMonitor")) {
root.expanded = true;
workspaceTabBar.currentIndex = 1;
} else if (root.isFocusedAppearanceSection(section)) {
root.expanded = true;
workspaceTabBar.currentIndex = 0;
} else {
return;
}
ColorDropdownRow {
text: I18n.tr("Urgent Color")
settingKey: "workspaceUrgentColorMode"
tags: ["workspace", "urgent", "color", "custom"]
visible: root.urgentWorkspaceColorsVisible
options: root.urgentColorOptions
defaultColor: Theme.error
currentMode: SettingsData.workspaceUrgentColorMode
customColor: SettingsData.workspaceUrgentCustomColor || "#B3261E"
onModeSelected: mode => SettingsData.set("workspaceUrgentColorMode", mode)
onCustomColorSelected: selectedColor => SettingsData.set("workspaceUrgentCustomColor", selectedColor.toString())
}
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)
Qt.callLater(workspaceTabBar.updateIndicator);
}
}
}
}
Column {
id: focusedTab
width: parent.width
spacing: Theme.spacingS
visible: SettingsData.workspaceFocusedBorderEnabled
leftPadding: Theme.spacingM
spacing: Theme.spacingM
visible: workspaceTabBar.currentIndex === 0
ColorDropdownRow {
width: parent.width - parent.leftPadding
text: I18n.tr("Border Color")
settingKey: "workspaceFocusedBorderColor"
tags: ["workspace", "focused", "border", "color", "custom"]
options: root.borderColorOptions
currentMode: SettingsData.workspaceFocusedBorderColor
customColor: SettingsData.workspaceFocusedBorderCustomColor || "#6750A4"
onModeSelected: mode => SettingsData.set("workspaceFocusedBorderColor", mode)
onCustomColorSelected: selectedColor => SettingsData.set("workspaceFocusedBorderCustomColor", selectedColor.toString())
WorkspaceAppearanceColorOptions {
focusedColorOptions: root.focusedColorOptions
occupiedColorOptions: root.occupiedColorOptions
unfocusedColorOptions: root.unfocusedColorOptions
urgentColorOptions: root.urgentColorOptions
occupiedColorVisible: root.workspaceStateColorsVisible
urgentColorVisible: root.urgentWorkspaceColorsVisible
focusedColorModeKey: "workspaceColorMode"
focusedCustomColorKey: "workspaceFocusedCustomColor"
occupiedColorModeKey: "workspaceOccupiedColorMode"
occupiedCustomColorKey: "workspaceOccupiedCustomColor"
unfocusedColorModeKey: "workspaceUnfocusedColorMode"
unfocusedCustomColorKey: "workspaceUnfocusedCustomColor"
urgentColorModeKey: "workspaceUrgentColorMode"
urgentCustomColorKey: "workspaceUrgentCustomColor"
}
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)
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)
}
WorkspaceAppearanceBorderFields {
visible: SettingsData.workspaceFocusedBorderEnabled
borderColorOptions: root.borderColorOptions
borderColorKey: "workspaceFocusedBorderColor"
borderCustomColorKey: "workspaceFocusedBorderCustomColor"
borderThicknessKey: "workspaceFocusedBorderThickness"
}
}
Column {
id: unfocusedTab
width: parent.width
spacing: Theme.spacingM
visible: workspaceTabBar.currentIndex === 1
StyledText {
width: parent.width
visible: !BarWidgetService.focusedScreenDetectionSupported
text: I18n.tr("Separate appearance for unfocused displays is not supported on this compositor.")
wrapMode: Text.WordWrap
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeMedium
}
SettingsToggleRow {
visible: BarWidgetService.focusedScreenDetectionSupported
settingKey: "workspaceUnfocusedMonitorSeparateAppearance"
tags: ["workspace", "unfocused", "monitor", "display", "separate", "color"]
text: I18n.tr("Separate Appearance for Unfocused Display(s)")
description: I18n.tr("Use different workspace colors on displays that are not focused")
checked: SettingsData.workspaceUnfocusedMonitorSeparateAppearance
onToggled: checked => SettingsData.set("workspaceUnfocusedMonitorSeparateAppearance", checked)
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
visible: BarWidgetService.focusedScreenDetectionSupported
}
Column {
id: unfocusedOptions
width: parent.width
spacing: Theme.spacingM
visible: BarWidgetService.focusedScreenDetectionSupported
enabled: SettingsData.workspaceUnfocusedMonitorSeparateAppearance
opacity: enabled ? 1 : 0.5
WorkspaceAppearanceColorOptions {
focusedColorOptions: root.focusedColorOptions
occupiedColorOptions: root.occupiedColorOptions
unfocusedColorOptions: root.unfocusedColorOptions
urgentColorOptions: root.urgentColorOptions
occupiedColorVisible: root.workspaceStateColorsVisible
urgentColorVisible: root.urgentWorkspaceColorsVisible
extraTags: ["unfocused", "monitor", "display"]
focusedColorModeKey: "workspaceUnfocusedMonitorColorMode"
focusedCustomColorKey: "workspaceUnfocusedMonitorFocusedCustomColor"
occupiedColorModeKey: "workspaceUnfocusedMonitorOccupiedColorMode"
occupiedCustomColorKey: "workspaceUnfocusedMonitorOccupiedCustomColor"
unfocusedColorModeKey: "workspaceUnfocusedMonitorUnfocusedColorMode"
unfocusedCustomColorKey: "workspaceUnfocusedMonitorUnfocusedCustomColor"
urgentColorModeKey: "workspaceUnfocusedMonitorUrgentColorMode"
urgentCustomColorKey: "workspaceUnfocusedMonitorUrgentCustomColor"
}
Rectangle {
width: parent.width
height: 1
color: Theme.outline
opacity: 0.15
}
SettingsToggleRow {
settingKey: "workspaceUnfocusedMonitorBorderEnabled"
tags: ["workspace", "border", "outline", "focused", "ring", "unfocused", "monitor", "display"]
text: I18n.tr("Focused Border")
description: I18n.tr("Show an outline ring around the focused workspace indicator")
checked: SettingsData.workspaceUnfocusedMonitorBorderEnabled
onToggled: checked => SettingsData.set("workspaceUnfocusedMonitorBorderEnabled", checked)
}
WorkspaceAppearanceBorderFields {
visible: SettingsData.workspaceUnfocusedMonitorBorderEnabled
borderColorOptions: root.borderColorOptions
extraTags: ["unfocused", "monitor", "display"]
borderColorKey: "workspaceUnfocusedMonitorBorderColor"
borderCustomColorKey: "workspaceUnfocusedMonitorBorderCustomColor"
borderThicknessKey: "workspaceUnfocusedMonitorBorderThickness"
}
}
}
}