From 48f6a0c632d755f29a743a2c730e28f79c299214 Mon Sep 17 00:00:00 2001 From: Maddison Cohodas Date: Fri, 26 Jun 2026 20:34:24 -0700 Subject: [PATCH] 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. --- quickshell/Common/SettingsData.qml | 13 + quickshell/Common/settings/SettingsSpec.js | 13 + .../DankBar/Widgets/WorkspaceSwitcher.qml | 58 ++-- .../WorkspaceAppearanceBorderFields.qml | 42 +++ .../Settings/WorkspaceAppearanceCard.qml | 262 +++++++++++------- .../WorkspaceAppearanceColorOptions.qml | 101 +++++++ quickshell/Services/BarWidgetService.qml | 4 + .../translations/settings_search_index.json | 65 +++++ 8 files changed, 432 insertions(+), 126 deletions(-) create mode 100644 quickshell/Modules/Settings/WorkspaceAppearanceBorderFields.qml create mode 100644 quickshell/Modules/Settings/WorkspaceAppearanceColorOptions.qml diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index fad4c53c..40509645 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -425,6 +425,19 @@ Singleton { property string workspaceFocusedBorderColor: "primary" property string workspaceFocusedBorderCustomColor: "#6750A4" property int workspaceFocusedBorderThickness: 2 + property bool workspaceUnfocusedMonitorSeparateAppearance: false + property string workspaceUnfocusedMonitorColorMode: "default" + property string workspaceUnfocusedMonitorFocusedCustomColor: "#6750A4" + property string workspaceUnfocusedMonitorOccupiedColorMode: "none" + property string workspaceUnfocusedMonitorOccupiedCustomColor: "#625B71" + property string workspaceUnfocusedMonitorUnfocusedColorMode: "default" + property string workspaceUnfocusedMonitorUnfocusedCustomColor: "#49454E" + property string workspaceUnfocusedMonitorUrgentColorMode: "default" + property string workspaceUnfocusedMonitorUrgentCustomColor: "#B3261E" + property bool workspaceUnfocusedMonitorBorderEnabled: false + property string workspaceUnfocusedMonitorBorderColor: "primary" + property string workspaceUnfocusedMonitorBorderCustomColor: "#6750A4" + property int workspaceUnfocusedMonitorBorderThickness: 2 property var workspaceNameIcons: ({}) property bool waveProgressEnabled: true property bool scrollTitleEnabled: true diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index df7e0271..f137e6f7 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -163,6 +163,19 @@ var SPEC = { workspaceFocusedBorderColor: { def: "primary" }, workspaceFocusedBorderCustomColor: { def: "#6750A4" }, workspaceFocusedBorderThickness: { def: 2 }, + workspaceUnfocusedMonitorSeparateAppearance: { def: false }, + workspaceUnfocusedMonitorColorMode: { def: "default" }, + workspaceUnfocusedMonitorFocusedCustomColor: { def: "#6750A4" }, + workspaceUnfocusedMonitorOccupiedColorMode: { def: "none" }, + workspaceUnfocusedMonitorOccupiedCustomColor: { def: "#625B71" }, + workspaceUnfocusedMonitorUnfocusedColorMode: { def: "default" }, + workspaceUnfocusedMonitorUnfocusedCustomColor: { def: "#49454E" }, + workspaceUnfocusedMonitorUrgentColorMode: { def: "default" }, + workspaceUnfocusedMonitorUrgentCustomColor: { def: "#B3261E" }, + workspaceUnfocusedMonitorBorderEnabled: { def: false }, + workspaceUnfocusedMonitorBorderColor: { def: "primary" }, + workspaceUnfocusedMonitorBorderCustomColor: { def: "#6750A4" }, + workspaceUnfocusedMonitorBorderThickness: { def: 2 }, workspaceNameIcons: { def: {} }, waveProgressEnabled: { def: true }, scrollTitleEnabled: { def: true }, diff --git a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml index 425ddb0a..cbbcd22d 100644 --- a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml +++ b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml @@ -71,25 +71,16 @@ Item { 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 "mango": - return MangoService.activeOutput || root.screenName; - case "sway": - case "scroll": - case "miracle": - const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); - return focusedWs?.monitor?.name || root.screenName; - default: - 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") @@ -1231,23 +1222,36 @@ Item { } } - readonly property color unfocusedColor: colorFromMode(SettingsData.workspaceUnfocusedColorMode, Theme.surfaceTextAlpha, SettingsData.workspaceUnfocusedCustomColor, Theme.surfaceTextAlpha) + 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: { - if (SettingsData.workspaceColorMode === "none") + const mode = effectiveColorMode(SettingsData.workspaceColorMode, SettingsData.workspaceUnfocusedMonitorColorMode); + if (mode === "none") return unfocusedColor; - return colorFromMode(SettingsData.workspaceColorMode, Theme.primary, SettingsData.workspaceFocusedCustomColor, Theme.primary); + return colorFromMode(mode, Theme.primary, effectiveCustomColor(SettingsData.workspaceFocusedCustomColor, SettingsData.workspaceUnfocusedMonitorFocusedCustomColor), Theme.primary); } readonly property color occupiedColor: { - if (SettingsData.workspaceOccupiedColorMode === "none") + const mode = effectiveColorMode(SettingsData.workspaceOccupiedColorMode, SettingsData.workspaceUnfocusedMonitorOccupiedColorMode); + if (mode === "none") return unfocusedColor; - return colorFromMode(SettingsData.workspaceOccupiedColorMode, unfocusedColor, SettingsData.workspaceOccupiedCustomColor, Theme.secondary); + return colorFromMode(mode, unfocusedColor, effectiveCustomColor(SettingsData.workspaceOccupiedCustomColor, SettingsData.workspaceUnfocusedMonitorOccupiedCustomColor), Theme.secondary); } - readonly property color urgentColor: colorFromMode(SettingsData.workspaceUrgentColorMode, Theme.error, SettingsData.workspaceUrgentCustomColor, Theme.error) + readonly property color urgentColor: colorFromMode(effectiveColorMode(SettingsData.workspaceUrgentColorMode, SettingsData.workspaceUnfocusedMonitorUrgentColorMode), Theme.error, effectiveCustomColor(SettingsData.workspaceUrgentCustomColor, SettingsData.workspaceUnfocusedMonitorUrgentCustomColor), Theme.error) - readonly property color focusedBorderColor: colorFromMode(SettingsData.workspaceFocusedBorderColor, Theme.primary, SettingsData.workspaceFocusedBorderCustomColor, Theme.primary) + 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; @@ -1449,17 +1453,17 @@ Item { 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 = (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0; + const borderWidth = (delegateRoot.focusedBorderEnabledForMonitor && isActive && !isPlaceholder) ? delegateRoot.focusedBorderThicknessForMonitor : 0; return delegateRoot.visualWidth + borderWidth * 2; } height: { - const borderWidth = (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0; + const borderWidth = (delegateRoot.focusedBorderEnabledForMonitor && isActive && !isPlaceholder) ? delegateRoot.focusedBorderThicknessForMonitor : 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" + border.width: (delegateRoot.focusedBorderEnabledForMonitor && isActive && !isPlaceholder) ? delegateRoot.focusedBorderThicknessForMonitor : 0 + border.color: (delegateRoot.focusedBorderEnabledForMonitor && isActive && !isPlaceholder) ? focusedBorderColor : "transparent" Behavior on width { NumberAnimation { diff --git a/quickshell/Modules/Settings/WorkspaceAppearanceBorderFields.qml b/quickshell/Modules/Settings/WorkspaceAppearanceBorderFields.qml new file mode 100644 index 00000000..5e658798 --- /dev/null +++ b/quickshell/Modules/Settings/WorkspaceAppearanceBorderFields.qml @@ -0,0 +1,42 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import qs.Common +import qs.Modules.Settings.Widgets + +Column { + id: root + + property var borderColorOptions: [] + property string borderColorKey: "" + property string borderCustomColorKey: "" + property string borderThicknessKey: "" + property var extraTags: [] + + width: parent?.width ?? 0 + spacing: Theme.spacingS + leftPadding: Theme.spacingM + + ColorDropdownRow { + width: parent.width - parent.leftPadding + text: I18n.tr("Border Color") + settingKey: root.borderColorKey + tags: ["workspace", "focused", "border", "color", "custom"].concat(root.extraTags) + options: root.borderColorOptions + currentMode: SettingsData[root.borderColorKey] + customColor: SettingsData[root.borderCustomColorKey] || "#6750A4" + onModeSelected: mode => SettingsData.set(root.borderColorKey, mode) + onCustomColorSelected: selectedColor => SettingsData.set(root.borderCustomColorKey, selectedColor.toString()) + } + + SettingsSliderRow { + width: parent.width - parent.leftPadding + text: I18n.tr("Thickness") + value: SettingsData[root.borderThicknessKey] + minimum: 1 + maximum: 6 + unit: "px" + defaultValue: 2 + onSliderValueChanged: newValue => SettingsData.set(root.borderThicknessKey, newValue) + } +} diff --git a/quickshell/Modules/Settings/WorkspaceAppearanceCard.qml b/quickshell/Modules/Settings/WorkspaceAppearanceCard.qml index 3b478382..30a13d41 100644 --- a/quickshell/Modules/Settings/WorkspaceAppearanceCard.qml +++ b/quickshell/Modules/Settings/WorkspaceAppearanceCard.qml @@ -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" + } } } } diff --git a/quickshell/Modules/Settings/WorkspaceAppearanceColorOptions.qml b/quickshell/Modules/Settings/WorkspaceAppearanceColorOptions.qml new file mode 100644 index 00000000..e5952729 --- /dev/null +++ b/quickshell/Modules/Settings/WorkspaceAppearanceColorOptions.qml @@ -0,0 +1,101 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import qs.Common + +Column { + id: root + + property var focusedColorOptions: [] + property var occupiedColorOptions: [] + property var unfocusedColorOptions: [] + property var urgentColorOptions: [] + + property bool occupiedColorVisible: true + property bool urgentColorVisible: true + + property string focusedColorModeKey: "" + property string focusedCustomColorKey: "" + property string occupiedColorModeKey: "" + property string occupiedCustomColorKey: "" + property string unfocusedColorModeKey: "" + property string unfocusedCustomColorKey: "" + property string urgentColorModeKey: "" + property string urgentCustomColorKey: "" + + property var extraTags: [] + + width: parent?.width ?? 0 + spacing: Theme.spacingM + + ColorDropdownRow { + text: I18n.tr("Focused Color") + settingKey: root.focusedColorModeKey + tags: ["workspace", "focused", "color", "custom"].concat(root.extraTags) + options: root.focusedColorOptions + currentMode: SettingsData[root.focusedColorModeKey] + customColor: SettingsData[root.focusedCustomColorKey] || "#6750A4" + onModeSelected: mode => SettingsData.set(root.focusedColorModeKey, mode) + onCustomColorSelected: selectedColor => SettingsData.set(root.focusedCustomColorKey, selectedColor.toString()) + } + + Rectangle { + width: parent.width + height: 1 + color: Theme.outline + opacity: 0.15 + } + + ColorDropdownRow { + text: I18n.tr("Occupied Color") + settingKey: root.occupiedColorModeKey + tags: ["workspace", "occupied", "color", "custom"].concat(root.extraTags) + visible: root.occupiedColorVisible + options: root.occupiedColorOptions + currentMode: SettingsData[root.occupiedColorModeKey] + customColor: SettingsData[root.occupiedCustomColorKey] || "#625B71" + onModeSelected: mode => SettingsData.set(root.occupiedColorModeKey, mode) + onCustomColorSelected: selectedColor => SettingsData.set(root.occupiedCustomColorKey, selectedColor.toString()) + } + + Rectangle { + width: parent.width + height: 1 + color: Theme.outline + opacity: 0.15 + visible: root.occupiedColorVisible + } + + ColorDropdownRow { + text: I18n.tr("Unfocused Color") + settingKey: root.unfocusedColorModeKey + tags: ["workspace", "unfocused", "color", "custom"].concat(root.extraTags) + options: root.unfocusedColorOptions + defaultColor: Theme.surfaceText + currentMode: SettingsData[root.unfocusedColorModeKey] + customColor: SettingsData[root.unfocusedCustomColorKey] || "#49454E" + onModeSelected: mode => SettingsData.set(root.unfocusedColorModeKey, mode) + onCustomColorSelected: selectedColor => SettingsData.set(root.unfocusedCustomColorKey, selectedColor.toString()) + } + + Rectangle { + width: parent.width + height: 1 + color: Theme.outline + opacity: 0.15 + visible: root.urgentColorVisible + } + + ColorDropdownRow { + text: I18n.tr("Urgent Color") + settingKey: root.urgentColorModeKey + tags: ["workspace", "urgent", "color", "custom"].concat(root.extraTags) + visible: root.urgentColorVisible + options: root.urgentColorOptions + defaultColor: Theme.error + currentMode: SettingsData[root.urgentColorModeKey] + customColor: SettingsData[root.urgentCustomColorKey] || "#B3261E" + onModeSelected: mode => SettingsData.set(root.urgentColorModeKey, mode) + onCustomColorSelected: selectedColor => SettingsData.set(root.urgentCustomColorKey, selectedColor.toString()) + } +} diff --git a/quickshell/Services/BarWidgetService.qml b/quickshell/Services/BarWidgetService.qml index 66dc4e51..8ffaa02c 100644 --- a/quickshell/Services/BarWidgetService.qml +++ b/quickshell/Services/BarWidgetService.qml @@ -70,11 +70,15 @@ Singleton { return screens.length > 0 ? widgetRegistry[widgetId][screens[0]] : null; } + readonly property bool focusedScreenDetectionSupported: CompositorService.isHyprland || CompositorService.isNiri || CompositorService.isMango || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle + function getFocusedScreenName() { if (CompositorService.isHyprland && Hyprland.focusedWorkspace?.monitor) return Hyprland.focusedWorkspace.monitor.name; if (CompositorService.isNiri && NiriService.currentOutput) return NiriService.currentOutput; + if (CompositorService.isMango && MangoService.activeOutput) + return MangoService.activeOutput; if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); return focusedWs?.monitor?.name || ""; diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 4cd501ff..552b93cc 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -1899,6 +1899,33 @@ ], "description": "Show an outline ring around the focused workspace indicator" }, + { + "section": "workspaceUnfocusedMonitorBorderEnabled", + "label": "Focused Border", + "tabIndex": 6, + "category": "Dank Bar", + "keywords": [ + "around", + "bar", + "border", + "dank", + "desktop", + "display", + "focused", + "indicator", + "monitor", + "outline", + "panel", + "ring", + "show", + "statusbar", + "topbar", + "unfocused", + "virtual", + "workspace" + ], + "description": "Show an outline ring around the focused workspace indicator" + }, { "section": "barFontScale", "label": "Font Scale", @@ -1979,6 +2006,44 @@ "icon": "opacity", "description": "Controls opacity of the bar background" }, + { + "section": "workspaceUnfocusedMonitorSeparateAppearance", + "label": "Separate Appearance for Unfocused Display(s)", + "tabIndex": 6, + "category": "Dank Bar", + "keywords": [ + "appearance", + "bar", + "color", + "colors", + "colour", + "colours", + "dank", + "desktop", + "different", + "display", + "display(s)", + "displays", + "focused", + "hue", + "monitor", + "monitors", + "output", + "outputs", + "palette", + "panel", + "screen", + "screens", + "separate", + "statusbar", + "tint", + "topbar", + "unfocused", + "virtual", + "workspace" + ], + "description": "Use different workspace colors on displays that are not focused" + }, { "section": "barShadow", "label": "Shadow Override",