ScalingService: 1st pass of the refactoring via signals instead of nested bindings for better efficienty and compatibility with old versions of Qt

This commit is contained in:
LemmyCook 2025-09-01 13:52:12 -04:00
parent 934c8c61b3
commit 210bbac583
31 changed files with 186 additions and 56 deletions

View file

@ -30,6 +30,9 @@ Singleton {
property bool isLoaded: false property bool isLoaded: false
// Signal emitted when settings are loaded after startupcale changes
signal settingsLoaded
// Function to validate monitor configurations // Function to validate monitor configurations
function validateMonitorConfigurations() { function validateMonitorConfigurations() {
var availableScreenNames = [] var availableScreenNames = []
@ -94,6 +97,9 @@ Singleton {
Logger.log("Settings", "Settings loaded successfully") Logger.log("Settings", "Settings loaded successfully")
isLoaded = true isLoaded = true
// Emit the signal
root.settingsLoaded()
Qt.callLater(function () { Qt.callLater(function () {
// Some stuff like settings validation should just be executed once on startup and not on every reload // Some stuff like settings validation should just be executed once on startup and not on every reload
validateMonitorConfigurations() validateMonitorConfigurations()

View file

@ -16,7 +16,7 @@ Loader {
id: root id: root
required property ShellScreen modelData required property ShellScreen modelData
readonly property real scaling: ScalingService.scale(screen) property real scaling: ScalingService.getScreenScale(screen)
screen: modelData screen: modelData
property color cornerColor: Qt.rgba(Color.mSurface.r, Color.mSurface.g, Color.mSurface.b, 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 cornerRadius: 20 * scaling
property real cornerSize: 20 * scaling property real cornerSize: 20 * scaling
Connections {
target: ScalingService
function onScaleChanged(screenName, scale) {
if (screenName === screen.name) {
scaling = scale
}
}
}
color: Color.transparent color: Color.transparent
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore

View file

@ -16,7 +16,16 @@ Variants {
id: root id: root
required property ShellScreen modelData 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) active: Settings.isLoaded && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name)
|| (Settings.data.bar.monitors.length === 0)) : false || (Settings.data.bar.monitors.length === 0)) : false
@ -67,6 +76,7 @@ Variants {
widgetName: modelData widgetName: modelData
widgetProps: { widgetProps: {
"screen": root.modelData || null, "screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"barSection": parent.objectName, "barSection": parent.objectName,
"sectionWidgetIndex": index, "sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.left.length "sectionWidgetsCount": Settings.data.bar.widgets.left.length
@ -90,9 +100,11 @@ Variants {
Repeater { Repeater {
model: Settings.data.bar.widgets.center model: Settings.data.bar.widgets.center
delegate: NWidgetLoader { delegate: NWidgetLoader {
widgetName: modelData widgetName: modelData
widgetProps: { widgetProps: {
"screen": root.modelData || null, "screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"barSection": parent.objectName, "barSection": parent.objectName,
"sectionWidgetIndex": index, "sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.center.length "sectionWidgetsCount": Settings.data.bar.widgets.center.length
@ -120,6 +132,7 @@ Variants {
widgetName: modelData widgetName: modelData
widgetProps: { widgetProps: {
"screen": root.modelData || null, "screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen),
"barSection": parent.objectName, "barSection": parent.objectName,
"sectionWidgetIndex": index, "sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.right.length "sectionWidgetsCount": Settings.data.bar.widgets.right.length

View file

@ -15,7 +15,7 @@ PopupWindow {
property bool isSubMenu: false property bool isSubMenu: false
property bool isHovered: rootMouseArea.containsMouse property bool isHovered: rootMouseArea.containsMouse
property ShellScreen screen property ShellScreen screen
property real scaling: screen ? ScalingService.scale(screen) : 1.0 property real scaling: ScalingService.getScreenScale(screen)
readonly property int menuWidth: 180 readonly property int menuWidth: 180

View file

@ -11,7 +11,7 @@ Row {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
readonly property real minWidth: 160 readonly property real minWidth: 160
readonly property real maxWidth: 400 readonly property real maxWidth: 400

View file

@ -10,7 +10,7 @@ NIconButton {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
sizeRatio: 0.8 sizeRatio: 0.8
colorBg: Color.mSurfaceVariant colorBg: Color.mSurfaceVariant

View file

@ -10,7 +10,7 @@ Item {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
property string barSection: "" property string barSection: ""
property int sectionWidgetIndex: 0 property int sectionWidgetIndex: 0
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0

View file

@ -11,7 +11,7 @@ NIconButton {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
visible: Settings.data.network.bluetoothEnabled visible: Settings.data.network.bluetoothEnabled
sizeRatio: 0.8 sizeRatio: 0.8

View file

@ -9,7 +9,7 @@ Item {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
property string barSection: "" property string barSection: ""
property int sectionWidgetIndex: 0 property int sectionWidgetIndex: 0
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0

View file

@ -8,7 +8,7 @@ Rectangle {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
implicitWidth: clock.width + Style.marginM * 2 * scaling implicitWidth: clock.width + Style.marginM * 2 * scaling
implicitHeight: Math.round(Style.capsuleHeight * scaling) implicitHeight: Math.round(Style.capsuleHeight * scaling)

View file

@ -10,7 +10,7 @@ Row {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
property string barSection: "" property string barSection: ""
property int sectionWidgetIndex: 0 property int sectionWidgetIndex: 0
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0

View file

@ -11,7 +11,7 @@ Row {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
readonly property real minWidth: 160 readonly property real minWidth: 160
readonly property real maxWidth: 400 readonly property real maxWidth: 400

View file

@ -11,7 +11,7 @@ Item {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
property string barSection: "" property string barSection: ""
property int sectionWidgetIndex: 0 property int sectionWidgetIndex: 0
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0

View file

@ -11,7 +11,7 @@ NIconButton {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
sizeRatio: 0.8 sizeRatio: 0.8

View file

@ -11,7 +11,7 @@ NIconButton {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
sizeRatio: 0.8 sizeRatio: 0.8
icon: "notifications" icon: "notifications"

View file

@ -10,7 +10,7 @@ NIconButton {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
property var powerProfiles: PowerProfiles property var powerProfiles: PowerProfiles
readonly property bool hasPP: powerProfiles.hasPerformanceProfile readonly property bool hasPP: powerProfiles.hasPerformanceProfile

View file

@ -8,7 +8,7 @@ NIconButton {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
visible: ScreenRecorderService.isRecording visible: ScreenRecorderService.isRecording
icon: "videocam" icon: "videocam"

View file

@ -7,7 +7,7 @@ NIconButton {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
icon: "widgets" icon: "widgets"
tooltipText: "Open side panel" tooltipText: "Open side panel"

View file

@ -8,7 +8,7 @@ Row {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Style.marginS * scaling spacing: Style.marginS * scaling

View file

@ -12,7 +12,7 @@ import qs.Widgets
Rectangle { Rectangle {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
readonly property real itemSize: Style.baseWidgetSize * 0.8 * scaling readonly property real itemSize: Style.baseWidgetSize * 0.8 * scaling

View file

@ -14,7 +14,7 @@ Rectangle {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
readonly property real itemSize: 24 * scaling readonly property real itemSize: 24 * scaling
visible: SystemTray.items.values.length > 0 visible: SystemTray.items.values.length > 0

View file

@ -11,7 +11,7 @@ Item {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
property string barSection: "" property string barSection: ""
property int sectionWidgetIndex: 0 property int sectionWidgetIndex: 0
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0

View file

@ -11,7 +11,7 @@ NIconButton {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
visible: Settings.data.network.wifiEnabled visible: Settings.data.network.wifiEnabled

View file

@ -12,7 +12,7 @@ Item {
id: root id: root
property ShellScreen screen: null property ShellScreen screen: null
property real scaling: ScalingService.scale(screen) property real scaling: 1.0
property bool isDestroying: false property bool isDestroying: false
property bool hovered: false property bool hovered: false

View file

@ -15,7 +15,15 @@ Variants {
delegate: Loader { delegate: Loader {
required property ShellScreen modelData 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 active: Settings.isLoaded && modelData ? Settings.data.dock.monitors.includes(modelData.name) : false

View file

@ -16,7 +16,7 @@ Variants {
id: root id: root
required property ShellScreen modelData 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 // Access the notification model from the service
property ListModel notificationModel: NotificationService.notificationModel property ListModel notificationModel: NotificationService.notificationModel

View file

@ -51,6 +51,16 @@ ColumnLayout {
border.width: Math.max(1, Style.borderS * scaling) border.width: Math.max(1, Style.borderS * scaling)
implicitHeight: contentCol.implicitHeight + Style.marginXL * 2 * 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 { ColumnLayout {
id: contentCol id: contentCol
anchors.fill: parent anchors.fill: parent
@ -148,7 +158,7 @@ ColumnLayout {
} }
NText { NText {
text: `${Math.round(ScalingService.getMonitorScale(modelData.name) * 100)}%` text: `${Math.round(localScaling * 100)}%`
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.minimumWidth: 50 * scaling Layout.minimumWidth: 50 * scaling
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
@ -164,8 +174,8 @@ ColumnLayout {
from: 0.7 from: 0.7
to: 1.8 to: 1.8
stepSize: 0.01 stepSize: 0.01
value: ScalingService.getMonitorScale(modelData.name) value: localScaling
onPressedChanged: ScalingService.setMonitorScale(modelData.name, value) onPressedChanged: ScalingService.setScreenScale(modelData, value)
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: 150 * scaling Layout.minimumWidth: 150 * scaling
} }
@ -173,7 +183,7 @@ ColumnLayout {
NIconButton { NIconButton {
icon: "refresh" icon: "refresh"
tooltipText: "Reset scaling" tooltipText: "Reset scaling"
onClicked: ScalingService.setMonitorScale(modelData.name, 1.0) onClicked: ScalingService.setScreenScale(modelData, 1.0)
} }
} }
} }

View file

@ -11,7 +11,15 @@ Variants {
delegate: Loader { delegate: Loader {
required property ShellScreen modelData 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 // Only show on screens that have notifications enabled
active: Settings.isLoaded && modelData ? (Settings.data.notifications.monitors.includes(modelData.name) active: Settings.isLoaded && modelData ? (Settings.data.notifications.monitors.includes(modelData.name)

View file

@ -1,17 +1,43 @@
pragma Singleton pragma Singleton
import QtQuick
import Quickshell import Quickshell
import qs.Commons import qs.Commons
Singleton { Singleton {
id: root 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 // Manual scaling via Settings
function scale(aScreen) { function getScreenScale(aScreen) {
try { try {
if (aScreen !== undefined && aScreen.name !== undefined) { if (aScreen !== undefined && aScreen.name !== undefined) {
return getMonitorScale(aScreen.name) return getScreenScaleByName(aScreen.name)
} }
} catch (e) { } catch (e) {
@ -21,15 +47,12 @@ Singleton {
} }
// ------------------------------------------- // -------------------------------------------
function getMonitorScale(aScreenName) { // Get scale from cache for better performance
function getScreenScaleByName(aScreenName) {
try { try {
var monitors = Settings.data.ui.monitorsScaling var scale = currentScales[aScreenName]
if (monitors !== undefined) { if ((scale !== undefined) && (scale != null)) {
for (var i = 0; i < monitors.length; i++) { return scale
if (monitors[i].name !== undefined && monitors[i].name === aScreenName) {
return monitors[i].scale
}
}
} }
} catch (e) { } catch (e) {
@ -39,34 +62,69 @@ Singleton {
} }
// ------------------------------------------- // -------------------------------------------
function setMonitorScale(aScreenName, scale) { function setScreenScale(aScreen, scale) {
try { try {
var monitors = Settings.data.ui.monitorsScaling if (aScreen !== undefined && aScreen.name !== undefined) {
if (monitors !== undefined) { return setScreenScaleByName(aScreen.name, scale)
for (var i = 0; i < monitors.length; i++) {
if (monitors[i].name !== undefined && monitors[i].name === aScreenName) {
monitors[i].scale = scale
return
}
}
} }
monitors.push({
"name": aScreenName,
"scale": scale
})
} catch (e) { } catch (e) {
//Logger.warn(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 // Dynamic scaling based on resolution
// Design reference resolution (for scale = 1.0) // Design reference resolution (for scale = 1.0)
readonly property int designScreenWidth: 2560 readonly property int designScreenWidth: 2560
readonly property int designScreenHeight: 1440 readonly property int designScreenHeight: 1440
function dynamicScale(aScreen) { function dynamicScale(aScreen) {
if (aScreen != null) { if (aScreen != null) {
var ratioW = aScreen.width / designScreenWidth var ratioW = aScreen.width / designScreenWidth

View file

@ -11,7 +11,16 @@ Loader {
asynchronous: true asynchronous: true
property ShellScreen screen 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 Component panelContent: null
property int panelWidth: 1500 property int panelWidth: 1500

View file

@ -10,6 +10,15 @@ Item {
property var widgetProps: ({}) property var widgetProps: ({})
property bool enabled: true 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 // Don't reserve space unless the loaded widget is really visible
implicitWidth: loader.item ? loader.item.visible ? loader.item.implicitWidth : 0 : 0 implicitWidth: loader.item ? loader.item.visible ? loader.item.implicitWidth : 0 : 0
implicitHeight: loader.item ? loader.item.visible ? loader.item.implicitHeight : 0 : 0 implicitHeight: loader.item ? loader.item.visible ? loader.item.implicitHeight : 0 : 0