diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index aaef3201..d39a2198 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -14,7 +14,7 @@ import "settings/SettingsStore.js" as Store Singleton { id: root - readonly property int settingsConfigVersion: 7 + readonly property int settingsConfigVersion: 9 readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true" @@ -205,7 +205,7 @@ Singleton { onFrameThicknessChanged: saveSettings() property real frameRounding: 24 onFrameRoundingChanged: saveSettings() - property string frameColor: "#2a2a2a" + property string frameColor: "" onFrameColorChanged: saveSettings() property real frameOpacity: 1.0 onFrameOpacityChanged: saveSettings() @@ -213,6 +213,18 @@ Singleton { onFrameSyncBarColorChanged: saveSettings() property var frameScreenPreferences: ["all"] onFrameScreenPreferencesChanged: saveSettings() + property real frameBarThickness: 48 + onFrameBarThicknessChanged: saveSettings() + property bool frameShowOnOverview: false + onFrameShowOnOverviewChanged: saveSettings() + + readonly property color effectiveFrameColor: { + const fc = frameColor; + if (!fc || fc === "default") return Theme.background; + if (fc === "primary") return Theme.primary; + if (fc === "surface") return Theme.surface; + return fc; + } property bool showLauncherButton: true property bool showWorkspaceSwitcher: true @@ -1979,7 +1991,26 @@ Singleton { return ""; } + function getActiveBarEdgesForScreen(screen) { + if (!screen) return []; + var edges = []; + for (var i = 0; i < barConfigs.length; i++) { + var bc = barConfigs[i]; + if (!bc.enabled) continue; + var prefs = bc.screenPreferences || ["all"]; + if (!prefs.includes("all") && !isScreenInPreferences(screen, prefs)) continue; + switch (bc.position ?? 0) { + case SettingsData.Position.Top: edges.push("top"); break; + case SettingsData.Position.Bottom: edges.push("bottom"); break; + case SettingsData.Position.Left: edges.push("left"); break; + case SettingsData.Position.Right: edges.push("right"); break; + } + } + return edges; + } + function getActiveBarThicknessForScreen(screen) { + if (frameEnabled) return frameBarThickness; if (!screen) return frameThickness; for (var i = 0; i < barConfigs.length; i++) { var bc = barConfigs[i]; diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index 1045ddbd..90445f91 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -552,10 +552,12 @@ var SPEC = { frameEnabled: { def: false }, frameThickness: { def: 15 }, frameRounding: { def: 24 }, - frameColor: { def: "#2a2a2a" }, + frameColor: { def: "" }, frameOpacity: { def: 1.0 }, frameSyncBarColor: { def: true }, - frameScreenPreferences: { def: ["all"] } + frameScreenPreferences: { def: ["all"] }, + frameBarThickness: { def: 42 }, + frameShowOnOverview: { def: false } }; function getValidKeys() { diff --git a/quickshell/Common/settings/SettingsStore.js b/quickshell/Common/settings/SettingsStore.js index 1e5a72fc..ebedae83 100644 --- a/quickshell/Common/settings/SettingsStore.js +++ b/quickshell/Common/settings/SettingsStore.js @@ -262,6 +262,22 @@ function migrateToVersion(obj, targetVersion) { settings.configVersion = 7; } + if (currentVersion < 8) { + console.info("Migrating settings from version", currentVersion, "to version 8"); + + if (settings.frameBarThickness === undefined) settings.frameBarThickness = 48; + + settings.configVersion = 8; + } + + if (currentVersion < 9) { + console.info("Migrating settings from version", currentVersion, "to version 9"); + + if (settings.frameShowOnOverview === undefined) settings.frameShowOnOverview = false; + + settings.configVersion = 9; + } + return settings; } diff --git a/quickshell/DMSShell.qml b/quickshell/DMSShell.qml index c50b5fdd..cb764c22 100644 --- a/quickshell/DMSShell.qml +++ b/quickshell/DMSShell.qml @@ -177,6 +177,8 @@ Item { } } + Frame {} + Repeater { id: dankBarRepeater model: ScriptModel { @@ -208,8 +210,6 @@ Item { } } - Frame {} - property bool dockEnabled: false Timer { diff --git a/quickshell/Modules/DankBar/BarCanvas.qml b/quickshell/Modules/DankBar/BarCanvas.qml index 30d67286..08f2ac09 100644 --- a/quickshell/Modules/DankBar/BarCanvas.qml +++ b/quickshell/Modules/DankBar/BarCanvas.qml @@ -38,7 +38,7 @@ Item { property real rt: { if (SettingsData.frameEnabled) - return Math.max(0, SettingsData.frameRounding - SettingsData.frameThickness); + return 0; if (barConfig?.squareCorners ?? false) return 0; if (barWindow.hasMaximizedToplevel) diff --git a/quickshell/Modules/DankBar/DankBarWindow.qml b/quickshell/Modules/DankBar/DankBarWindow.qml index 26fec7f7..11613945 100644 --- a/quickshell/Modules/DankBar/DankBarWindow.qml +++ b/quickshell/Modules/DankBar/DankBarWindow.qml @@ -239,7 +239,7 @@ PanelWindow { readonly property string _barId: barConfig?.id ?? "default" property real _backgroundAlpha: barConfig?.transparency ?? 1.0 readonly property color _bgColor: (SettingsData.frameEnabled && SettingsData.frameSyncBarColor) - ? SettingsData.frameColor + ? Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity) : Theme.withAlpha(_surfaceContainer, _backgroundAlpha) function _updateBackgroundAlpha() { @@ -397,7 +397,12 @@ PanelWindow { } readonly property int notificationCount: NotificationService.notifications.length - readonly property real effectiveBarThickness: Theme.snap(Math.max(barWindow.widgetThickness + (barConfig?.innerPadding ?? 4) + 4, Theme.barHeight - 4 - (8 - (barConfig?.innerPadding ?? 4))), _dpr) + readonly property real effectiveBarThickness: SettingsData.frameEnabled + ? SettingsData.frameBarThickness + : Theme.snap(Math.max(barWindow.widgetThickness + (barConfig?.innerPadding ?? 4) + 4, Theme.barHeight - 4 - (8 - (barConfig?.innerPadding ?? 4))), _dpr) + readonly property bool effectiveOpenOnOverview: SettingsData.frameEnabled + ? SettingsData.frameShowOnOverview + : (barConfig?.openOnOverview ?? false) readonly property real widgetThickness: Theme.snap(Math.max(20, 26 + (barConfig?.innerPadding ?? 4) * 0.6), _dpr) readonly property bool hasAdjacentTopBar: { @@ -653,7 +658,7 @@ PanelWindow { readonly property int barThickness: Theme.px(barWindow.effectiveBarThickness + barWindow.effectiveSpacing, barWindow._dpr) - readonly property bool inOverviewWithShow: CompositorService.isNiri && NiriService.inOverview && (barConfig?.openOnOverview ?? false) + readonly property bool inOverviewWithShow: CompositorService.isNiri && NiriService.inOverview && barWindow.effectiveOpenOnOverview readonly property bool effectiveVisible: (barConfig?.visible ?? true) || inOverviewWithShow readonly property bool showing: effectiveVisible && (topBarCore.reveal || inOverviewWithShow || !topBarCore.autoHide) @@ -794,7 +799,7 @@ PanelWindow { } property bool reveal: { - const inOverviewWithShow = CompositorService.isNiri && NiriService.inOverview && (barConfig?.openOnOverview ?? false); + const inOverviewWithShow = CompositorService.isNiri && NiriService.inOverview && barWindow.effectiveOpenOnOverview; if (inOverviewWithShow) return true; @@ -891,7 +896,7 @@ PanelWindow { top: barWindow.isVertical ? parent.top : undefined bottom: barWindow.isVertical ? parent.bottom : undefined } - readonly property bool inOverview: CompositorService.isNiri && NiriService.inOverview && (barConfig?.openOnOverview ?? false) + readonly property bool inOverview: CompositorService.isNiri && NiriService.inOverview && barWindow.effectiveOpenOnOverview hoverEnabled: (barConfig?.autoHide ?? false) && !inOverview && !topBarCore.hasActivePopout acceptedButtons: Qt.NoButton enabled: (barConfig?.autoHide ?? false) && !inOverview diff --git a/quickshell/Modules/Frame/FrameBorder.qml b/quickshell/Modules/Frame/FrameBorder.qml index 0f845f64..837b8008 100644 --- a/quickshell/Modules/Frame/FrameBorder.qml +++ b/quickshell/Modules/Frame/FrameBorder.qml @@ -7,19 +7,19 @@ import qs.Common Item { id: root - required property string barEdge // "top" | "bottom" | "left" | "right" | "" - required property real barThickness + required property var barEdges // array of "top" | "bottom" | "left" | "right" anchors.fill: parent - readonly property real _thickness: SettingsData.frameThickness - readonly property real _rounding: SettingsData.frameRounding + readonly property real _thickness: SettingsData.frameThickness + readonly property real _barThickness: SettingsData.frameBarThickness + readonly property real _rounding: SettingsData.frameRounding Rectangle { id: borderRect anchors.fill: parent - color: SettingsData.frameColor + color: SettingsData.effectiveFrameColor opacity: SettingsData.frameOpacity layer.enabled: true @@ -42,10 +42,10 @@ Item { Rectangle { anchors { fill: parent - topMargin: root.barEdge === "top" ? root.barThickness : root._thickness - bottomMargin: root.barEdge === "bottom" ? root.barThickness : root._thickness - leftMargin: root.barEdge === "left" ? root.barThickness : root._thickness - rightMargin: root.barEdge === "right" ? root.barThickness : root._thickness + topMargin: root.barEdges.includes("top") ? root._barThickness : root._thickness + bottomMargin: root.barEdges.includes("bottom") ? root._barThickness : root._thickness + leftMargin: root.barEdges.includes("left") ? root._barThickness : root._thickness + rightMargin: root.barEdges.includes("right") ? root._barThickness : root._thickness } radius: root._rounding } diff --git a/quickshell/Modules/Frame/FrameExclusions.qml b/quickshell/Modules/Frame/FrameExclusions.qml index 9c3d6e0a..3051452c 100644 --- a/quickshell/Modules/Frame/FrameExclusions.qml +++ b/quickshell/Modules/Frame/FrameExclusions.qml @@ -10,48 +10,51 @@ Scope { required property ShellScreen screen - readonly property string barEdge: SettingsData.getActiveBarEdgeForScreen(screen) + readonly property var barEdges: { + SettingsData.barConfigs; // force re-eval when bar configs change + return SettingsData.getActiveBarEdgesForScreen(screen); + } // One thin invisible PanelWindow per edge. - // Skips the edge where the bar already provides its own exclusiveZone. + // Skips any edge where a bar already provides its own exclusiveZone. Loader { - active: root.barEdge !== "top" - sourceComponent: EdgeExclusion { - screen: root.screen - anchorTop: true - anchorLeft: true - anchorRight: true - } - } - - Loader { - active: root.barEdge !== "bottom" - sourceComponent: EdgeExclusion { - screen: root.screen - anchorBottom: true - anchorLeft: true - anchorRight: true - } - } - - Loader { - active: root.barEdge !== "left" + active: !root.barEdges.includes("top") sourceComponent: EdgeExclusion { screen: root.screen - anchorLeft: true - anchorTop: true - anchorBottom: true + anchorTop: true + anchorLeft: true + anchorRight: true } } Loader { - active: root.barEdge !== "right" + active: !root.barEdges.includes("bottom") sourceComponent: EdgeExclusion { screen: root.screen - anchorRight: true - anchorTop: true - anchorBottom: true + anchorBottom: true + anchorLeft: true + anchorRight: true + } + } + + Loader { + active: !root.barEdges.includes("left") + sourceComponent: EdgeExclusion { + screen: root.screen + anchorLeft: true + anchorTop: true + anchorBottom: true + } + } + + Loader { + active: !root.barEdges.includes("right") + sourceComponent: EdgeExclusion { + screen: root.screen + anchorRight: true + anchorTop: true + anchorBottom: true } } diff --git a/quickshell/Modules/Frame/FrameWindow.qml b/quickshell/Modules/Frame/FrameWindow.qml index 4952a80b..a6e15c2f 100644 --- a/quickshell/Modules/Frame/FrameWindow.qml +++ b/quickshell/Modules/Frame/FrameWindow.qml @@ -11,7 +11,7 @@ PanelWindow { required property ShellScreen screen WlrLayershell.namespace: "dms:frame" - WlrLayershell.layer: WlrLayer.Bottom + WlrLayershell.layer: WlrLayer.Top WlrLayershell.exclusionMode: ExclusionMode.Ignore anchors { @@ -28,7 +28,9 @@ PanelWindow { FrameBorder { anchors.fill: parent - barEdge: SettingsData.getActiveBarEdgeForScreen(win.screen) - barThickness: SettingsData.getActiveBarThicknessForScreen(win.screen) + barEdges: { + SettingsData.barConfigs; // force re-eval when bar configs change + return SettingsData.getActiveBarEdgesForScreen(win.screen); + } } } diff --git a/quickshell/Modules/Settings/DankBarTab.qml b/quickshell/Modules/Settings/DankBarTab.qml index a4bd87d1..bba6dd83 100644 --- a/quickshell/Modules/Settings/DankBarTab.qml +++ b/quickshell/Modules/Settings/DankBarTab.qml @@ -693,6 +693,8 @@ Item { SettingsToggleRow { visible: CompositorService.isNiri + enabled: !SettingsData.frameEnabled + opacity: SettingsData.frameEnabled ? 0.5 : 1.0 text: I18n.tr("Show on Overview") checked: selectedBarConfig?.openOnOverview ?? false onToggled: toggled => { @@ -798,11 +800,42 @@ Item { } } + Item { + visible: SettingsData.frameEnabled + width: parent.width + implicitHeight: frameNote.implicitHeight + Theme.spacingS * 2 + + Row { + id: frameNote + x: Theme.spacingM + width: parent.width - Theme.spacingM * 2 + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "frame_source" + size: Theme.fontSizeMedium + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Spacing and size are managed by Frame mode") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + wrapMode: Text.WordWrap + width: parent.width - Theme.fontSizeMedium - Theme.spacingS + } + } + } + SettingsCard { iconName: "space_bar" title: I18n.tr("Spacing") settingKey: "barSpacing" visible: selectedBarConfig?.enabled + enabled: !SettingsData.frameEnabled + opacity: SettingsData.frameEnabled ? 0.5 : 1.0 SettingsSliderRow { id: edgeSpacingSlider @@ -1287,6 +1320,8 @@ Item { SettingsToggleRow { text: I18n.tr("Square Corners") + enabled: !SettingsData.frameEnabled + opacity: SettingsData.frameEnabled ? 0.5 : 1.0 checked: selectedBarConfig?.squareCorners ?? false onToggled: checked => SettingsData.updateBarConfig(selectedBarId, { squareCorners: checked @@ -1295,6 +1330,8 @@ Item { SettingsToggleRow { text: I18n.tr("No Background") + enabled: !SettingsData.frameEnabled + opacity: SettingsData.frameEnabled ? 0.5 : 1.0 checked: selectedBarConfig?.noBackground ?? false onToggled: checked => SettingsData.updateBarConfig(selectedBarId, { noBackground: checked @@ -1334,6 +1371,8 @@ Item { SettingsToggleRow { text: I18n.tr("Goth Corners") + enabled: !SettingsData.frameEnabled + opacity: SettingsData.frameEnabled ? 0.5 : 1.0 checked: selectedBarConfig?.gothCornersEnabled ?? false onToggled: checked => SettingsData.updateBarConfig(selectedBarId, { gothCornersEnabled: checked diff --git a/quickshell/Modules/Settings/FrameTab.qml b/quickshell/Modules/Settings/FrameTab.qml index c4fb4eee..212cf182 100644 --- a/quickshell/Modules/Settings/FrameTab.qml +++ b/quickshell/Modules/Settings/FrameTab.qml @@ -91,6 +91,27 @@ Item { } } + SettingsSliderRow { + id: barThicknessSlider + settingKey: "frameBarThickness" + tags: ["frame", "bar", "thickness", "size", "height", "width"] + text: I18n.tr("Bar-edge thickness") + description: I18n.tr("Height of horizontal bars / width of vertical bars in frame mode") + unit: "px" + minimum: 24 + maximum: 100 + step: 1 + defaultValue: 48 + value: SettingsData.frameBarThickness + onSliderDragFinished: v => SettingsData.set("frameBarThickness", v) + + Binding { + target: barThicknessSlider + property: "value" + value: SettingsData.frameBarThickness + } + } + SettingsSliderRow { id: opacitySlider settingKey: "frameOpacity" @@ -110,13 +131,45 @@ Item { } } - // Color row + // Color mode buttons + SettingsButtonGroupRow { + settingKey: "frameColor" + tags: ["frame", "border", "color", "theme", "primary", "surface", "default"] + text: I18n.tr("Border color") + model: [I18n.tr("Default"), I18n.tr("Primary"), I18n.tr("Surface"), I18n.tr("Custom")] + currentIndex: { + const fc = SettingsData.frameColor; + if (!fc || fc === "default") return 0; + if (fc === "primary") return 1; + if (fc === "surface") return 2; + return 3; + } + onSelectionChanged: (index, selected) => { + if (!selected) return; + switch (index) { + case 0: SettingsData.set("frameColor", ""); break; + case 1: SettingsData.set("frameColor", "primary"); break; + case 2: SettingsData.set("frameColor", "surface"); break; + case 3: + const cur = SettingsData.frameColor; + const isPreset = !cur || cur === "primary" || cur === "surface"; + if (isPreset) SettingsData.set("frameColor", "#2a2a2a"); + break; + } + } + } + + // Custom color swatch — only visible when a hex color is stored (Custom mode) Item { + visible: { + const fc = SettingsData.frameColor; + return !!(fc && fc !== "primary" && fc !== "surface"); + } width: parent.width - height: colorRow.height + Theme.spacingM * 2 + height: customColorRow.height + Theme.spacingM * 2 Row { - id: colorRow + id: customColorRow width: parent.width - Theme.spacingM * 2 x: Theme.spacingM anchors.verticalCenter: parent.verticalCenter @@ -124,7 +177,7 @@ Item { StyledText { anchors.verticalCenter: parent.verticalCenter - text: I18n.tr("Border color") + text: I18n.tr("Custom color") font.pixelSize: Theme.fontSizeMedium font.weight: Font.Medium color: Theme.surfaceText @@ -136,7 +189,7 @@ Item { width: 32 height: 32 radius: 16 - color: SettingsData.frameColor + color: SettingsData.effectiveFrameColor border.color: Theme.outline border.width: 1 @@ -144,7 +197,7 @@ Item { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { - PopoutService.colorPickerModal.selectedColor = SettingsData.frameColor; + PopoutService.colorPickerModal.selectedColor = SettingsData.effectiveFrameColor; PopoutService.colorPickerModal.pickerTitle = I18n.tr("Frame Border Color"); PopoutService.colorPickerModal.onColorSelectedCallback = function (color) { SettingsData.set("frameColor", color.toString()); @@ -174,6 +227,16 @@ Item { checked: SettingsData.frameSyncBarColor onToggled: checked => SettingsData.set("frameSyncBarColor", checked) } + + SettingsToggleRow { + visible: CompositorService.isNiri + settingKey: "frameShowOnOverview" + tags: ["frame", "overview", "show", "hide", "niri"] + text: I18n.tr("Show on Overview") + description: I18n.tr("Show the bar and frame during Niri overview mode") + checked: SettingsData.frameShowOnOverview + onToggled: checked => SettingsData.set("frameShowOnOverview", checked) + } } // ── Display Assignment ────────────────────────────────────────────