From b340529b106d6de64988345a9c87151c0b694cef Mon Sep 17 00:00:00 2001 From: Lemmy Date: Sun, 18 Jan 2026 19:32:04 -0500 Subject: [PATCH] Bar: custom setup per screen (position, density, widgets) --- Assets/Translations/en.json | 7 + Assets/settings-default.json | 3 +- Commons/Settings.qml | 174 +++++++++++ Commons/Style.qml | 74 +++++ Modules/Bar/Bar.qml | 49 +-- Modules/Bar/Extras/BarExclusionZone.qml | 11 +- Modules/Bar/Extras/BarPill.qml | 2 +- Modules/Bar/Extras/BarPillHorizontal.qml | 8 +- Modules/Bar/Extras/BarPillVertical.qml | 4 +- Modules/Bar/Extras/TrayMenu.qml | 6 +- Modules/Bar/Widgets/ActiveWindow.qml | 5 +- Modules/Bar/Widgets/AudioVisualizer.qml | 5 +- Modules/Bar/Widgets/Battery.qml | 3 +- Modules/Bar/Widgets/Bluetooth.qml | 3 +- Modules/Bar/Widgets/Brightness.qml | 3 +- Modules/Bar/Widgets/Clock.qml | 4 +- Modules/Bar/Widgets/ControlCenter.qml | 2 +- Modules/Bar/Widgets/CustomButton.qml | 3 +- Modules/Bar/Widgets/DarkMode.qml | 2 +- Modules/Bar/Widgets/KeepAwake.qml | 3 +- Modules/Bar/Widgets/Launcher.qml | 2 +- Modules/Bar/Widgets/LockKeys.qml | 2 +- Modules/Bar/Widgets/MediaMini.qml | 9 +- Modules/Bar/Widgets/Microphone.qml | 3 +- Modules/Bar/Widgets/Network.qml | 3 +- Modules/Bar/Widgets/NightLight.qml | 2 +- Modules/Bar/Widgets/NoctaliaPerformance.qml | 2 +- Modules/Bar/Widgets/NotificationHistory.qml | 2 +- Modules/Bar/Widgets/PowerProfile.qml | 2 +- Modules/Bar/Widgets/SessionMenu.qml | 2 +- Modules/Bar/Widgets/Spacer.qml | 3 +- Modules/Bar/Widgets/SystemMonitor.qml | 4 +- Modules/Bar/Widgets/Taskbar.qml | 4 +- Modules/Bar/Widgets/Tray.qml | 8 +- Modules/Bar/Widgets/VPN.qml | 3 +- Modules/Bar/Widgets/Volume.qml | 3 +- Modules/Bar/Widgets/WallpaperSelector.qml | 2 +- Modules/Bar/Widgets/Workspace.qml | 4 +- Modules/DesktopWidgets/DesktopWidgets.qml | 2 +- Modules/Dock/Dock.qml | 11 +- Modules/MainScreen/BarContentWindow.qml | 9 +- Modules/MainScreen/BarExclusionZone.qml | 2 +- Modules/MainScreen/MainScreen.qml | 13 +- Modules/MainScreen/SmartPanel.qml | 2 +- Modules/Notification/Notification.qml | 2 +- Modules/OSD/OSD.qml | 4 +- Modules/Panels/Launcher/Launcher.qml | 7 +- Modules/Panels/Media/MediaPlayerPanel.qml | 2 +- .../Settings/Bar/MonitorWidgetsDialog.qml | 294 ++++++++++++++++++ Modules/Panels/Settings/SettingsPanel.qml | 2 +- Modules/Panels/Settings/Tabs/Bar/BarTab.qml | 31 +- .../Settings/Tabs/Bar/MonitorsSubTab.qml | 215 +++++++++++-- .../Settings/Tabs/Bar/WidgetsSubTab.qml | 1 + Modules/Panels/Tray/TrayDrawerPanel.qml | 4 +- Modules/Panels/Wallpaper/WallpaperPanel.qml | 7 +- Modules/Toast/ToastScreen.qml | 2 +- Services/UI/BarService.qml | 5 +- Widgets/NPopupContextMenu.qml | 2 +- Widgets/NToggle.qml | 1 + 59 files changed, 907 insertions(+), 142 deletions(-) create mode 100644 Modules/Panels/Settings/Bar/MonitorWidgetsDialog.qml diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index df4c97498..a88c20402 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -712,7 +712,13 @@ "appearance-show-outline-label": "Show widget outlines", "appearance-use-separate-opacity-description": "Enable to use a separate opacity value for the bar background.", "appearance-use-separate-opacity-label": "Use separate bar opacity", + "monitor-configure-widgets": "Configure widgets", + "monitor-override-settings": "Override global settings", + "monitor-override-settings-description": "Use custom settings for this monitor.", + "monitor-reset-all": "Reset all", + "monitor-widgets-title": "Widget Configuration - {monitor}", "monitors-desc": "Show bar on specific monitors. Defaults to all if none are chosen.", + "monitors-desc-new": "Configure which monitors display the bar and customize settings per monitor.", "monitors-title": "Monitors display", "title": "Bar", "tray-blacklist-description": "Add tray exclusion rules, supports wildcards (*).", @@ -720,6 +726,7 @@ "tray-blacklist-placeholder": "e.g., nm-applet, Fcitx*", "tray-pin-application": "Pin Application", "tray-unpin-application": "Unpin Application", + "use-global-widgets": "Use global widgets", "widgets-desc": "Drag widgets to change their order. Use the right-click menu to transfer widgets between sections or remove them.", "widgets-title": "Widgets positioning" }, diff --git a/Assets/settings-default.json b/Assets/settings-default.json index ab1bc840e..92b1ff677 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -58,7 +58,8 @@ "id": "ControlCenter" } ] - } + }, + "screenOverrides": [] }, "general": { "avatarImage": "", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 0532ed490..dcd29f064 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -244,6 +244,10 @@ Singleton { } ] } + + // Per-screen overrides for position and widgets + // Format: [{ "name": "HDMI-1", "position": "left" }, { "name": "DP-1", "position": "bottom", "widgets": {...} }] + property list screenOverrides: [] } // general @@ -728,6 +732,176 @@ Singleton { return String(defaultValue); } + // ----------------------------------------------------- + // Helper to find a screen override entry by name in the array + // Format: [{ "name": "HDMI-A-1", "position": "left" }, ...] + // Note: QML's list is not a true JS array, so we check for .length instead of Array.isArray() + function _findScreenOverride(screenName) { + var overrides = data.bar.screenOverrides; + if (!screenName || !overrides || overrides.length === undefined) { + return null; + } + for (var i = 0; i < overrides.length; i++) { + if (overrides[i] && overrides[i].name === screenName) { + return overrides[i]; + } + } + return null; + } + + // Helper to find index of a screen override entry + function _findScreenOverrideIndex(screenName) { + var overrides = data.bar.screenOverrides; + if (!screenName || !overrides || overrides.length === undefined) { + return -1; + } + for (var i = 0; i < overrides.length; i++) { + if (overrides[i] && overrides[i].name === screenName) { + return i; + } + } + return -1; + } + + // ----------------------------------------------------- + // Check if a screen's overrides are enabled + // Returns true if enabled flag is true or undefined (backward compat) + // Returns false only if enabled is explicitly false + function isScreenOverrideEnabled(screenName) { + var override = _findScreenOverride(screenName); + if (!override) { + return false; + } + return override.enabled !== false; + } + + // ----------------------------------------------------- + // Get effective bar position for a screen (with inheritance) + // If the screen has a position override and overrides are enabled, use it; otherwise use global default + function getBarPositionForScreen(screenName) { + var override = _findScreenOverride(screenName); + if (override && override.enabled !== false && override.position !== undefined) { + return override.position; + } + return data.bar.position || "top"; + } + + // ----------------------------------------------------- + // Get effective bar widgets for a screen (with inheritance) + // If the screen has widget overrides and overrides are enabled, use them; otherwise use global defaults + function getBarWidgetsForScreen(screenName) { + var override = _findScreenOverride(screenName); + if (override && override.enabled !== false && override.widgets !== undefined) { + return override.widgets; + } + return data.bar.widgets; + } + + // ----------------------------------------------------- + // Get effective bar density for a screen (with inheritance) + // If the screen has a density override and overrides are enabled, use it; otherwise use global default + function getBarDensityForScreen(screenName) { + var override = _findScreenOverride(screenName); + if (override && override.enabled !== false && override.density !== undefined) { + return override.density; + } + return data.bar.density || "default"; + } + + // ----------------------------------------------------- + // Check if a screen has any overrides, optionally for a specific property + function hasScreenOverride(screenName, property) { + var override = _findScreenOverride(screenName); + if (!override) { + return false; + } + if (property) { + return override[property] !== undefined; + } + // Check if screen has any override property (besides "name") + var keys = Object.keys(override); + return keys.length > 1 || (keys.length === 1 && keys[0] !== "name"); + } + + // ----------------------------------------------------- + // Get the screen override entry directly (for in-place modifications) + // Returns the actual entry object from the array, not a copy + function getScreenOverrideEntry(screenName) { + return _findScreenOverride(screenName); + } + + // ----------------------------------------------------- + // Set a per-screen override + function setScreenOverride(screenName, property, value) { + if (!screenName) + return; + + var overrides = JSON.parse(JSON.stringify(data.bar.screenOverrides || [])); + if (overrides.length === undefined) { + overrides = []; + } + + var index = -1; + for (var i = 0; i < overrides.length; i++) { + if (overrides[i] && overrides[i].name === screenName) { + index = i; + break; + } + } + + if (index === -1) { + // Create new entry + var newEntry = { + "name": screenName + }; + newEntry[property] = value; + overrides.push(newEntry); + } else { + // Update existing entry + overrides[index][property] = value; + } + data.bar.screenOverrides = overrides; + } + + // ----------------------------------------------------- + // Clear a per-screen override (revert to global default) + // If property is null, clears all overrides for that screen + function clearScreenOverride(screenName, property) { + if (!screenName) + return; + + var overrides = data.bar.screenOverrides; + if (!overrides || overrides.length === undefined) { + return; + } + + overrides = JSON.parse(JSON.stringify(overrides)); + + var index = -1; + for (var i = 0; i < overrides.length; i++) { + if (overrides[i] && overrides[i].name === screenName) { + index = i; + break; + } + } + + if (index === -1) { + return; + } + + if (property) { + delete overrides[index][property]; + // Remove screen entry if only "name" remains + var keys = Object.keys(overrides[index]); + if (keys.length <= 1 && (keys.length === 0 || keys[0] === "name")) { + overrides.splice(index, 1); + } + } else { + overrides.splice(index, 1); + } + data.bar.screenOverrides = overrides; + } + // ----------------------------------------------------- // Public function to trigger immediate settings saving function saveImmediate() { diff --git a/Commons/Style.qml b/Commons/Style.qml index a647e6d8b..2c939436e 100644 --- a/Commons/Style.qml +++ b/Commons/Style.qml @@ -160,4 +160,78 @@ Singleton { function toEven(n) { return Math.floor(n / 2) * 2; } + + // Get bar height for a specific density and orientation + function getBarHeightForDensity(density, isVertical) { + let h; + switch (density) { + case "mini": + h = isVertical ? 23 : 21; + break; + case "compact": + h = isVertical ? 27 : 25; + break; + case "comfortable": + h = isVertical ? 39 : 37; + break; + case "spacious": + h = isVertical ? 49 : 47; + break; + default: + case "default": + h = isVertical ? 33 : 31; + } + return toOdd(h); + } + + // Get capsule height for a specific density and bar height + function getCapsuleHeightForDensity(density, barHeight) { + let h; + switch (density) { + case "mini": + h = Math.round(barHeight * 0.90); + break; + case "compact": + h = Math.round(barHeight * 0.85); + break; + case "comfortable": + h = Math.round(barHeight * 0.75); + break; + case "spacious": + h = Math.round(barHeight * 0.65); + break; + default: + h = Math.round(barHeight * 0.82); + break; + } + return toOdd(h); + } + + // Get bar font size for a specific bar height, capsule height, and orientation + function getBarFontSizeForDensity(barHeight, capsuleHeight, isVertical) { + const baseFontSize = Math.max(1, (barHeight / capsuleHeight) * Style.fontSizeXXS); + return isVertical ? baseFontSize * 0.9 : baseFontSize; + } + + // Convenience functions for per-screen bar sizing + function getBarHeightForScreen(screenName) { + var density = Settings.getBarDensityForScreen(screenName); + var position = Settings.getBarPositionForScreen(screenName); + var isVertical = position === "left" || position === "right"; + return getBarHeightForDensity(density, isVertical); + } + + function getCapsuleHeightForScreen(screenName) { + var barHeight = getBarHeightForScreen(screenName); + var density = Settings.getBarDensityForScreen(screenName); + return getCapsuleHeightForDensity(density, barHeight); + } + + function getBarFontSizeForScreen(screenName) { + var barHeight = getBarHeightForScreen(screenName); + var capsuleHeight = getCapsuleHeightForScreen(screenName); + var position = Settings.getBarPositionForScreen(screenName); + var isVertical = position === "left" || position === "right"; + return getBarFontSizeForDensity(barHeight, capsuleHeight, isVertical); + } } diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index e93c9dba2..93fab1807 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -33,11 +33,22 @@ Item { // Expose the actual bar Item for unified background system readonly property var barItem: barRegion - // Bar positioning properties - readonly property string barPosition: Settings.data.bar.position || "top" + // Bar positioning properties (per-screen) + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" readonly property bool barFloating: Settings.data.bar.floating || false + // Bar density (per-screen) + readonly property string barDensity: Settings.getBarDensityForScreen(screen?.name) + + // Bar sizing based on per-screen density + readonly property real barHeight: Style.getBarHeightForDensity(barDensity, barIsVertical) + readonly property real capsuleHeight: Style.getCapsuleHeightForDensity(barDensity, barHeight) + readonly property real barFontSize: Style.getBarFontSizeForDensity(barHeight, capsuleHeight, barIsVertical) + + // Bar widgets (per-screen) + readonly property var barWidgets: Settings.getBarWidgetsForScreen(screen?.name) + // Fill the parent (the Loader) anchors.fill: parent @@ -72,10 +83,10 @@ Item { id: bar // Position and size the bar content based on orientation - x: (root.barPosition === "right") ? (parent.width - Style.barHeight) : 0 - y: (root.barPosition === "bottom") ? (parent.height - Style.barHeight) : 0 - width: root.barIsVertical ? Style.barHeight : parent.width - height: root.barIsVertical ? parent.height : Style.barHeight + x: (root.barPosition === "right") ? (parent.width - root.barHeight) : 0 + y: (root.barPosition === "bottom") ? (parent.height - root.barHeight) : 0 + width: root.barIsVertical ? root.barHeight : parent.width + height: root.barIsVertical ? parent.height : root.barHeight // Corner states for new unified background system // State -1: No radius (flat/square corner) @@ -196,7 +207,7 @@ Item { Loader { anchors.fill: parent - sourceComponent: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? verticalBarComponent : horizontalBarComponent + sourceComponent: root.barIsVertical ? verticalBarComponent : horizontalBarComponent } } } @@ -217,7 +228,7 @@ Item { spacing: Style.marginS Repeater { - model: root.filterValidWidgets(Settings.data.bar.widgets.left) + model: root.filterValidWidgets(root.barWidgets.left) delegate: BarWidgetLoader { required property var modelData required property int index @@ -228,7 +239,7 @@ Item { "widgetId": modelData.id, "section": "left", "sectionWidgetIndex": index, - "sectionWidgetsCount": Settings.data.bar.widgets.left.length + "sectionWidgetsCount": root.barWidgets.left.length }) Layout.alignment: Qt.AlignHCenter } @@ -242,7 +253,7 @@ Item { spacing: Style.marginS Repeater { - model: root.filterValidWidgets(Settings.data.bar.widgets.center) + model: root.filterValidWidgets(root.barWidgets.center) delegate: BarWidgetLoader { required property var modelData required property int index @@ -253,7 +264,7 @@ Item { "widgetId": modelData.id, "section": "center", "sectionWidgetIndex": index, - "sectionWidgetsCount": Settings.data.bar.widgets.center.length + "sectionWidgetsCount": root.barWidgets.center.length }) Layout.alignment: Qt.AlignHCenter } @@ -268,7 +279,7 @@ Item { spacing: Style.marginS Repeater { - model: root.filterValidWidgets(Settings.data.bar.widgets.right) + model: root.filterValidWidgets(root.barWidgets.right) delegate: BarWidgetLoader { required property var modelData required property int index @@ -279,7 +290,7 @@ Item { "widgetId": modelData.id, "section": "right", "sectionWidgetIndex": index, - "sectionWidgetsCount": Settings.data.bar.widgets.right.length + "sectionWidgetsCount": root.barWidgets.right.length }) Layout.alignment: Qt.AlignHCenter } @@ -305,7 +316,7 @@ Item { spacing: Style.marginS Repeater { - model: root.filterValidWidgets(Settings.data.bar.widgets.left) + model: root.filterValidWidgets(root.barWidgets.left) delegate: BarWidgetLoader { required property var modelData required property int index @@ -316,7 +327,7 @@ Item { "widgetId": modelData.id, "section": "left", "sectionWidgetIndex": index, - "sectionWidgetsCount": Settings.data.bar.widgets.left.length + "sectionWidgetsCount": root.barWidgets.left.length }) Layout.alignment: Qt.AlignVCenter } @@ -332,7 +343,7 @@ Item { spacing: Style.marginS Repeater { - model: root.filterValidWidgets(Settings.data.bar.widgets.center) + model: root.filterValidWidgets(root.barWidgets.center) delegate: BarWidgetLoader { required property var modelData required property int index @@ -343,7 +354,7 @@ Item { "widgetId": modelData.id, "section": "center", "sectionWidgetIndex": index, - "sectionWidgetsCount": Settings.data.bar.widgets.center.length + "sectionWidgetsCount": root.barWidgets.center.length }) Layout.alignment: Qt.AlignVCenter } @@ -360,7 +371,7 @@ Item { spacing: Style.marginS Repeater { - model: root.filterValidWidgets(Settings.data.bar.widgets.right) + model: root.filterValidWidgets(root.barWidgets.right) delegate: BarWidgetLoader { required property var modelData required property int index @@ -371,7 +382,7 @@ Item { "widgetId": modelData.id, "section": "right", "sectionWidgetIndex": index, - "sectionWidgetsCount": Settings.data.bar.widgets.right.length + "sectionWidgetsCount": root.barWidgets.right.length }) Layout.alignment: Qt.AlignVCenter } diff --git a/Modules/Bar/Extras/BarExclusionZone.qml b/Modules/Bar/Extras/BarExclusionZone.qml index 51219038a..4a4ef9cc2 100644 --- a/Modules/Bar/Extras/BarExclusionZone.qml +++ b/Modules/Bar/Extras/BarExclusionZone.qml @@ -14,11 +14,12 @@ PanelWindow { property bool exclusive: Settings.data.bar.exclusive !== undefined ? Settings.data.bar.exclusive : false - readonly property string barPosition: Settings.data.bar.position || "top" + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" readonly property bool barFloating: Settings.data.bar.floating || false readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal : 0 readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical : 0 + readonly property real barHeight: Style.getBarHeightForScreen(screen?.name) // Invisible - just reserves space color: "transparent" @@ -45,9 +46,9 @@ PanelWindow { // Vertical bar: reserve bar height + margin on the anchored edge only if (barFloating) { // For left bar, reserve left margin; for right bar, reserve right margin - return Style.barHeight + barMarginH; + return barHeight + barMarginH; } - return Style.barHeight; + return barHeight; } return 0; // Auto-width when left/right anchors are true } @@ -57,9 +58,9 @@ PanelWindow { // Horizontal bar: reserve bar height + margin on the anchored edge only if (barFloating) { // For top bar, reserve top margin; for bottom bar, reserve bottom margin - return Style.barHeight + barMarginV; + return barHeight + barMarginV; } - return Style.barHeight; + return barHeight; } return 0; // Auto-height when top/bottom anchors are true } diff --git a/Modules/Bar/Extras/BarPill.qml b/Modules/Bar/Extras/BarPill.qml index c07ded86d..428068601 100644 --- a/Modules/Bar/Extras/BarPill.qml +++ b/Modules/Bar/Extras/BarPill.qml @@ -22,7 +22,7 @@ Item { property color customBackgroundColor: "transparent" property color customTextIconColor: "transparent" - readonly property string barPosition: Settings.data.bar.position + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" signal shown diff --git a/Modules/Bar/Extras/BarPillHorizontal.qml b/Modules/Bar/Extras/BarPillHorizontal.qml index c265cb800..ab5a079f2 100644 --- a/Modules/Bar/Extras/BarPillHorizontal.qml +++ b/Modules/Bar/Extras/BarPillHorizontal.qml @@ -41,9 +41,9 @@ Item { property bool showPill: false property bool shouldAnimateHide: false - readonly property int pillHeight: Style.capsuleHeight - readonly property int pillPaddingHorizontal: Math.round(Style.capsuleHeight * 0.2) - readonly property int pillOverlap: Math.round(Style.capsuleHeight * 0.5) + readonly property int pillHeight: Style.getCapsuleHeightForScreen(screen?.name) + readonly property int pillPaddingHorizontal: Math.round(pillHeight * 0.2) + readonly property int pillOverlap: Math.round(pillHeight * 0.5) readonly property int pillMaxWidth: Math.max(1, Math.round(textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap)) // Always prioritize hover color, then the custom one and finally the fallback color @@ -258,7 +258,7 @@ Item { onEntered: { hovered = true; root.entered(); - TooltipService.show(root, root.tooltipText, BarService.getTooltipDirection(), (forceOpen || forceClose) ? Style.tooltipDelay : Style.tooltipDelayLong); + TooltipService.show(root, root.tooltipText, BarService.getTooltipDirection(root.screen?.name), (forceOpen || forceClose) ? Style.tooltipDelay : Style.tooltipDelayLong); if (forceClose) { return; } diff --git a/Modules/Bar/Extras/BarPillVertical.qml b/Modules/Bar/Extras/BarPillVertical.qml index 33a7a06b8..08633c36f 100644 --- a/Modules/Bar/Extras/BarPillVertical.qml +++ b/Modules/Bar/Extras/BarPillVertical.qml @@ -39,7 +39,7 @@ Item { property bool shouldAnimateHide: false // Sizing logic for vertical bars - readonly property int buttonSize: Style.capsuleHeight + readonly property int buttonSize: Style.getCapsuleHeightForScreen(screen?.name) readonly property int pillHeight: buttonSize readonly property int pillOverlap: Math.round(buttonSize * 0.5) readonly property int maxPillWidth: rotateText ? Math.max(buttonSize, Math.round(textItem.implicitHeight + Style.marginXL)) : buttonSize @@ -297,7 +297,7 @@ Item { onEntered: { hovered = true; root.entered(); - TooltipService.show(root, root.tooltipText, BarService.getTooltipDirection(), (forceOpen || forceClose) ? Style.tooltipDelay : Style.tooltipDelayLong); + TooltipService.show(root, root.tooltipText, BarService.getTooltipDirection(root.screen?.name), (forceOpen || forceClose) ? Style.tooltipDelay : Style.tooltipDelayLong); if (forceClose) { return; } diff --git a/Modules/Bar/Extras/TrayMenu.qml b/Modules/Bar/Extras/TrayMenu.qml index 897b97128..811bc6342 100644 --- a/Modules/Bar/Extras/TrayMenu.qml +++ b/Modules/Bar/Extras/TrayMenu.qml @@ -84,7 +84,7 @@ PopupWindow { } anchor.rect.y: { if (anchorItem && screen) { - const barPosition = Settings.data.bar.position; + const barPosition = Settings.getBarPositionForScreen(root.screen?.name); // Calculate base Y offset (relative to anchor item) let baseY = anchorY; @@ -138,7 +138,7 @@ PopupWindow { if (isSubMenu) { return anchorY; } - return anchorY + (Settings.data.bar.position === "bottom" ? -implicitHeight : Style.barHeight); + return anchorY + (Settings.getBarPositionForScreen(root.screen?.name) === "bottom" ? -implicitHeight : Style.barHeight); } function showAt(item, x, y) { @@ -328,7 +328,7 @@ PopupWindow { // Determine submenu opening direction let openLeft = false; - const barPosition = Settings.data.bar.position; + const barPosition = Settings.getBarPositionForScreen(root.screen?.name); const globalPos = entry.mapToItem(null, 0, 0); if (barPosition === "right") { diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 5d539db11..48cfd6e2e 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -41,7 +41,8 @@ Item { readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth || 0, screen ? screen.width * 0.06 : 0) readonly property bool useFixedWidth: (widgetSettings.useFixedWidth !== undefined) ? widgetSettings.useFixedWidth : (widgetMetadata.useFixedWidth || false) - readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" readonly property bool hasFocusedWindow: CompositorService.getFocusedWindow() !== null readonly property string windowTitle: CompositorService.getFocusedWindowTitle() || "No active window" readonly property string fallbackIcon: "user-desktop" @@ -318,7 +319,7 @@ Item { acceptedButtons: Qt.LeftButton | Qt.RightButton onEntered: { if ((windowTitle !== "") && isVerticalBar || (scrollingMode === "never")) { - TooltipService.show(root, windowTitle, BarService.getTooltipDirection()); + TooltipService.show(root, windowTitle, BarService.getTooltipDirection(root.screen?.name)); } } onExited: { diff --git a/Modules/Bar/Widgets/AudioVisualizer.qml b/Modules/Bar/Widgets/AudioVisualizer.qml index a519c8559..fa219f1a6 100644 --- a/Modules/Bar/Widgets/AudioVisualizer.qml +++ b/Modules/Bar/Widgets/AudioVisualizer.qml @@ -19,7 +19,8 @@ Item { property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 - readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetSettings: { @@ -190,7 +191,7 @@ Item { fillColor: root.fillColor showMinimumSignal: true vertical: root.isVerticalBar - barPosition: Settings.data.bar.position + barPosition: root.barPosition } } diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml index 534f085f2..4bdc2d1f0 100644 --- a/Modules/Bar/Widgets/Battery.qml +++ b/Modules/Bar/Widgets/Battery.qml @@ -32,7 +32,8 @@ Item { return {}; } - readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode readonly property real warningThreshold: widgetSettings.warningThreshold !== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold readonly property bool hideIfNotDetected: widgetSettings.hideIfNotDetected !== undefined ? widgetSettings.hideIfNotDetected : widgetMetadata.hideIfNotDetected diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml index 8c56e7332..645e0fa42 100644 --- a/Modules/Bar/Widgets/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -29,7 +29,8 @@ Item { return {}; } - readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode implicitWidth: pill.width diff --git a/Modules/Bar/Widgets/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml index cc16db94b..4fe1c8518 100644 --- a/Modules/Bar/Widgets/Brightness.qml +++ b/Modules/Bar/Widgets/Brightness.qml @@ -30,7 +30,8 @@ Item { return {}; } - readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode // Used to avoid opening the pill on Quickshell startup diff --git a/Modules/Bar/Widgets/Clock.qml b/Modules/Bar/Widgets/Clock.qml index e4a0de8d0..265ba6f49 100644 --- a/Modules/Bar/Widgets/Clock.qml +++ b/Modules/Bar/Widgets/Clock.qml @@ -29,7 +29,7 @@ Rectangle { return {}; } - readonly property string barPosition: Settings.data.bar.position + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" readonly property var now: Time.now @@ -164,7 +164,7 @@ Rectangle { acceptedButtons: Qt.LeftButton | Qt.RightButton onEntered: { if (!PanelService.getPanel("clockPanel", screen)?.active) { - TooltipService.show(root, buildTooltipText(), BarService.getTooltipDirection()); + TooltipService.show(root, buildTooltipText(), BarService.getTooltipDirection(root.screen?.name)); tooltipRefreshTimer.start(); } } diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml index d6cb3fe63..c3eda213e 100644 --- a/Modules/Bar/Widgets/ControlCenter.qml +++ b/Modules/Bar/Widgets/ControlCenter.qml @@ -82,7 +82,7 @@ NIconButton { // If using distro logo, don't use theme icon. icon: (customIconPath === "" && !useDistroLogo) ? customIcon : "" tooltipText: I18n.tr("tooltips.open-control-center") - tooltipDirection: BarService.getTooltipDirection() + tooltipDirection: BarService.getTooltipDirection(screen?.name) baseSize: Style.capsuleHeight applyUiScale: false customRadius: Style.radiusL diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index f619db5ae..ebd29c9d3 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -31,7 +31,8 @@ Item { return {}; } - readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec diff --git a/Modules/Bar/Widgets/DarkMode.qml b/Modules/Bar/Widgets/DarkMode.qml index 876aac5d8..ad862deb0 100644 --- a/Modules/Bar/Widgets/DarkMode.qml +++ b/Modules/Bar/Widgets/DarkMode.qml @@ -10,7 +10,7 @@ NIconButton { icon: "dark-mode" tooltipText: Settings.data.colorSchemes.darkMode ? I18n.tr("tooltips.switch-to-light-mode") : I18n.tr("tooltips.switch-to-dark-mode") - tooltipDirection: BarService.getTooltipDirection() + tooltipDirection: BarService.getTooltipDirection(screen?.name) baseSize: Style.capsuleHeight applyUiScale: false customRadius: Style.radiusL diff --git a/Modules/Bar/Widgets/KeepAwake.qml b/Modules/Bar/Widgets/KeepAwake.qml index 81844ca08..2c2fd9423 100644 --- a/Modules/Bar/Widgets/KeepAwake.qml +++ b/Modules/Bar/Widgets/KeepAwake.qml @@ -29,7 +29,8 @@ Item { return {}; } - readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" implicitWidth: pill.width implicitHeight: pill.height diff --git a/Modules/Bar/Widgets/Launcher.qml b/Modules/Bar/Widgets/Launcher.qml index b91ae15e6..47576e689 100644 --- a/Modules/Bar/Widgets/Launcher.qml +++ b/Modules/Bar/Widgets/Launcher.qml @@ -32,7 +32,7 @@ NIconButton { icon: iconName tooltipText: I18n.tr("actions.open-launcher") - tooltipDirection: BarService.getTooltipDirection() + tooltipDirection: BarService.getTooltipDirection(screen?.name) baseSize: Style.capsuleHeight applyUiScale: false customRadius: Style.radiusL diff --git a/Modules/Bar/Widgets/LockKeys.qml b/Modules/Bar/Widgets/LockKeys.qml index e171f692d..e6da9fccc 100644 --- a/Modules/Bar/Widgets/LockKeys.qml +++ b/Modules/Bar/Widgets/LockKeys.qml @@ -31,7 +31,7 @@ Rectangle { return {}; } - readonly property string barPosition: Settings.data.bar.position + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool isVertical: barPosition === "left" || barPosition === "right" readonly property bool showCaps: (widgetSettings.showCapsLock !== undefined) ? widgetSettings.showCapsLock : widgetMetadata.showCapsLock diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 4eda688ad..a6860db01 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -30,8 +30,9 @@ Item { return {}; } - // Bar orientation - readonly property bool isVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + // Bar orientation (per-screen) + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isVertical: barPosition === "left" || barPosition === "right" // Widget settings readonly property string hideMode: (widgetSettings.hideMode !== undefined) ? widgetSettings.hideMode : "hidden" @@ -387,7 +388,7 @@ Item { onEntered: { if (isVertical || scrollingMode === "never") { - TooltipService.show(root, title, BarService.getTooltipDirection()); + TooltipService.show(root, title, BarService.getTooltipDirection(root.screen?.name)); } } onExited: TooltipService.hide() @@ -404,7 +405,7 @@ Item { values: CavaService.values fillColor: Color.mPrimary opacity: 0.4 - barPosition: Settings.data.bar.position + barPosition: root.barPosition } } diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml index 0a0bd05ea..2c019fad5 100644 --- a/Modules/Bar/Widgets/Microphone.qml +++ b/Modules/Bar/Widgets/Microphone.qml @@ -32,7 +32,8 @@ Item { return {}; } - readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode readonly property string middleClickCommand: (widgetSettings.middleClickCommand !== undefined) ? widgetSettings.middleClickCommand : widgetMetadata.middleClickCommand diff --git a/Modules/Bar/Widgets/Network.qml b/Modules/Bar/Widgets/Network.qml index e014cb684..4f33027c9 100644 --- a/Modules/Bar/Widgets/Network.qml +++ b/Modules/Bar/Widgets/Network.qml @@ -29,7 +29,8 @@ Item { return {}; } - readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode implicitWidth: pill.width diff --git a/Modules/Bar/Widgets/NightLight.qml b/Modules/Bar/Widgets/NightLight.qml index 7a0ae72aa..eef5ed64f 100644 --- a/Modules/Bar/Widgets/NightLight.qml +++ b/Modules/Bar/Widgets/NightLight.qml @@ -26,7 +26,7 @@ NIconButton { icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off" tooltipText: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? I18n.tr("common.night-light") : I18n.tr("common.night-light")) : I18n.tr("common.night-light") - tooltipDirection: BarService.getTooltipDirection() + tooltipDirection: BarService.getTooltipDirection(screen?.name) onClicked: { // Check if wlsunset is available before enabling night light if (!ProgramCheckerService.wlsunsetAvailable) { diff --git a/Modules/Bar/Widgets/NoctaliaPerformance.qml b/Modules/Bar/Widgets/NoctaliaPerformance.qml index 0b0e67840..ea0c1f3d8 100644 --- a/Modules/Bar/Widgets/NoctaliaPerformance.qml +++ b/Modules/Bar/Widgets/NoctaliaPerformance.qml @@ -26,6 +26,6 @@ NIconButton { icon: PowerProfileService.noctaliaPerformanceMode ? "rocket" : "rocket-off" tooltipText: PowerProfileService.noctaliaPerformanceMode ? I18n.tr("tooltips.noctalia-performance-enabled") : I18n.tr("tooltips.noctalia-performance-enabled") - tooltipDirection: BarService.getTooltipDirection() + tooltipDirection: BarService.getTooltipDirection(screen?.name) onClicked: PowerProfileService.toggleNoctaliaPerformance() } diff --git a/Modules/Bar/Widgets/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml index 77b781ba9..1f6b6736f 100644 --- a/Modules/Bar/Widgets/NotificationHistory.qml +++ b/Modules/Bar/Widgets/NotificationHistory.qml @@ -54,7 +54,7 @@ NIconButton { customRadius: Style.radiusL icon: NotificationService.doNotDisturb ? "bell-off" : "bell" tooltipText: NotificationService.doNotDisturb ? I18n.tr("tooltips.open-notification-history-enable-dnd") : I18n.tr("tooltips.open-notification-history-enable-dnd") - tooltipDirection: BarService.getTooltipDirection() + tooltipDirection: BarService.getTooltipDirection(screen?.name) colorBg: Style.capsuleColor colorFg: Color.mOnSurface colorBorder: "transparent" diff --git a/Modules/Bar/Widgets/PowerProfile.qml b/Modules/Bar/Widgets/PowerProfile.qml index d63a2ef46..9319f294a 100644 --- a/Modules/Bar/Widgets/PowerProfile.qml +++ b/Modules/Bar/Widgets/PowerProfile.qml @@ -20,7 +20,7 @@ NIconButton { tooltipText: I18n.tr("tooltips.power-profile", { "profile": PowerProfileService.getName() }) - tooltipDirection: BarService.getTooltipDirection() + tooltipDirection: BarService.getTooltipDirection(screen?.name) colorBg: (PowerProfileService.profile === PowerProfile.Balanced) ? Style.capsuleColor : Color.mPrimary colorFg: (PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnSurface : Color.mOnPrimary colorBorder: "transparent" diff --git a/Modules/Bar/Widgets/SessionMenu.qml b/Modules/Bar/Widgets/SessionMenu.qml index e5de8f6b8..6744d11c3 100644 --- a/Modules/Bar/Widgets/SessionMenu.qml +++ b/Modules/Bar/Widgets/SessionMenu.qml @@ -52,7 +52,7 @@ NIconButton { customRadius: Style.radiusL icon: "power" tooltipText: I18n.tr("tooltips.session-menu") - tooltipDirection: BarService.getTooltipDirection() + tooltipDirection: BarService.getTooltipDirection(screen?.name) colorBg: Style.capsuleColor colorFg: root.iconColor colorBorder: "transparent" diff --git a/Modules/Bar/Widgets/Spacer.qml b/Modules/Bar/Widgets/Spacer.qml index c61e80624..549b99ada 100644 --- a/Modules/Bar/Widgets/Spacer.qml +++ b/Modules/Bar/Widgets/Spacer.qml @@ -27,7 +27,8 @@ Item { return {}; } - readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" readonly property int spacerSize: widgetSettings.width !== undefined ? widgetSettings.width : widgetMetadata.width implicitWidth: isBarVertical ? Style.barHeight : spacerSize diff --git a/Modules/Bar/Widgets/SystemMonitor.qml b/Modules/Bar/Widgets/SystemMonitor.qml index b485617c2..4c6fa87f8 100644 --- a/Modules/Bar/Widgets/SystemMonitor.qml +++ b/Modules/Bar/Widgets/SystemMonitor.qml @@ -32,7 +32,7 @@ Rectangle { return {}; } - readonly property string barPosition: Settings.data.bar.position + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool isVertical: barPosition === "left" || barPosition === "right" readonly property bool compactMode: widgetSettings.compactMode !== undefined ? widgetSettings.compactMode : widgetMetadata.compactMode @@ -170,7 +170,7 @@ Rectangle { } } onEntered: { - TooltipService.show(root, buildTooltipText(), BarService.getTooltipDirection()); + TooltipService.show(root, buildTooltipText(), BarService.getTooltipDirection(root.screen?.name)); tooltipRefreshTimer.start(); } onExited: { diff --git a/Modules/Bar/Widgets/Taskbar.qml b/Modules/Bar/Widgets/Taskbar.qml index 087723f65..55edf3315 100644 --- a/Modules/Bar/Widgets/Taskbar.qml +++ b/Modules/Bar/Widgets/Taskbar.qml @@ -20,7 +20,7 @@ Rectangle { property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 - readonly property string barPosition: Settings.data.bar.position + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right" property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] @@ -672,7 +672,7 @@ Rectangle { } onEntered: { root.hoveredWindowId = taskbarItem.modelData.id; - TooltipService.show(taskbarItem, taskbarItem.title, BarService.getTooltipDirection()); + TooltipService.show(taskbarItem, taskbarItem.title, BarService.getTooltipDirection(root.screen?.name)); } onExited: { root.hoveredWindowId = ""; diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index c628a5047..736c4f1d0 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -53,7 +53,7 @@ Rectangle { return {}; } - readonly property string barPosition: Settings.data.bar.position + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool isVertical: barPosition === "left" || barPosition === "right" readonly property bool density: Settings.data.bar.density readonly property int iconSize: Style.toOdd(Style.capsuleHeight * 0.65) @@ -290,7 +290,7 @@ Rectangle { id: chevronIconBefore visible: root.drawerEnabled && dropdownItems.length > 0 && BarService.getPillDirection(root) tooltipText: I18n.tr("tooltips.open-tray-dropdown") - tooltipDirection: BarService.getTooltipDirection() + tooltipDirection: BarService.getTooltipDirection(root.screen?.name) baseSize: Style.capsuleHeight applyUiScale: false customRadius: Style.radiusL @@ -433,7 +433,7 @@ Rectangle { if (popupMenuWindow) { popupMenuWindow.close(); } - TooltipService.show(trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection()); + TooltipService.show(trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection(root.screen?.name)); } onExited: TooltipService.hide() } @@ -446,7 +446,7 @@ Rectangle { id: chevronIconAfter visible: root.drawerEnabled && dropdownItems.length > 0 && !BarService.getPillDirection(root) tooltipText: I18n.tr("tooltips.open-tray-dropdown") - tooltipDirection: BarService.getTooltipDirection() + tooltipDirection: BarService.getTooltipDirection(root.screen?.name) baseSize: Style.capsuleHeight applyUiScale: false customRadius: Style.radiusL diff --git a/Modules/Bar/Widgets/VPN.qml b/Modules/Bar/Widgets/VPN.qml index 55cd6fc69..04ce0b611 100644 --- a/Modules/Bar/Widgets/VPN.qml +++ b/Modules/Bar/Widgets/VPN.qml @@ -28,7 +28,8 @@ Item { return {}; } - readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode implicitWidth: pill.width diff --git a/Modules/Bar/Widgets/Volume.qml b/Modules/Bar/Widgets/Volume.qml index eab658ddd..def44e864 100644 --- a/Modules/Bar/Widgets/Volume.qml +++ b/Modules/Bar/Widgets/Volume.qml @@ -31,7 +31,8 @@ Item { return {}; } - readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right" + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) + readonly property bool isBarVertical: barPosition === "left" || barPosition === "right" readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode readonly property string middleClickCommand: (widgetSettings.middleClickCommand !== undefined) ? widgetSettings.middleClickCommand : widgetMetadata.middleClickCommand diff --git a/Modules/Bar/Widgets/WallpaperSelector.qml b/Modules/Bar/Widgets/WallpaperSelector.qml index b64900a3f..b998c4d3a 100644 --- a/Modules/Bar/Widgets/WallpaperSelector.qml +++ b/Modules/Bar/Widgets/WallpaperSelector.qml @@ -18,7 +18,7 @@ NIconButton { customRadius: Style.radiusL icon: "wallpaper-selector" tooltipText: I18n.tr("tooltips.wallpaper-selector") - tooltipDirection: BarService.getTooltipDirection() + tooltipDirection: BarService.getTooltipDirection(screen?.name) colorBg: Style.capsuleColor colorFg: Color.mOnSurface colorBorder: "transparent" diff --git a/Modules/Bar/Widgets/Workspace.qml b/Modules/Bar/Widgets/Workspace.qml index b7ee3c362..ebb419195 100644 --- a/Modules/Bar/Widgets/Workspace.qml +++ b/Modules/Bar/Widgets/Workspace.qml @@ -34,7 +34,7 @@ Item { return {}; } - readonly property string barPosition: Settings.data.bar.position + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool isVertical: barPosition === "left" || barPosition === "right" readonly property real baseDimensionRatio: 0.65 * (widgetSettings.labelMode === "none" ? 0.75 : 1) @@ -899,7 +899,7 @@ Item { } onEntered: { groupedTaskbarItem.itemHovered = true; - TooltipService.show(groupedTaskbarItem, model.title || model.appId || "Unknown app.", BarService.getTooltipDirection()); + TooltipService.show(groupedTaskbarItem, model.title || model.appId || "Unknown app.", BarService.getTooltipDirection(root.screen?.name)); } onExited: { groupedTaskbarItem.itemHovered = false; diff --git a/Modules/DesktopWidgets/DesktopWidgets.qml b/Modules/DesktopWidgets/DesktopWidgets.qml index fc7c4c8d0..4a35d0581 100644 --- a/Modules/DesktopWidgets/DesktopWidgets.qml +++ b/Modules/DesktopWidgets/DesktopWidgets.qml @@ -307,7 +307,7 @@ Variants { id: editModeControlsPanel visible: DesktopWidgetRegistry.editMode && Settings.data.desktopWidgets.enabled - readonly property string barPos: Settings.data.bar.position || "top" + readonly property string barPos: Settings.getBarPositionForScreen(window.screen?.name) readonly property bool barFloating: Settings.data.bar.floating || false readonly property int barOffsetTop: { diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 4f25c6e66..0a22b384a 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -83,7 +83,7 @@ Loader { // Bar detection and positioning properties readonly property bool hasBar: modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false - readonly property bool barAtSameEdge: hasBar && Settings.data.bar.position === dockPosition + readonly property bool barAtSameEdge: hasBar && Settings.getBarPositionForScreen(modelData?.name) === dockPosition readonly property int barHeight: Style.barHeight // Shared state between windows @@ -401,10 +401,11 @@ Loader { id: dockContainerWrapper // Helper properties for orthogonal bar detection - readonly property bool barOnLeft: hasBar && Settings.data.bar.position === "left" && !Settings.data.bar.floating - readonly property bool barOnRight: hasBar && Settings.data.bar.position === "right" && !Settings.data.bar.floating - readonly property bool barOnTop: hasBar && Settings.data.bar.position === "top" && !Settings.data.bar.floating - readonly property bool barOnBottom: hasBar && Settings.data.bar.position === "bottom" && !Settings.data.bar.floating + readonly property string screenBarPosition: Settings.getBarPositionForScreen(modelData?.name) + readonly property bool barOnLeft: hasBar && screenBarPosition === "left" && !Settings.data.bar.floating + readonly property bool barOnRight: hasBar && screenBarPosition === "right" && !Settings.data.bar.floating + readonly property bool barOnTop: hasBar && screenBarPosition === "top" && !Settings.data.bar.floating + readonly property bool barOnBottom: hasBar && screenBarPosition === "bottom" && !Settings.data.bar.floating // Calculate padding needed to shift center to match exclusive mode readonly property int extraTop: (isVertical && !exclusive && barOnTop) ? Style.barHeight : 0 diff --git a/Modules/MainScreen/BarContentWindow.qml b/Modules/MainScreen/BarContentWindow.qml index d67978235..87a3a226d 100644 --- a/Modules/MainScreen/BarContentWindow.qml +++ b/Modules/MainScreen/BarContentWindow.qml @@ -29,12 +29,13 @@ PanelWindow { WlrLayershell.layer: WlrLayer.Top WlrLayershell.exclusionMode: ExclusionMode.Ignore // Don't reserve space - BarExclusionZone in MainScreen handles that - // Position and size to match bar location - readonly property string barPosition: Settings.data.bar.position || "top" + // Position and size to match bar location (per-screen) + readonly property string barPosition: Settings.getBarPositionForScreen(barWindow.screen?.name) readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" readonly property bool barFloating: Settings.data.bar.floating || false readonly property real barMarginH: Math.ceil(barFloating ? Settings.data.bar.marginHorizontal : 0) readonly property real barMarginV: Math.ceil(barFloating ? Settings.data.bar.marginVertical : 0) + readonly property real barHeight: Style.getBarHeightForScreen(barWindow.screen?.name) // Anchor to the bar's edge anchors { @@ -53,8 +54,8 @@ PanelWindow { } // Set a tight window size - implicitWidth: barIsVertical ? Style.barHeight : barWindow.screen.width - implicitHeight: barIsVertical ? barWindow.screen.height : Style.barHeight + implicitWidth: barIsVertical ? barHeight : barWindow.screen.width + implicitHeight: barIsVertical ? barWindow.screen.height : barHeight // Bar content - just the widgets, no background Bar { diff --git a/Modules/MainScreen/BarExclusionZone.qml b/Modules/MainScreen/BarExclusionZone.qml index e20bd2b0a..81a3d3cef 100644 --- a/Modules/MainScreen/BarExclusionZone.qml +++ b/Modules/MainScreen/BarExclusionZone.qml @@ -14,7 +14,7 @@ PanelWindow { id: root readonly property bool exclusive: Settings.data.bar.exclusive - readonly property string barPosition: Settings.data.bar.position || "top" + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" readonly property bool barFloating: Settings.data.bar.floating || false readonly property real barMarginH: barFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0 diff --git a/Modules/MainScreen/MainScreen.qml b/Modules/MainScreen/MainScreen.qml index 43118e217..99d713290 100644 --- a/Modules/MainScreen/MainScreen.qml +++ b/Modules/MainScreen/MainScreen.qml @@ -320,28 +320,29 @@ PanelWindow { // Screen reference property ShellScreen screen: root.screen - // Bar background positioning properties - readonly property string barPosition: Settings.data.bar.position || "top" + // Bar background positioning properties (per-screen) + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" readonly property bool barFloating: Settings.data.bar.floating || false readonly property real barMarginH: barFloating ? Math.floor(Settings.data.bar.marginHorizontal) : 0 readonly property real barMarginV: barFloating ? Math.floor(Settings.data.bar.marginVertical) : 0 + readonly property real barHeight: Style.getBarHeightForScreen(screen?.name) // Expose bar dimensions directly on this Item for BarBackground // Use screen dimensions directly x: { if (barPosition === "right") - return screen.width - Style.barHeight - barMarginH; + return screen.width - barHeight - barMarginH; return barMarginH; } y: { if (barPosition === "bottom") - return screen.height - Style.barHeight - barMarginV; + return screen.height - barHeight - barMarginV; return barMarginV; } width: { if (barIsVertical) { - return Style.barHeight; + return barHeight; } return screen.width - barMarginH * 2; } @@ -349,7 +350,7 @@ PanelWindow { if (barIsVertical) { return screen.height - barMarginV * 2; } - return Style.barHeight; + return barHeight; } // Corner states (same as Bar.qml) diff --git a/Modules/MainScreen/SmartPanel.qml b/Modules/MainScreen/SmartPanel.qml index 3ceacd09f..dd3cc92dc 100644 --- a/Modules/MainScreen/SmartPanel.qml +++ b/Modules/MainScreen/SmartPanel.qml @@ -88,7 +88,7 @@ Item { // Expose panel region for background rendering readonly property var panelRegion: panelContent.geometryPlaceholder - readonly property string barPosition: Settings.data.bar.position + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool barIsVertical: barPosition === "left" || barPosition === "right" readonly property bool barFloating: Settings.data.bar.floating readonly property real barMarginH: barFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0 diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index 09c1862ce..0579589c5 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -76,7 +76,7 @@ Variants { readonly property bool isRight: location.endsWith("_right") readonly property bool isCentered: location === "top" || location === "bottom" - readonly property string barPos: Settings.data.bar.position + readonly property string barPos: Settings.getBarPositionForScreen(notifWindow.screen?.name) readonly property bool isFloating: Settings.data.bar.floating readonly property int notifWidth: Math.round(440 * Style.uiScaleRatio) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 4e6e2cd59..aa8b0a436 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -420,12 +420,14 @@ Variants { anchors.left: isLeft anchors.right: isRight + readonly property string screenBarPosition: Settings.getBarPositionForScreen(root.modelData?.name) + function calculateMargin(isAnchored, position) { if (!isAnchored) return 0; let base = Style.marginM; - if (Settings.data.bar.position === position) { + if (screenBarPosition === position) { const isVertical = position === "top" || position === "bottom"; const floatExtra = Math.ceil(Settings.data.bar.floating ? (isVertical ? Settings.data.bar.marginVertical : Settings.data.bar.marginHorizontal) : 0); return Style.barHeight + base + floatExtra; diff --git a/Modules/Panels/Launcher/Launcher.qml b/Modules/Panels/Launcher/Launcher.qml index dfe5dfe8a..3123ae78a 100644 --- a/Modules/Panels/Launcher/Launcher.qml +++ b/Modules/Panels/Launcher/Launcher.qml @@ -36,12 +36,13 @@ SmartPanel { preferredHeightRatio: 0.5 // Positioning + readonly property string screenBarPosition: Settings.getBarPositionForScreen(screen?.name) readonly property string panelPosition: { if (Settings.data.appLauncher.position === "follow_bar") { - if (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") { - return `center_${Settings.data.bar.position}`; + if (screenBarPosition === "left" || screenBarPosition === "right") { + return `center_${screenBarPosition}`; } else { - return `${Settings.data.bar.position}_center`; + return `${screenBarPosition}_center`; } } else { return Settings.data.appLauncher.position; diff --git a/Modules/Panels/Media/MediaPlayerPanel.qml b/Modules/Panels/Media/MediaPlayerPanel.qml index 7ea037ad0..8d54d0b83 100644 --- a/Modules/Panels/Media/MediaPlayerPanel.qml +++ b/Modules/Panels/Media/MediaPlayerPanel.qml @@ -484,7 +484,7 @@ SmartPanel { values: CavaService.values fillColor: Color.mPrimary opacity: 0.4 - barPosition: Settings.data.bar.position + barPosition: Settings.getBarPositionForScreen(root.screen?.name) } } diff --git a/Modules/Panels/Settings/Bar/MonitorWidgetsDialog.qml b/Modules/Panels/Settings/Bar/MonitorWidgetsDialog.qml new file mode 100644 index 000000000..9e670ddba --- /dev/null +++ b/Modules/Panels/Settings/Bar/MonitorWidgetsDialog.qml @@ -0,0 +1,294 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import "../../../../Helpers/QtObj2JS.js" as QtObj2JS +import qs.Commons +import qs.Services.Noctalia +import qs.Services.UI +import qs.Widgets + +// Monitor Widgets Configuration Dialog +Popup { + id: root + + required property string screenName + + // Helper function to find screen from parent chain + function findScreen() { + var item = parent; + while (item) { + if (item.screen !== undefined) { + return item.screen; + } + item = item.parent; + } + return null; + } + + readonly property var screen: findScreen() + readonly property real maxHeight: screen ? screen.height * 0.85 : (parent ? parent.height * 0.85 : 700) + readonly property real maxWidth: screen ? screen.width * 0.6 : (parent ? parent.width * 0.6 : 600) + + width: Math.min(Math.max(content.implicitWidth + padding * 2, 500), maxWidth) + height: Math.min(content.implicitHeight + padding * 2, maxHeight) + padding: Style.marginXL + modal: true + dim: false + anchors.centerIn: parent + + onOpened: { + forceActiveFocus(); + updateAvailableWidgetsModel(); + } + + background: Rectangle { + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mPrimary + border.width: Style.borderM + } + + // Helper to get/set widgets for this screen + function _getWidgetsContainer() { + // Ensure screen has widget overrides + if (!Settings.hasScreenOverride(screenName, "widgets")) { + // Deep copy current widgets to create override + var currentWidgets = Settings.getBarWidgetsForScreen(screenName); + var widgetsCopy = QtObj2JS.qtObjectToPlainObject(currentWidgets); + Settings.setScreenOverride(screenName, "widgets", widgetsCopy); + } + var entry = Settings.getScreenOverrideEntry(screenName); + return entry ? entry.widgets : Settings.data.bar.widgets; + } + + // Widget manipulation functions + function _addWidgetToSection(widgetId, section) { + var newWidget = { + "id": widgetId + }; + if (BarWidgetRegistry.widgetHasUserSettings(widgetId)) { + var metadata = BarWidgetRegistry.widgetMetadata[widgetId]; + if (metadata) { + Object.keys(metadata).forEach(function (key) { + if (key !== "allowUserSettings") { + newWidget[key] = metadata[key]; + } + }); + } + } + var widgets = _getWidgetsContainer(); + widgets[section].push(newWidget); + } + + function _removeWidgetFromSection(section, index) { + var widgets = _getWidgetsContainer(); + if (index >= 0 && index < widgets[section].length) { + var newArray = widgets[section].slice(); + var removedWidgets = newArray.splice(index, 1); + widgets[section] = newArray; + + if (removedWidgets[0].id === "ControlCenter" && BarService.lookupWidget("ControlCenter") === undefined) { + ToastService.showWarning(I18n.tr("toast.missing-control-center.label"), I18n.tr("toast.missing-control-center.description"), 12000); + } + } + } + + function _reorderWidgetInSection(section, fromIndex, toIndex) { + var widgets = _getWidgetsContainer(); + if (fromIndex >= 0 && fromIndex < widgets[section].length && toIndex >= 0 && toIndex < widgets[section].length) { + var newArray = widgets[section].slice(); + var item = newArray[fromIndex]; + newArray.splice(fromIndex, 1); + newArray.splice(toIndex, 0, item); + widgets[section] = newArray; + } + } + + function _updateWidgetSettingsInSection(section, index, settings) { + var widgets = _getWidgetsContainer(); + widgets[section][index] = settings; + } + + function _moveWidgetBetweenSections(fromSection, index, toSection) { + var widgets = _getWidgetsContainer(); + if (index >= 0 && index < widgets[fromSection].length) { + var widget = widgets[fromSection][index]; + var sourceArray = widgets[fromSection].slice(); + sourceArray.splice(index, 1); + widgets[fromSection] = sourceArray; + var targetArray = widgets[toSection].slice(); + targetArray.push(widget); + widgets[toSection] = targetArray; + } + } + + // Available widgets ListModel + function updateAvailableWidgetsModel() { + availableWidgetsModel.clear(); + var widgetIds = BarWidgetRegistry.getAvailableWidgets(); + if (!widgetIds) + return; + for (var i = 0; i < widgetIds.length; i++) { + var id = widgetIds[i]; + var displayName = id; + if (BarWidgetRegistry.isPluginWidget(id)) { + var pluginId = id.replace("plugin:", ""); + var manifest = PluginRegistry.getPluginManifest(pluginId); + if (manifest && manifest.name) { + displayName = manifest.name; + } else { + displayName = pluginId; + } + } + availableWidgetsModel.append({ + "key": id, + "name": displayName + }); + } + } + + ListModel { + id: availableWidgetsModel + } + + // Get effective widgets for this screen + readonly property var effectiveWidgets: Settings.getBarWidgetsForScreen(screenName) + + contentItem: FocusScope { + focus: true + + ColumnLayout { + id: content + anchors.fill: parent + spacing: Style.marginM + + // Title + RowLayout { + Layout.fillWidth: true + + NText { + text: I18n.tr("panels.bar.monitor-widgets-title", { + "monitor": screenName + }) + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("common.close") + onClicked: root.close() + } + } + + // Separator + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + } + + // Reset to global button + NButton { + visible: Settings.hasScreenOverride(root.screenName, "widgets") + text: I18n.tr("panels.bar.use-global-widgets") + icon: "refresh" + Layout.fillWidth: true + onClicked: { + Settings.clearScreenOverride(root.screenName, "widgets"); + } + } + + // Scrollable widget sections + NScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 200 + + ColumnLayout { + width: parent.width + spacing: Style.marginL + + NText { + text: I18n.tr("panels.bar.widgets-desc") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + // Left Section + NSectionEditor { + sectionName: "Left" + sectionId: "left" + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml") + widgetRegistry: BarWidgetRegistry + widgetModel: root.effectiveWidgets.left + availableWidgets: availableWidgetsModel + onAddWidget: (widgetId, section) => root._addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => root._removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => root._reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => root._updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => root._moveWidgetBetweenSections(fromSection, index, toSection) + onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest) + } + + // Center Section + NSectionEditor { + sectionName: "Center" + sectionId: "center" + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml") + widgetRegistry: BarWidgetRegistry + widgetModel: root.effectiveWidgets.center + availableWidgets: availableWidgetsModel + onAddWidget: (widgetId, section) => root._addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => root._removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => root._reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => root._updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => root._moveWidgetBetweenSections(fromSection, index, toSection) + onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest) + } + + // Right Section + NSectionEditor { + sectionName: "Right" + sectionId: "right" + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml") + widgetRegistry: BarWidgetRegistry + widgetModel: root.effectiveWidgets.right + availableWidgets: availableWidgetsModel + onAddWidget: (widgetId, section) => root._addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => root._removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => root._reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => root._updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => root._moveWidgetBetweenSections(fromSection, index, toSection) + onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest) + } + } + } + + // Close button + RowLayout { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + + Item { + Layout.fillWidth: true + } + + NButton { + text: I18n.tr("common.close") + onClicked: root.close() + } + } + } + } + + // Plugin settings dialog + NPluginSettingsPopup { + id: pluginSettingsDialog + parent: Overlay.overlay + showToastOnSave: false + } +} diff --git a/Modules/Panels/Settings/SettingsPanel.qml b/Modules/Panels/Settings/SettingsPanel.qml index db52791d1..3fb586adc 100644 --- a/Modules/Panels/Settings/SettingsPanel.qml +++ b/Modules/Panels/Settings/SettingsPanel.qml @@ -19,7 +19,7 @@ SmartPanel { readonly property bool attachToBar: settingsPanelMode === "attached" readonly property string barDensity: Settings.data.bar.density - readonly property string barPosition: Settings.data.bar.position + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) readonly property bool barFloating: Settings.data.bar.floating readonly property real barMarginH: barFloating ? Math.ceil(Settings.data.bar.marginHorizontal) : 0 readonly property real barMarginV: barFloating ? Math.ceil(Settings.data.bar.marginVertical) : 0 diff --git a/Modules/Panels/Settings/Tabs/Bar/BarTab.qml b/Modules/Panels/Settings/Tabs/Bar/BarTab.qml index 647c08c93..bb97c5305 100644 --- a/Modules/Panels/Settings/Tabs/Bar/BarTab.qml +++ b/Modules/Panels/Settings/Tabs/Bar/BarTab.qml @@ -24,7 +24,7 @@ ColumnLayout { }); } - // Signal functions for widgets sub-tab + // Signal functions for widgets sub-tab (global widgets only) function _addWidgetToSection(widgetId, section) { var newWidget = { "id": widgetId @@ -43,10 +43,11 @@ ColumnLayout { } function _removeWidgetFromSection(section, index) { - if (index >= 0 && index < Settings.data.bar.widgets[section].length) { - var newArray = Settings.data.bar.widgets[section].slice(); + var widgets = Settings.data.bar.widgets; + if (index >= 0 && index < widgets[section].length) { + var newArray = widgets[section].slice(); var removedWidgets = newArray.splice(index, 1); - Settings.data.bar.widgets[section] = newArray; + widgets[section] = newArray; if (removedWidgets[0].id === "ControlCenter" && BarService.lookupWidget("ControlCenter") === undefined) { ToastService.showWarning(I18n.tr("toast.missing-control-center.label"), I18n.tr("toast.missing-control-center.description"), 12000); @@ -55,12 +56,13 @@ ColumnLayout { } function _reorderWidgetInSection(section, fromIndex, toIndex) { - if (fromIndex >= 0 && fromIndex < Settings.data.bar.widgets[section].length && toIndex >= 0 && toIndex < Settings.data.bar.widgets[section].length) { - var newArray = Settings.data.bar.widgets[section].slice(); + var widgets = Settings.data.bar.widgets; + if (fromIndex >= 0 && fromIndex < widgets[section].length && toIndex >= 0 && toIndex < widgets[section].length) { + var newArray = widgets[section].slice(); var item = newArray[fromIndex]; newArray.splice(fromIndex, 1); newArray.splice(toIndex, 0, item); - Settings.data.bar.widgets[section] = newArray; + widgets[section] = newArray; } } @@ -69,14 +71,15 @@ ColumnLayout { } function _moveWidgetBetweenSections(fromSection, index, toSection) { - if (index >= 0 && index < Settings.data.bar.widgets[fromSection].length) { - var widget = Settings.data.bar.widgets[fromSection][index]; - var sourceArray = Settings.data.bar.widgets[fromSection].slice(); + var widgets = Settings.data.bar.widgets; + if (index >= 0 && index < widgets[fromSection].length) { + var widget = widgets[fromSection][index]; + var sourceArray = widgets[fromSection].slice(); sourceArray.splice(index, 1); - Settings.data.bar.widgets[fromSection] = sourceArray; - var targetArray = Settings.data.bar.widgets[toSection].slice(); + widgets[fromSection] = sourceArray; + var targetArray = widgets[toSection].slice(); targetArray.push(widget); - Settings.data.bar.widgets[toSection] = targetArray; + widgets[toSection] = targetArray; } } @@ -187,7 +190,7 @@ ColumnLayout { Item { Layout.fillWidth: true - Layout.preferredHeight: Style.marginL + Layout.preferredHeight: Style.marginS } NTabView { diff --git a/Modules/Panels/Settings/Tabs/Bar/MonitorsSubTab.qml b/Modules/Panels/Settings/Tabs/Bar/MonitorsSubTab.qml index 5727c8043..37a12021b 100644 --- a/Modules/Panels/Settings/Tabs/Bar/MonitorsSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Bar/MonitorsSubTab.qml @@ -2,8 +2,10 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell +import "../../Bar" as BarSettings import qs.Commons import qs.Services.Compositor +import qs.Services.UI import qs.Widgets ColumnLayout { @@ -15,33 +17,208 @@ ColumnLayout { property var removeMonitor NText { - text: I18n.tr("panels.bar.monitors-desc") + text: I18n.tr("panels.bar.monitors-desc-new") wrapMode: Text.WordWrap Layout.fillWidth: true } + // Monitor cards Repeater { model: Quickshell.screens || [] - delegate: NCheckbox { + delegate: NBox { + id: monitorCard Layout.fillWidth: true - label: modelData.name || "Unknown" - description: { - const compositorScale = CompositorService.getDisplayScale(modelData.name); - I18n.tr("system.monitor-description", { - "model": modelData.model, - "width": modelData.width * compositorScale, - "height": modelData.height * compositorScale, - "scale": compositorScale - }); + implicitHeight: cardContent.implicitHeight + Style.marginL * 2 + color: Color.mSurface + + required property var modelData + readonly property string screenName: modelData.name || "Unknown" + readonly property bool barEnabled: (Settings.data.bar.monitors || []).indexOf(screenName) !== -1 + readonly property bool hasOverride: Settings.hasScreenOverride(screenName) + + // Track if override is enabled (controls both visibility AND whether overrides are applied) + readonly property bool overrideEnabled: Settings.isScreenOverrideEnabled(screenName) + + // Get effective values for this screen + readonly property string effectivePosition: Settings.getBarPositionForScreen(screenName) + readonly property string effectiveDensity: Settings.getBarDensityForScreen(screenName) + + ColumnLayout { + id: cardContent + anchors.fill: parent + anchors.margins: Style.marginL + spacing: Style.marginM + + RowLayout { + Layout.fillWidth: true + + // Header: Monitor name and specs + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXXS + + NText { + Layout.fillWidth: true + text: monitorCard.screenName + pointSize: Style.fontSizeM + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: { + const compositorScale = CompositorService.getDisplayScale(monitorCard.screenName); + return I18n.tr("system.monitor-description", { + "model": monitorCard.modelData.model || I18n.tr("common.unknown"), + "width": Math.round(monitorCard.modelData.width * compositorScale), + "height": Math.round(monitorCard.modelData.height * compositorScale), + "scale": compositorScale + }); + } + pointSize: Style.fontSizeS + color: Color.mOnSurfaceVariant + } + } + + // Enable bar toggle + NToggle { + Layout.fillWidth: true + checked: monitorCard.barEnabled + onToggled: checked => { + if (checked) { + Settings.data.bar.monitors = root.addMonitor(Settings.data.bar.monitors, monitorCard.screenName); + } else { + Settings.data.bar.monitors = root.removeMonitor(Settings.data.bar.monitors, monitorCard.screenName); + } + } + } + } + + NDivider { + Layout.fillWidth: true + visible: Settings.data.bar.monitors.includes(monitorCard.screenName) + } + + // Override section (only visible when bar is enabled) + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + visible: monitorCard.barEnabled + + // Override toggle + NToggle { + Layout.fillWidth: true + label: I18n.tr("panels.bar.monitor-override-settings") + description: I18n.tr("panels.bar.monitor-override-settings-description") + checked: monitorCard.overrideEnabled + onToggled: checked => { + Settings.setScreenOverride(monitorCard.screenName, "enabled", checked); + } + } + + // Override controls (only visible when override toggle is on) + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + visible: monitorCard.overrideEnabled + + // Position override + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-position-label") + description: I18n.tr("panels.bar.appearance-position-description") + model: [ + { + "key": "top", + "name": I18n.tr("positions.top") + }, + { + "key": "bottom", + "name": I18n.tr("positions.bottom") + }, + { + "key": "left", + "name": I18n.tr("positions.left") + }, + { + "key": "right", + "name": I18n.tr("positions.right") + } + ] + currentKey: monitorCard.effectivePosition + onSelected: key => Settings.setScreenOverride(monitorCard.screenName, "position", key) + } + } + + // Density override + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NComboBox { + Layout.fillWidth: true + label: I18n.tr("panels.bar.appearance-density-label") + description: I18n.tr("panels.bar.appearance-density-description") + model: [ + { + "key": "mini", + "name": I18n.tr("options.bar.density-mini") + }, + { + "key": "compact", + "name": I18n.tr("options.bar.density-compact") + }, + { + "key": "default", + "name": I18n.tr("options.bar.density-default") + }, + { + "key": "comfortable", + "name": I18n.tr("options.bar.density-comfortable") + }, + { + "key": "spacious", + "name": I18n.tr("options.bar.density-spacious") + } + ] + currentKey: monitorCard.effectiveDensity + onSelected: key => Settings.setScreenOverride(monitorCard.screenName, "density", key) + } + } + + // Widgets configuration button and Reset all + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NButton { + Layout.fillWidth: true + text: I18n.tr("panels.bar.monitor-configure-widgets") + icon: "layout-grid" + onClicked: widgetDialog.open() + } + + NButton { + Layout.fillWidth: true + text: I18n.tr("panels.bar.monitor-reset-all") + icon: "restore" + onClicked: Settings.clearScreenOverride(monitorCard.screenName) + } + } + } + } + } + + // Widget configuration dialog + BarSettings.MonitorWidgetsDialog { + id: widgetDialog + screenName: monitorCard.screenName + parent: Overlay.overlay } - checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1 - onToggled: checked => { - if (checked) { - Settings.data.bar.monitors = root.addMonitor(Settings.data.bar.monitors, modelData.name); - } else { - Settings.data.bar.monitors = root.removeMonitor(Settings.data.bar.monitors, modelData.name); - } - } } } } diff --git a/Modules/Panels/Settings/Tabs/Bar/WidgetsSubTab.qml b/Modules/Panels/Settings/Tabs/Bar/WidgetsSubTab.qml index 4a1157f02..4f97a954a 100644 --- a/Modules/Panels/Settings/Tabs/Bar/WidgetsSubTab.qml +++ b/Modules/Panels/Settings/Tabs/Bar/WidgetsSubTab.qml @@ -25,6 +25,7 @@ ColumnLayout { wrapMode: Text.WordWrap Layout.fillWidth: true } + // Left Section NSectionEditor { sectionName: "Left" diff --git a/Modules/Panels/Tray/TrayDrawerPanel.qml b/Modules/Panels/Tray/TrayDrawerPanel.qml index 043cca32d..c651637f4 100644 --- a/Modules/Panels/Tray/TrayDrawerPanel.qml +++ b/Modules/Panels/Tray/TrayDrawerPanel.qml @@ -242,7 +242,7 @@ SmartPanel { if (modelData.hasMenu && modelData.menu && panelContent.popupMenuWindow && panelContent.trayMenu && panelContent.trayMenu.item) { panelContent.popupMenuWindow.open(); - const barPosition = Settings.data.bar.position; + const barPosition = Settings.getBarPositionForScreen(root.screen?.name); let menuX, menuY; if (barPosition === "left") { @@ -275,7 +275,7 @@ SmartPanel { if (panelContent.popupMenuWindow) { panelContent.popupMenuWindow.close(); } - TooltipService.show(trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection()); + TooltipService.show(trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection(root.screen?.name)); } onExited: TooltipService.hide() } diff --git a/Modules/Panels/Wallpaper/WallpaperPanel.qml b/Modules/Panels/Wallpaper/WallpaperPanel.qml index cdd10a467..bf4cbaa87 100644 --- a/Modules/Panels/Wallpaper/WallpaperPanel.qml +++ b/Modules/Panels/Wallpaper/WallpaperPanel.qml @@ -17,12 +17,13 @@ SmartPanel { preferredHeightRatio: 0.45 // Positioning + readonly property string screenBarPosition: Settings.getBarPositionForScreen(screen?.name) readonly property string panelPosition: { if (Settings.data.wallpaper.panelPosition === "follow_bar") { - if (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") { - return `center_${Settings.data.bar.position}`; + if (screenBarPosition === "left" || screenBarPosition === "right") { + return `center_${screenBarPosition}`; } else { - return `${Settings.data.bar.position}_center`; + return `${screenBarPosition}_center`; } } else { return Settings.data.wallpaper.panelPosition; diff --git a/Modules/Toast/ToastScreen.qml b/Modules/Toast/ToastScreen.qml index 8c085017e..f52da250d 100644 --- a/Modules/Toast/ToastScreen.qml +++ b/Modules/Toast/ToastScreen.qml @@ -143,7 +143,7 @@ Item { readonly property bool isRight: location.endsWith("_right") readonly property bool isCentered: location === "top" || location === "bottom" - readonly property string barPos: Settings.data.bar.position + readonly property string barPos: Settings.getBarPositionForScreen(panel.screen?.name) readonly property bool isFloating: Settings.data.bar.floating // Calculate bar offsets for each edge separately diff --git a/Services/UI/BarService.qml b/Services/UI/BarService.qml index 568a9761b..aa7fef738 100644 --- a/Services/UI/BarService.qml +++ b/Services/UI/BarService.qml @@ -256,8 +256,9 @@ Singleton { return false; } - function getTooltipDirection() { - switch (Settings.data.bar.position) { + function getTooltipDirection(screenName) { + const position = Settings.getBarPositionForScreen(screenName); + switch (position) { case "right": return "left"; case "left": diff --git a/Widgets/NPopupContextMenu.qml b/Widgets/NPopupContextMenu.qml index dd3915ada..6fe0ec47e 100644 --- a/Widgets/NPopupContextMenu.qml +++ b/Widgets/NPopupContextMenu.qml @@ -21,7 +21,7 @@ PopupWindow { property real minWidth: 120 property real calculatedWidth: 180 - readonly property string barPosition: Settings.data.bar.position + readonly property string barPosition: Settings.getBarPositionForScreen(screen?.name) signal triggered(string action, var item) diff --git a/Widgets/NToggle.qml b/Widgets/NToggle.qml index 0d51ed304..3884cc27c 100644 --- a/Widgets/NToggle.qml +++ b/Widgets/NToggle.qml @@ -30,6 +30,7 @@ RowLayout { }) : "" NLabel { + Layout.fillWidth: true label: root.label description: root.description visible: root.label !== "" || root.description !== ""