diff --git a/Commons/Settings.qml b/Commons/Settings.qml index f0ccc1b..b01dfdf 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -30,6 +30,9 @@ Singleton { property bool isLoaded: false + // Signal emitted when settings are loaded after startupcale changes + signal settingsLoaded + // Function to validate monitor configurations function validateMonitorConfigurations() { var availableScreenNames = [] @@ -94,6 +97,9 @@ Singleton { Logger.log("Settings", "Settings loaded successfully") isLoaded = true + // Emit the signal + root.settingsLoaded() + Qt.callLater(function () { // Some stuff like settings validation should just be executed once on startup and not on every reload validateMonitorConfigurations() diff --git a/Modules/Background/ScreenCorners.qml b/Modules/Background/ScreenCorners.qml index da5481c..c18a888 100644 --- a/Modules/Background/ScreenCorners.qml +++ b/Modules/Background/ScreenCorners.qml @@ -16,7 +16,7 @@ Loader { id: root required property ShellScreen modelData - readonly property real scaling: ScalingService.scale(screen) + property real scaling: ScalingService.getScreenScale(screen) screen: modelData property color cornerColor: Qt.rgba(Color.mSurface.r, Color.mSurface.g, Color.mSurface.b, @@ -24,6 +24,15 @@ Loader { property real cornerRadius: 20 * scaling property real cornerSize: 20 * scaling + Connections { + target: ScalingService + function onScaleChanged(screenName, scale) { + if (screenName === screen.name) { + scaling = scale + } + } + } + color: Color.transparent WlrLayershell.exclusionMode: ExclusionMode.Ignore diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index ac8364b..59e6655 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -16,7 +16,16 @@ Variants { id: root required property ShellScreen modelData - readonly property real scaling: modelData ? ScalingService.scale(modelData) : 1.0 + property real scaling: ScalingService.getScreenScale(modelData) + + Connections { + target: ScalingService + function onScaleChanged(screenName, scale) { + if ((modelData !== null) && (screenName === modelData.name)) { + scaling = scale + } + } + } active: Settings.isLoaded && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false @@ -67,6 +76,7 @@ Variants { widgetName: modelData widgetProps: { "screen": root.modelData || null, + "scaling": ScalingService.getScreenScale(screen), "barSection": parent.objectName, "sectionWidgetIndex": index, "sectionWidgetsCount": Settings.data.bar.widgets.left.length @@ -90,9 +100,11 @@ Variants { Repeater { model: Settings.data.bar.widgets.center delegate: NWidgetLoader { + widgetName: modelData widgetProps: { "screen": root.modelData || null, + "scaling": ScalingService.getScreenScale(screen), "barSection": parent.objectName, "sectionWidgetIndex": index, "sectionWidgetsCount": Settings.data.bar.widgets.center.length @@ -120,6 +132,7 @@ Variants { widgetName: modelData widgetProps: { "screen": root.modelData || null, + "scaling": ScalingService.getScreenScale(screen), "barSection": parent.objectName, "sectionWidgetIndex": index, "sectionWidgetsCount": Settings.data.bar.widgets.right.length diff --git a/Modules/Bar/Extras/TrayMenu.qml b/Modules/Bar/Extras/TrayMenu.qml index 0d92cfc..e274d3d 100644 --- a/Modules/Bar/Extras/TrayMenu.qml +++ b/Modules/Bar/Extras/TrayMenu.qml @@ -15,7 +15,7 @@ PopupWindow { property bool isSubMenu: false property bool isHovered: rootMouseArea.containsMouse property ShellScreen screen - property real scaling: screen ? ScalingService.scale(screen) : 1.0 + property real scaling: ScalingService.getScreenScale(screen) readonly property int menuWidth: 180 diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index c9f98ca..c7387d1 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -11,7 +11,7 @@ Row { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 readonly property real minWidth: 160 readonly property real maxWidth: 400 diff --git a/Modules/Bar/Widgets/ArchUpdater.qml b/Modules/Bar/Widgets/ArchUpdater.qml index 2e5b4f7..3fb7c85 100644 --- a/Modules/Bar/Widgets/ArchUpdater.qml +++ b/Modules/Bar/Widgets/ArchUpdater.qml @@ -10,7 +10,7 @@ NIconButton { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 sizeRatio: 0.8 colorBg: Color.mSurfaceVariant diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml index d505943..22d8602 100644 --- a/Modules/Bar/Widgets/Battery.qml +++ b/Modules/Bar/Widgets/Battery.qml @@ -10,7 +10,7 @@ Item { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 property string barSection: "" property int sectionWidgetIndex: 0 property int sectionWidgetsCount: 0 diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml index 7cb4186..4036743 100644 --- a/Modules/Bar/Widgets/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -11,7 +11,7 @@ NIconButton { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 visible: Settings.data.network.bluetoothEnabled sizeRatio: 0.8 diff --git a/Modules/Bar/Widgets/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml index f848d8d..4b6d91a 100644 --- a/Modules/Bar/Widgets/Brightness.qml +++ b/Modules/Bar/Widgets/Brightness.qml @@ -9,7 +9,7 @@ Item { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 property string barSection: "" property int sectionWidgetIndex: 0 property int sectionWidgetsCount: 0 diff --git a/Modules/Bar/Widgets/Clock.qml b/Modules/Bar/Widgets/Clock.qml index 349e47a..9da63f5 100644 --- a/Modules/Bar/Widgets/Clock.qml +++ b/Modules/Bar/Widgets/Clock.qml @@ -8,7 +8,7 @@ Rectangle { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 implicitWidth: clock.width + Style.marginM * 2 * scaling implicitHeight: Math.round(Style.capsuleHeight * scaling) diff --git a/Modules/Bar/Widgets/KeyboardLayout.qml b/Modules/Bar/Widgets/KeyboardLayout.qml index b452c72..24f0c26 100644 --- a/Modules/Bar/Widgets/KeyboardLayout.qml +++ b/Modules/Bar/Widgets/KeyboardLayout.qml @@ -10,7 +10,7 @@ Row { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 property string barSection: "" property int sectionWidgetIndex: 0 property int sectionWidgetsCount: 0 diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index b909a84..7d2ffb7 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -11,7 +11,7 @@ Row { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 readonly property real minWidth: 160 readonly property real maxWidth: 400 diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml index 6162ba0..b9761a3 100644 --- a/Modules/Bar/Widgets/Microphone.qml +++ b/Modules/Bar/Widgets/Microphone.qml @@ -11,7 +11,7 @@ Item { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 property string barSection: "" property int sectionWidgetIndex: 0 property int sectionWidgetsCount: 0 diff --git a/Modules/Bar/Widgets/NightLight.qml b/Modules/Bar/Widgets/NightLight.qml index f6eda83..342a424 100644 --- a/Modules/Bar/Widgets/NightLight.qml +++ b/Modules/Bar/Widgets/NightLight.qml @@ -11,7 +11,7 @@ NIconButton { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 sizeRatio: 0.8 diff --git a/Modules/Bar/Widgets/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml index ff0db05..222a0eb 100644 --- a/Modules/Bar/Widgets/NotificationHistory.qml +++ b/Modules/Bar/Widgets/NotificationHistory.qml @@ -11,7 +11,7 @@ NIconButton { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 sizeRatio: 0.8 icon: "notifications" diff --git a/Modules/Bar/Widgets/PowerProfile.qml b/Modules/Bar/Widgets/PowerProfile.qml index b22b52d..d29b180 100644 --- a/Modules/Bar/Widgets/PowerProfile.qml +++ b/Modules/Bar/Widgets/PowerProfile.qml @@ -10,7 +10,7 @@ NIconButton { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 property var powerProfiles: PowerProfiles readonly property bool hasPP: powerProfiles.hasPerformanceProfile diff --git a/Modules/Bar/Widgets/ScreenRecorderIndicator.qml b/Modules/Bar/Widgets/ScreenRecorderIndicator.qml index ce68391..58c1208 100644 --- a/Modules/Bar/Widgets/ScreenRecorderIndicator.qml +++ b/Modules/Bar/Widgets/ScreenRecorderIndicator.qml @@ -8,7 +8,7 @@ NIconButton { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 visible: ScreenRecorderService.isRecording icon: "videocam" diff --git a/Modules/Bar/Widgets/SidePanelToggle.qml b/Modules/Bar/Widgets/SidePanelToggle.qml index 7020209..f37ff12 100644 --- a/Modules/Bar/Widgets/SidePanelToggle.qml +++ b/Modules/Bar/Widgets/SidePanelToggle.qml @@ -7,7 +7,7 @@ NIconButton { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 icon: "widgets" tooltipText: "Open side panel" diff --git a/Modules/Bar/Widgets/SystemMonitor.qml b/Modules/Bar/Widgets/SystemMonitor.qml index c9514f7..ce16aa3 100644 --- a/Modules/Bar/Widgets/SystemMonitor.qml +++ b/Modules/Bar/Widgets/SystemMonitor.qml @@ -8,7 +8,7 @@ Row { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 anchors.verticalCenter: parent.verticalCenter spacing: Style.marginS * scaling diff --git a/Modules/Bar/Widgets/Taskbar.qml b/Modules/Bar/Widgets/Taskbar.qml index c11fd62..623d7e7 100644 --- a/Modules/Bar/Widgets/Taskbar.qml +++ b/Modules/Bar/Widgets/Taskbar.qml @@ -12,7 +12,7 @@ import qs.Widgets Rectangle { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 readonly property real itemSize: Style.baseWidgetSize * 0.8 * scaling diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index d960f45..1c5a6cf 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -14,7 +14,7 @@ Rectangle { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 readonly property real itemSize: 24 * scaling visible: SystemTray.items.values.length > 0 diff --git a/Modules/Bar/Widgets/Volume.qml b/Modules/Bar/Widgets/Volume.qml index 7a0f0c5..36e396b 100644 --- a/Modules/Bar/Widgets/Volume.qml +++ b/Modules/Bar/Widgets/Volume.qml @@ -11,7 +11,7 @@ Item { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 property string barSection: "" property int sectionWidgetIndex: 0 property int sectionWidgetsCount: 0 diff --git a/Modules/Bar/Widgets/WiFi.qml b/Modules/Bar/Widgets/WiFi.qml index 2362a82..0917f36 100644 --- a/Modules/Bar/Widgets/WiFi.qml +++ b/Modules/Bar/Widgets/WiFi.qml @@ -11,7 +11,7 @@ NIconButton { id: root property ShellScreen screen - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 visible: Settings.data.network.wifiEnabled diff --git a/Modules/Bar/Widgets/Workspace.qml b/Modules/Bar/Widgets/Workspace.qml index a9bef1e..93ac300 100644 --- a/Modules/Bar/Widgets/Workspace.qml +++ b/Modules/Bar/Widgets/Workspace.qml @@ -12,7 +12,7 @@ Item { id: root property ShellScreen screen: null - property real scaling: ScalingService.scale(screen) + property real scaling: 1.0 property bool isDestroying: false property bool hovered: false diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index eba466c..6003c2f 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -15,7 +15,15 @@ Variants { delegate: Loader { required property ShellScreen modelData - readonly property real scaling: ScalingService.scale(modelData) + property real scaling: ScalingService.getScreenScale(modelData) + Connections { + target: ScalingService + function onScaleChanged(screenName, scale) { + if (screenName === modelData.name) { + scaling = scale + } + } + } active: Settings.isLoaded && modelData ? Settings.data.dock.monitors.includes(modelData.name) : false diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index 9421b15..9d5291f 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -16,7 +16,7 @@ Variants { id: root required property ShellScreen modelData - readonly property real scaling: ScalingService.scale(modelData) + readonly property real scaling: ScalingService.getScreenScale(modelData) // Access the notification model from the service property ListModel notificationModel: NotificationService.notificationModel diff --git a/Modules/SettingsPanel/Tabs/DisplayTab.qml b/Modules/SettingsPanel/Tabs/DisplayTab.qml index 5cb95b9..9a5c4e3 100644 --- a/Modules/SettingsPanel/Tabs/DisplayTab.qml +++ b/Modules/SettingsPanel/Tabs/DisplayTab.qml @@ -51,6 +51,16 @@ ColumnLayout { border.width: Math.max(1, Style.borderS * scaling) implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * scaling + property real localScaling: ScalingService.getScreenScale(modelData) + Connections { + target: ScalingService + function onScaleChanged(screenName, scale) { + if (screenName === modelData.name) { + localScaling = scale + } + } + } + ColumnLayout { id: contentCol anchors.fill: parent @@ -148,7 +158,7 @@ ColumnLayout { } NText { - text: `${Math.round(ScalingService.getMonitorScale(modelData.name) * 100)}%` + text: `${Math.round(localScaling * 100)}%` Layout.alignment: Qt.AlignVCenter Layout.minimumWidth: 50 * scaling horizontalAlignment: Text.AlignRight @@ -164,8 +174,8 @@ ColumnLayout { from: 0.7 to: 1.8 stepSize: 0.01 - value: ScalingService.getMonitorScale(modelData.name) - onPressedChanged: ScalingService.setMonitorScale(modelData.name, value) + value: localScaling + onPressedChanged: ScalingService.setScreenScale(modelData, value) Layout.fillWidth: true Layout.minimumWidth: 150 * scaling } @@ -173,7 +183,7 @@ ColumnLayout { NIconButton { icon: "refresh" tooltipText: "Reset scaling" - onClicked: ScalingService.setMonitorScale(modelData.name, 1.0) + onClicked: ScalingService.setScreenScale(modelData, 1.0) } } } diff --git a/Modules/Toast/ToastOverlay.qml b/Modules/Toast/ToastOverlay.qml index 10b030b..db880b5 100644 --- a/Modules/Toast/ToastOverlay.qml +++ b/Modules/Toast/ToastOverlay.qml @@ -11,7 +11,15 @@ Variants { delegate: Loader { required property ShellScreen modelData - readonly property real scaling: ScalingService.scale(modelData) + property real scaling: ScalingService.getScreenScale(modelData) + Connections { + target: ScalingService + function onScaleChanged(screenName, scale) { + if (screenName === modelData.name) { + scaling = scale + } + } + } // Only show on screens that have notifications enabled active: Settings.isLoaded && modelData ? (Settings.data.notifications.monitors.includes(modelData.name) diff --git a/Services/ScalingService.qml b/Services/ScalingService.qml index 8aa32bb..6b204f7 100644 --- a/Services/ScalingService.qml +++ b/Services/ScalingService.qml @@ -1,17 +1,43 @@ pragma Singleton +import QtQuick import Quickshell import qs.Commons Singleton { id: root + // Cache for current scales - updated via signals + property var currentScales: ({}) + + // Signal emitted when scale changes + signal scaleChanged(string screenName, real scale) + + Component.onCompleted: { + Logger.log("Scaling", "Service started") + } + + Connections { + target: Settings + function onSettingsLoaded() { + // Initialize cache from Settings once they are loaded on startup + var monitors = Settings.data.ui.monitorsScaling || [] + for (var i = 0; i < monitors.length; i++) { + if (monitors[i].name && monitors[i].scale !== undefined) { + currentScales[monitors[i].name] = monitors[i].scale + root.scaleChanged(monitors[i].name, monitors[i].scale) + Logger.log("Scaling", "Caching scaling for", monitors[i].name, ":", monitors[i].scale) + } + } + } + } + // ------------------------------------------- // Manual scaling via Settings - function scale(aScreen) { + function getScreenScale(aScreen) { try { if (aScreen !== undefined && aScreen.name !== undefined) { - return getMonitorScale(aScreen.name) + return getScreenScaleByName(aScreen.name) } } catch (e) { @@ -21,15 +47,12 @@ Singleton { } // ------------------------------------------- - function getMonitorScale(aScreenName) { + // Get scale from cache for better performance + function getScreenScaleByName(aScreenName) { try { - var monitors = Settings.data.ui.monitorsScaling - if (monitors !== undefined) { - for (var i = 0; i < monitors.length; i++) { - if (monitors[i].name !== undefined && monitors[i].name === aScreenName) { - return monitors[i].scale - } - } + var scale = currentScales[aScreenName] + if ((scale !== undefined) && (scale != null)) { + return scale } } catch (e) { @@ -39,34 +62,69 @@ Singleton { } // ------------------------------------------- - function setMonitorScale(aScreenName, scale) { + function setScreenScale(aScreen, scale) { try { - var monitors = Settings.data.ui.monitorsScaling - if (monitors !== undefined) { - for (var i = 0; i < monitors.length; i++) { - if (monitors[i].name !== undefined && monitors[i].name === aScreenName) { - monitors[i].scale = scale - return - } - } + if (aScreen !== undefined && aScreen.name !== undefined) { + return setScreenScaleByName(aScreen.name, scale) } - monitors.push({ - "name": aScreenName, - "scale": scale - }) } catch (e) { //Logger.warn(e) } } + // ------------------------------------------- + function setScreenScaleByName(aScreenName, scale) { + try { + // Check if scale actually changed + var oldScale = currentScales[aScreenName] || 1.0 + if (oldScale === scale) { + return + // No change needed + } + + // Update cache directly + currentScales[aScreenName] = scale + + // Update Settings with immutable update for proper persistence + var monitors = Settings.data.ui.monitorsScaling || [] + var found = false + + var newMonitors = monitors.map(function (monitor) { + if (monitor.name === aScreenName) { + found = true + return { + "name": aScreenName, + "scale": scale + } + } + return monitor + }) + + if (!found) { + newMonitors.push({ + "name": aScreenName, + "scale": scale + }) + } + + // Use slice() to ensure Settings detects the change + Settings.data.ui.monitorsScaling = newMonitors.slice() + + // Emit signal for components to react + root.scaleChanged(aScreenName, scale) + + Logger.log("Scaling", "Scale changed for", aScreenName, "to", scale) + } catch (e) { + Logger.warn("Scaling", "Error setting scale:", e) + } + } + // ------------------------------------------- // Dynamic scaling based on resolution - // Design reference resolution (for scale = 1.0) readonly property int designScreenWidth: 2560 readonly property int designScreenHeight: 1440 - function dynamicScale(aScreen) { if (aScreen != null) { var ratioW = aScreen.width / designScreenWidth diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml index e65a1b0..bc61176 100644 --- a/Widgets/NPanel.qml +++ b/Widgets/NPanel.qml @@ -11,7 +11,16 @@ Loader { asynchronous: true property ShellScreen screen - readonly property real scaling: screen ? ScalingService.scale(screen) : 1.0 + property real scaling: ScalingService.getScreenScale(screen) + + Connections { + target: ScalingService + function onScaleChanged(screenName, scale) { + if ((screen !== null) && (screenName === screen.name)) { + scaling = scale + } + } + } property Component panelContent: null property int panelWidth: 1500 diff --git a/Widgets/NWidgetLoader.qml b/Widgets/NWidgetLoader.qml index 611e0cb..6e8c64b 100644 --- a/Widgets/NWidgetLoader.qml +++ b/Widgets/NWidgetLoader.qml @@ -10,6 +10,15 @@ Item { property var widgetProps: ({}) property bool enabled: true + Connections { + target: ScalingService + function onScaleChanged(screenName, scale) { + if ((loader.item.screen !== null) && (screenName === loader.item.screen.name)) { + loader.item['scaling'] = scale + } + } + } + // Don't reserve space unless the loaded widget is really visible implicitWidth: loader.item ? loader.item.visible ? loader.item.implicitWidth : 0 : 0 implicitHeight: loader.item ? loader.item.visible ? loader.item.implicitHeight : 0 : 0