From 9cb6613308f444c5d46cb1dddae332855bf61b2f Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 20 Dec 2025 14:55:09 +0100 Subject: [PATCH] NSettingsIndicator: add default setting indicator (#1080) N*Widgets: show NSettingsIndicator if settings are not default --- Assets/Translations/en.json | 4 + Commons/Settings.qml | 88 ++++++++ Modules/Panels/Settings/Tabs/AudioTab.qml | 21 +- Modules/Panels/Settings/Tabs/BarTab.qml | 41 ++-- .../Settings/Tabs/DesktopWidgetsTab.qml | 2 + Modules/Panels/Settings/Tabs/DockTab.qml | 144 ++++++------ Modules/Panels/Settings/Tabs/GeneralTab.qml | 115 +++++----- .../Panels/Settings/Tabs/NotificationsTab.qml | 183 +++++++-------- Modules/Panels/Settings/Tabs/OsdTab.qml | 24 +- .../Panels/Settings/Tabs/SystemMonitorTab.qml | 36 +++ .../Panels/Settings/Tabs/UserInterfaceTab.qml | 211 +++++++++--------- Modules/Panels/Settings/Tabs/WallpaperTab.qml | 14 +- Widgets/NComboBox.qml | 71 ++++++ Widgets/NLabel.qml | 34 ++- Widgets/NSearchableComboBox.qml | 49 ++++ Widgets/NSettingsIndicator.qml | 48 ++++ Widgets/NSpinBox.qml | 11 + Widgets/NTextInput.qml | 12 + Widgets/NToggle.qml | 12 + Widgets/NValueSlider.qml | 84 +++++-- 20 files changed, 793 insertions(+), 411 deletions(-) create mode 100644 Widgets/NSettingsIndicator.qml diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 89e71860d..719b0f739 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -882,6 +882,10 @@ "title": "Session Menu" }, "settings": { + "indicator": { + "default-value": "Default: {value}", + "system-default": "System Default" + }, "about": { "contributors": { "section": { diff --git a/Commons/Settings.qml b/Commons/Settings.qml index e8273058a..7d61f6772 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -151,6 +151,30 @@ Singleton { watchChanges: false } + // FileView to load default settings for comparison + FileView { + id: defaultSettingsFileView + path: Quickshell.shellDir + "/Assets/settings-default.json" + printErrors: false + watchChanges: false + } + + // Cached default settings object + property var _defaultSettings: null + + // Load default settings when file is loaded + Connections { + target: defaultSettingsFileView + function onLoaded() { + try { + root._defaultSettings = JSON.parse(defaultSettingsFileView.text()); + } catch (e) { + Logger.w("Settings", "Failed to parse default settings file: " + e); + root._defaultSettings = null; + } + } + } + JsonAdapter { id: adapter @@ -651,6 +675,70 @@ Singleton { return path; } + // ----------------------------------------------------- + // Get default value for a setting path (e.g., "general.scaleRatio" or "bar.position") + // Returns undefined if not found + function getDefaultValue(path) { + if (!root._defaultSettings) { + return undefined; + } + + var parts = path.split("."); + var current = root._defaultSettings; + + for (var i = 0; i < parts.length; i++) { + if (current === undefined || current === null) { + return undefined; + } + current = current[parts[i]]; + } + + return current; + } + + // ----------------------------------------------------- + // Compare current value with default value + // Returns true if values differ, false if they match or default is not found + function isValueChanged(path, currentValue) { + var defaultValue = getDefaultValue(path); + if (defaultValue === undefined) { + return false; // Can't compare if default not found + } + + // Deep comparison for objects and arrays + if (typeof currentValue === "object" && typeof defaultValue === "object") { + return JSON.stringify(currentValue) !== JSON.stringify(defaultValue); + } + + // Simple comparison for primitives + return currentValue !== defaultValue; + } + + // ----------------------------------------------------- + // Format default value for tooltip display + // Returns a human-readable string representation of the default value + function formatDefaultValueForTooltip(path) { + var defaultValue = getDefaultValue(path); + if (defaultValue === undefined) { + return ""; + } + + // Format based on type + if (typeof defaultValue === "boolean") { + return defaultValue ? "true" : "false"; + } else if (typeof defaultValue === "number") { + return defaultValue.toString(); + } else if (typeof defaultValue === "string") { + return defaultValue === "" ? "(empty)" : defaultValue; + } else if (Array.isArray(defaultValue)) { + return defaultValue.length === 0 ? "(empty)" : "[" + defaultValue.length + " items]"; + } else if (typeof defaultValue === "object") { + return "(object)"; + } + + return String(defaultValue); + } + // ----------------------------------------------------- // Public function to trigger immediate settings saving function saveImmediate() { diff --git a/Modules/Panels/Settings/Tabs/AudioTab.qml b/Modules/Panels/Settings/Tabs/AudioTab.qml index e05dd68d4..5879393c9 100644 --- a/Modules/Panels/Settings/Tabs/AudioTab.qml +++ b/Modules/Panels/Settings/Tabs/AudioTab.qml @@ -93,13 +93,10 @@ ColumnLayout { spacing: Style.marginXS Layout.fillWidth: true - NLabel { - label: I18n.tr("settings.audio.volumes.input-volume.label") - description: I18n.tr("settings.audio.volumes.input-volume.description") - } - NValueSlider { Layout.fillWidth: true + label: I18n.tr("settings.audio.volumes.input-volume.label") + description: I18n.tr("settings.audio.volumes.input-volume.description") from: 0 to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 value: AudioService.inputVolume @@ -136,6 +133,8 @@ ColumnLayout { value: Settings.data.audio.volumeStep stepSize: 1 suffix: "%" + isSettings: true + defaultValue: Settings.getDefaultValue("audio.volumeStep") onValueChanged: Settings.data.audio.volumeStep = value } } @@ -149,6 +148,8 @@ ColumnLayout { label: I18n.tr("settings.audio.volumes.volume-overdrive.label") description: I18n.tr("settings.audio.volumes.volume-overdrive.description") checked: Settings.data.audio.volumeOverdrive + isSettings: true + defaultValue: Settings.getDefaultValue("audio.volumeOverdrive") onToggled: checked => Settings.data.audio.volumeOverdrive = checked } } @@ -159,6 +160,8 @@ ColumnLayout { description: I18n.tr("settings.audio.external-mixer.description") placeholderText: I18n.tr("settings.audio.external-mixer.placeholder") text: Settings.data.audio.externalMixer + isSettings: true + defaultValue: Settings.getDefaultValue("audio.externalMixer") onTextChanged: Settings.data.audio.externalMixer = text } @@ -262,6 +265,8 @@ ColumnLayout { description: I18n.tr("settings.audio.media.primary-player.description") placeholderText: I18n.tr("settings.audio.media.primary-player.placeholder") text: Settings.data.audio.preferredPlayer + isSettings: true + defaultValue: Settings.getDefaultValue("audio.preferredPlayer") onTextChanged: { Settings.data.audio.preferredPlayer = text; MediaService.updateCurrentPlayer(); @@ -373,6 +378,8 @@ ColumnLayout { } ] currentKey: Settings.data.audio.visualizerType + isSettings: true + defaultValue: Settings.getDefaultValue("audio.visualizerType") onSelected: key => Settings.data.audio.visualizerType = key } @@ -390,6 +397,8 @@ ColumnLayout { } ] currentKey: Settings.data.audio.visualizerQuality + isSettings: true + defaultValue: Settings.getDefaultValue("audio.visualizerQuality") onSelected: key => Settings.data.audio.visualizerQuality = key } @@ -441,6 +450,8 @@ ColumnLayout { } ] currentKey: Settings.data.audio.cavaFrameRate + isSettings: true + defaultValue: Settings.getDefaultValue("audio.cavaFrameRate") onSelected: key => Settings.data.audio.cavaFrameRate = key } } diff --git a/Modules/Panels/Settings/Tabs/BarTab.qml b/Modules/Panels/Settings/Tabs/BarTab.qml index a34fb1bdc..eb2758b8e 100644 --- a/Modules/Panels/Settings/Tabs/BarTab.qml +++ b/Modules/Panels/Settings/Tabs/BarTab.qml @@ -53,6 +53,8 @@ ColumnLayout { } ] currentKey: Settings.data.bar.position + isSettings: true + defaultValue: Settings.getDefaultValue("bar.position") onSelected: key => Settings.data.bar.position = key } @@ -79,6 +81,8 @@ ColumnLayout { } ] currentKey: Settings.data.bar.density + isSettings: true + defaultValue: Settings.getDefaultValue("bar.density") onSelected: key => Settings.data.bar.density = key } @@ -87,6 +91,8 @@ ColumnLayout { label: I18n.tr("settings.bar.appearance.transparent.label") description: I18n.tr("settings.bar.appearance.transparent.description") checked: Settings.data.bar.transparent + isSettings: true + defaultValue: Settings.getDefaultValue("bar.transparent") onToggled: checked => Settings.data.bar.transparent = checked } @@ -95,6 +101,8 @@ ColumnLayout { label: I18n.tr("settings.bar.appearance.show-outline.label") description: I18n.tr("settings.bar.appearance.show-outline.description") checked: Settings.data.bar.showOutline + isSettings: true + defaultValue: Settings.getDefaultValue("bar.showOutline") onToggled: checked => Settings.data.bar.showOutline = checked } @@ -103,6 +111,8 @@ ColumnLayout { label: I18n.tr("settings.bar.appearance.show-capsule.label") description: I18n.tr("settings.bar.appearance.show-capsule.description") checked: Settings.data.bar.showCapsule + isSettings: true + defaultValue: Settings.getDefaultValue("bar.showCapsule") onToggled: checked => Settings.data.bar.showCapsule = checked } @@ -111,17 +121,16 @@ ColumnLayout { spacing: Style.marginXXS visible: Settings.data.bar.showCapsule - NLabel { - label: I18n.tr("settings.bar.appearance.capsule-opacity.label") - description: I18n.tr("settings.bar.appearance.capsule-opacity.description") - } - NValueSlider { Layout.fillWidth: true + label: I18n.tr("settings.bar.appearance.capsule-opacity.label") + description: I18n.tr("settings.bar.appearance.capsule-opacity.description") from: 0 to: 1 stepSize: 0.01 value: Settings.data.bar.capsuleOpacity + isSettings: true + defaultValue: Settings.getDefaultValue("bar.capsuleOpacity") onMoved: value => Settings.data.bar.capsuleOpacity = value text: Math.floor(Settings.data.bar.capsuleOpacity * 100) + "%" } @@ -132,6 +141,8 @@ ColumnLayout { label: I18n.tr("settings.bar.appearance.floating.label") description: I18n.tr("settings.bar.appearance.floating.description") checked: Settings.data.bar.floating + isSettings: true + defaultValue: Settings.getDefaultValue("bar.floating") onToggled: checked => { Settings.data.bar.floating = checked; } @@ -143,6 +154,8 @@ ColumnLayout { description: I18n.tr("settings.bar.appearance.outer-corners.description") checked: Settings.data.bar.outerCorners visible: !Settings.data.bar.floating + isSettings: true + defaultValue: Settings.getDefaultValue("bar.outerCorners") onToggled: checked => Settings.data.bar.outerCorners = checked } @@ -164,18 +177,15 @@ ColumnLayout { ColumnLayout { spacing: Style.marginXXS - NText { - text: I18n.tr("settings.bar.appearance.margins.vertical") - pointSize: Style.fontSizeXS - color: Color.mOnSurfaceVariant - } - NValueSlider { Layout.fillWidth: true + label: I18n.tr("settings.bar.appearance.margins.vertical") from: 0 to: 1 stepSize: 0.01 value: Settings.data.bar.marginVertical + isSettings: true + defaultValue: Settings.getDefaultValue("bar.marginVertical") onMoved: value => Settings.data.bar.marginVertical = value text: Math.round(Settings.data.bar.marginVertical * 100) + "%" } @@ -184,18 +194,15 @@ ColumnLayout { ColumnLayout { spacing: Style.marginXXS - NText { - text: I18n.tr("settings.bar.appearance.margins.horizontal") - pointSize: Style.fontSizeXS - color: Color.mOnSurfaceVariant - } - NValueSlider { Layout.fillWidth: true + label: I18n.tr("settings.bar.appearance.margins.horizontal") from: 0 to: 1 stepSize: 0.01 value: Settings.data.bar.marginHorizontal + isSettings: true + defaultValue: Settings.getDefaultValue("bar.marginHorizontal") onMoved: value => Settings.data.bar.marginHorizontal = value text: Math.ceil(Settings.data.bar.marginHorizontal * 100) + "%" } diff --git a/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml b/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml index 4363f8d2d..7915742f7 100644 --- a/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml +++ b/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml @@ -37,6 +37,8 @@ ColumnLayout { label: I18n.tr("settings.desktop-widgets.enabled.label") description: I18n.tr("settings.desktop-widgets.enabled.description") checked: Settings.data.desktopWidgets.enabled + isSettings: true + defaultValue: Settings.getDefaultValue("desktopWidgets.enabled") onToggled: checked => Settings.data.desktopWidgets.enabled = checked } diff --git a/Modules/Panels/Settings/Tabs/DockTab.qml b/Modules/Panels/Settings/Tabs/DockTab.qml index 8e8055ca3..d4a99cf9f 100644 --- a/Modules/Panels/Settings/Tabs/DockTab.qml +++ b/Modules/Panels/Settings/Tabs/DockTab.qml @@ -34,6 +34,8 @@ ColumnLayout { label: I18n.tr("settings.dock.enabled.label") description: I18n.tr("settings.dock.enabled.description") checked: Settings.data.dock.enabled + isSettings: true + defaultValue: Settings.getDefaultValue("dock.enabled") onToggled: checked => Settings.data.dock.enabled = checked } @@ -57,106 +59,86 @@ ColumnLayout { } ] currentKey: Settings.data.dock.displayMode + isSettings: true + defaultValue: Settings.getDefaultValue("dock.displayMode") onSelected: key => { Settings.data.dock.displayMode = key; } } - ColumnLayout { + NValueSlider { visible: Settings.data.dock.enabled - spacing: Style.marginXXS Layout.fillWidth: true - NLabel { - label: I18n.tr("settings.dock.appearance.background-opacity.label") - description: I18n.tr("settings.dock.appearance.background-opacity.description") - } - NValueSlider { - Layout.fillWidth: true - from: 0 - to: 1 - stepSize: 0.01 - value: Settings.data.dock.backgroundOpacity - onMoved: value => Settings.data.dock.backgroundOpacity = value - text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%" - } + label: I18n.tr("settings.dock.appearance.background-opacity.label") + description: I18n.tr("settings.dock.appearance.background-opacity.description") + from: 0 + to: 1 + stepSize: 0.01 + value: Settings.data.dock.backgroundOpacity + isSettings: true + defaultValue: Settings.getDefaultValue("dock.backgroundOpacity") + onMoved: value => Settings.data.dock.backgroundOpacity = value + text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%" } - ColumnLayout { + NValueSlider { visible: Settings.data.dock.enabled - spacing: Style.marginXXS Layout.fillWidth: true - NLabel { - label: I18n.tr("settings.dock.appearance.dead-opacity.label") - description: I18n.tr("settings.dock.appearance.dead-opacity.description") - } - NValueSlider { - Layout.fillWidth: true - from: 0 - to: 1 - stepSize: 0.01 - value: Settings.data.dock.deadOpacity - onMoved: value => Settings.data.dock.deadOpacity = value - text: Math.floor(Settings.data.dock.deadOpacity * 100) + "%" - } + label: I18n.tr("settings.dock.appearance.dead-opacity.label") + description: I18n.tr("settings.dock.appearance.dead-opacity.description") + from: 0 + to: 1 + stepSize: 0.01 + value: Settings.data.dock.deadOpacity + isSettings: true + defaultValue: Settings.getDefaultValue("dock.deadOpacity") + onMoved: value => Settings.data.dock.deadOpacity = value + text: Math.floor(Settings.data.dock.deadOpacity * 100) + "%" } - ColumnLayout { + NValueSlider { visible: Settings.data.dock.enabled - spacing: Style.marginXXS Layout.fillWidth: true - - NLabel { - label: I18n.tr("settings.dock.appearance.floating-distance.label") - description: I18n.tr("settings.dock.appearance.floating-distance.description") - } - - NValueSlider { - Layout.fillWidth: true - from: 0 - to: 4 - stepSize: 0.01 - value: Settings.data.dock.floatingRatio - onMoved: value => Settings.data.dock.floatingRatio = value - text: Math.floor(Settings.data.dock.floatingRatio * 100) + "%" - } + label: I18n.tr("settings.dock.appearance.floating-distance.label") + description: I18n.tr("settings.dock.appearance.floating-distance.description") + from: 0 + to: 4 + stepSize: 0.01 + value: Settings.data.dock.floatingRatio + isSettings: true + defaultValue: Settings.getDefaultValue("dock.floatingRatio") + onMoved: value => Settings.data.dock.floatingRatio = value + text: Math.floor(Settings.data.dock.floatingRatio * 100) + "%" } - ColumnLayout { + NValueSlider { visible: Settings.data.dock.enabled - spacing: Style.marginXXS Layout.fillWidth: true - NLabel { - label: I18n.tr("settings.dock.appearance.icon-size.label") - description: I18n.tr("settings.dock.appearance.icon-size.description") - } - NValueSlider { - Layout.fillWidth: true - from: 0 - to: 2 - stepSize: 0.01 - value: Settings.data.dock.size - onMoved: value => Settings.data.dock.size = value - text: Math.floor(Settings.data.dock.size * 100) + "%" - } + label: I18n.tr("settings.dock.appearance.icon-size.label") + description: I18n.tr("settings.dock.appearance.icon-size.description") + from: 0 + to: 2 + stepSize: 0.01 + value: Settings.data.dock.size + isSettings: true + defaultValue: Settings.getDefaultValue("dock.size") + onMoved: value => Settings.data.dock.size = value + text: Math.floor(Settings.data.dock.size * 100) + "%" } - ColumnLayout { + NValueSlider { visible: Settings.data.dock.enabled && Settings.data.dock.displayMode === "auto_hide" - spacing: Style.marginXXS Layout.fillWidth: true - NLabel { - label: I18n.tr("settings.dock.appearance.hide-show-speed.label") - description: I18n.tr("settings.dock.appearance.hide-show-speed.description") - } - NValueSlider { - Layout.fillWidth: true - from: 0.1 - to: 2.0 - stepSize: 0.01 - value: Settings.data.dock.animationSpeed - onMoved: value => Settings.data.dock.animationSpeed = value - text: (Settings.data.dock.animationSpeed * 100).toFixed(0) + "%" - } + label: I18n.tr("settings.dock.appearance.hide-show-speed.label") + description: I18n.tr("settings.dock.appearance.hide-show-speed.description") + from: 0.1 + to: 2.0 + stepSize: 0.01 + value: Settings.data.dock.animationSpeed + isSettings: true + defaultValue: Settings.getDefaultValue("dock.animationSpeed") + onMoved: value => Settings.data.dock.animationSpeed = value + text: (Settings.data.dock.animationSpeed * 100).toFixed(0) + "%" } NToggle { @@ -164,6 +146,8 @@ ColumnLayout { label: I18n.tr("settings.dock.appearance.inactive-indicators.label") description: I18n.tr("settings.dock.appearance.inactive-indicators.description") checked: Settings.data.dock.inactiveIndicators + isSettings: true + defaultValue: Settings.getDefaultValue("dock.inactiveIndicators") onToggled: checked => Settings.data.dock.inactiveIndicators = checked } @@ -172,6 +156,8 @@ ColumnLayout { label: I18n.tr("settings.dock.appearance.pinned-static.label") description: I18n.tr("settings.dock.appearance.pinned-static.description") checked: Settings.data.dock.pinnedStatic + isSettings: true + defaultValue: Settings.getDefaultValue("dock.pinnedStatic") onToggled: checked => Settings.data.dock.pinnedStatic = checked } @@ -180,6 +166,8 @@ ColumnLayout { label: I18n.tr("settings.dock.monitors.only-same-monitor.label") description: I18n.tr("settings.dock.monitors.only-same-monitor.description") checked: Settings.data.dock.onlySameOutput + isSettings: true + defaultValue: Settings.getDefaultValue("dock.onlySameOutput") onToggled: checked => Settings.data.dock.onlySameOutput = checked } @@ -189,6 +177,8 @@ ColumnLayout { label: I18n.tr("settings.dock.appearance.colorize-icons.label") description: I18n.tr("settings.dock.appearance.colorize-icons.description") checked: Settings.data.dock.colorizeIcons + isSettings: true + defaultValue: Settings.getDefaultValue("dock.colorizeIcons") onToggled: checked => Settings.data.dock.colorizeIcons = checked } diff --git a/Modules/Panels/Settings/Tabs/GeneralTab.qml b/Modules/Panels/Settings/Tabs/GeneralTab.qml index c4d8db6e1..d0311eeb8 100644 --- a/Modules/Panels/Settings/Tabs/GeneralTab.qml +++ b/Modules/Panels/Settings/Tabs/GeneralTab.qml @@ -91,6 +91,9 @@ ColumnLayout { searchPlaceholder: I18n.tr("settings.general.fonts.default.search-placeholder") popupHeight: 420 minimumWidth: 300 + isSettings: true + defaultValue: Settings.getDefaultValue("ui.fontDefault") + settingsPath: "ui.fontDefault" onSelected: key => Settings.data.ui.fontDefault = key } @@ -103,79 +106,76 @@ ColumnLayout { searchPlaceholder: I18n.tr("settings.general.fonts.monospace.search-placeholder") popupHeight: 320 minimumWidth: 300 + isSettings: true + defaultValue: Settings.getDefaultValue("ui.fontFixed") + settingsPath: "ui.fontFixed" onSelected: key => Settings.data.ui.fontFixed = key } - ColumnLayout { - NLabel { + RowLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NValueSlider { + Layout.fillWidth: true label: I18n.tr("settings.general.fonts.default.scale.label") description: I18n.tr("settings.general.fonts.default.scale.description") + from: 0.75 + to: 1.25 + stepSize: 0.01 + value: Settings.data.ui.fontDefaultScale + isSettings: true + defaultValue: Settings.getDefaultValue("ui.fontDefaultScale") + onMoved: value => Settings.data.ui.fontDefaultScale = value + text: Math.floor(Settings.data.ui.fontDefaultScale * 100) + "%" } - RowLayout { - spacing: Style.marginL - Layout.fillWidth: true + // Reset button container + Item { + Layout.preferredWidth: 30 * Style.uiScaleRatio + Layout.preferredHeight: 30 * Style.uiScaleRatio - NValueSlider { - Layout.fillWidth: true - from: 0.75 - to: 1.25 - stepSize: 0.01 - value: Settings.data.ui.fontDefaultScale - onMoved: value => Settings.data.ui.fontDefaultScale = value - text: Math.floor(Settings.data.ui.fontDefaultScale * 100) + "%" - } - - // Reset button container - Item { - Layout.preferredWidth: 30 * Style.uiScaleRatio - Layout.preferredHeight: 30 * Style.uiScaleRatio - - NIconButton { - icon: "refresh" - baseSize: Style.baseWidgetSize * 0.8 - tooltipText: I18n.tr("settings.general.fonts.reset-scaling") - onClicked: Settings.data.ui.fontDefaultScale = 1.0 - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - } + NIconButton { + icon: "restore" + baseSize: Style.baseWidgetSize * 0.8 + tooltipText: I18n.tr("settings.general.fonts.reset-scaling") + onClicked: Settings.data.ui.fontDefaultScale = 1.0 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter } } } - ColumnLayout { - NLabel { + RowLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NValueSlider { + Layout.fillWidth: true label: I18n.tr("settings.general.fonts.monospace.scale.label") description: I18n.tr("settings.general.fonts.monospace.scale.description") + from: 0.75 + to: 1.25 + stepSize: 0.01 + value: Settings.data.ui.fontFixedScale + isSettings: true + defaultValue: Settings.getDefaultValue("ui.fontFixedScale") + onMoved: value => Settings.data.ui.fontFixedScale = value + text: Math.floor(Settings.data.ui.fontFixedScale * 100) + "%" } - RowLayout { - spacing: Style.marginL - Layout.fillWidth: true + // Reset button container + Item { + Layout.preferredWidth: 30 * Style.uiScaleRatio + Layout.preferredHeight: 30 * Style.uiScaleRatio - NValueSlider { - Layout.fillWidth: true - from: 0.75 - to: 1.25 - stepSize: 0.01 - value: Settings.data.ui.fontFixedScale - onMoved: value => Settings.data.ui.fontFixedScale = value - text: Math.floor(Settings.data.ui.fontFixedScale * 100) + "%" - } - - // Reset button container - Item { - Layout.preferredWidth: 30 * Style.uiScaleRatio - Layout.preferredHeight: 30 * Style.uiScaleRatio - - NIconButton { - icon: "refresh" - baseSize: Style.baseWidgetSize * 0.8 - tooltipText: I18n.tr("settings.general.fonts.reset-scaling") - onClicked: Settings.data.ui.fontFixedScale = 1.0 - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - } + NIconButton { + icon: "restore" + baseSize: Style.baseWidgetSize * 0.8 + tooltipText: I18n.tr("settings.general.fonts.reset-scaling") + onClicked: Settings.data.ui.fontFixedScale = 1.0 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter } } } @@ -202,6 +202,8 @@ ColumnLayout { Layout.fillWidth: true label: I18n.tr("settings.general.language.select.label") description: I18n.tr("settings.general.language.select.description") + isSettings: true + defaultValue: Settings.getDefaultValue("general.language") model: [ { "key": "", @@ -214,6 +216,7 @@ ColumnLayout { }; })) currentKey: Settings.data.general.language + settingsPath: "general.language" onSelected: key => { // Need to change language on next frame using "callLater" or it will pull the rug below our feet: the NComboBox would be rebuilt immediately before it can close properly. Qt.callLater(() => { diff --git a/Modules/Panels/Settings/Tabs/NotificationsTab.qml b/Modules/Panels/Settings/Tabs/NotificationsTab.qml index d9ed2d619..033572c3b 100644 --- a/Modules/Panels/Settings/Tabs/NotificationsTab.qml +++ b/Modules/Panels/Settings/Tabs/NotificationsTab.qml @@ -37,6 +37,8 @@ ColumnLayout { label: I18n.tr("settings.notifications.settings.enabled.label") description: I18n.tr("settings.notifications.settings.enabled.description") checked: Settings.data.notifications.enabled !== false + isSettings: true + defaultValue: Settings.getDefaultValue("notifications.enabled") onToggled: checked => Settings.data.notifications.enabled = checked } @@ -77,6 +79,8 @@ ColumnLayout { } ] currentKey: Settings.data.notifications.location || "top_right" + isSettings: true + defaultValue: Settings.getDefaultValue("notifications.location") || "top_right" onSelected: key => Settings.data.notifications.location = key } @@ -84,28 +88,24 @@ ColumnLayout { label: I18n.tr("settings.notifications.settings.always-on-top.label") description: I18n.tr("settings.notifications.settings.always-on-top.description") checked: Settings.data.notifications.overlayLayer + isSettings: true + defaultValue: Settings.getDefaultValue("notifications.overlayLayer") onToggled: checked => Settings.data.notifications.overlayLayer = checked } // Background Opacity - ColumnLayout { - spacing: Style.marginXXS + NValueSlider { Layout.fillWidth: true - - NLabel { - label: I18n.tr("settings.notifications.settings.background-opacity.label") - description: I18n.tr("settings.notifications.settings.background-opacity.description") - } - - NValueSlider { - Layout.fillWidth: true - from: 0 - to: 100 - stepSize: 1 - value: Settings.data.notifications.backgroundOpacity * 100 - onMoved: value => Settings.data.notifications.backgroundOpacity = value / 100 - text: Math.round(Settings.data.notifications.backgroundOpacity * 100) + "%" - } + label: I18n.tr("settings.notifications.settings.background-opacity.label") + description: I18n.tr("settings.notifications.settings.background-opacity.description") + from: 0 + to: 100 + stepSize: 1 + value: Settings.data.notifications.backgroundOpacity * 100 + isSettings: true + defaultValue: (Settings.getDefaultValue("notifications.backgroundOpacity") || 1) * 100 + onMoved: value => Settings.data.notifications.backgroundOpacity = value / 100 + text: Math.round(Settings.data.notifications.backgroundOpacity * 100) + "%" } // OSD settings moved to the dedicated OSD tab @@ -159,6 +159,8 @@ ColumnLayout { description: I18n.tr("settings.notifications.sounds.enabled.description") checked: Settings.data.notifications?.sounds?.enabled ?? false visible: SoundService.multimediaAvailable + isSettings: true + defaultValue: Settings.getDefaultValue("notifications.sounds.enabled") onToggled: checked => Settings.data.notifications.sounds.enabled = checked } @@ -168,17 +170,16 @@ ColumnLayout { Layout.fillWidth: true visible: SoundService.multimediaAvailable && (Settings.data.notifications?.sounds?.enabled ?? false) - NLabel { - label: I18n.tr("settings.notifications.sounds.volume.label") - description: I18n.tr("settings.notifications.sounds.volume.description") - } - NValueSlider { Layout.fillWidth: true + label: I18n.tr("settings.notifications.sounds.volume.label") + description: I18n.tr("settings.notifications.sounds.volume.description") from: 0 to: 100 stepSize: 1 value: (Settings.data.notifications?.sounds?.volume ?? 0.5) * 100 + isSettings: true + defaultValue: (Settings.getDefaultValue("notifications.sounds.volume") || 0.5) * 100 onMoved: value => Settings.data.notifications.sounds.volume = value / 100 text: Math.round((Settings.data.notifications?.sounds?.volume ?? 0.5) * 100) + "%" } @@ -191,6 +192,8 @@ ColumnLayout { label: I18n.tr("settings.notifications.sounds.separate.label") description: I18n.tr("settings.notifications.sounds.separate.description") checked: Settings.data.notifications?.sounds?.separateSounds ?? false + isSettings: true + defaultValue: Settings.getDefaultValue("notifications.sounds.separateSounds") onToggled: checked => Settings.data.notifications.sounds.separateSounds = checked } @@ -332,126 +335,110 @@ ColumnLayout { label: I18n.tr("settings.notifications.duration.respect-expire.label") description: I18n.tr("settings.notifications.duration.respect-expire.description") checked: Settings.data.notifications.respectExpireTimeout + isSettings: true + defaultValue: Settings.getDefaultValue("notifications.respectExpireTimeout") onToggled: checked => Settings.data.notifications.respectExpireTimeout = checked } // Low Urgency Duration - ColumnLayout { - spacing: Style.marginXXS + RowLayout { + spacing: Style.marginL Layout.fillWidth: true - NLabel { + NValueSlider { + Layout.fillWidth: true label: I18n.tr("settings.notifications.duration.low-urgency.label") description: I18n.tr("settings.notifications.duration.low-urgency.description") + from: 1 + to: 30 + stepSize: 1 + value: Settings.data.notifications.lowUrgencyDuration + isSettings: true + defaultValue: Settings.getDefaultValue("notifications.lowUrgencyDuration") + onMoved: value => Settings.data.notifications.lowUrgencyDuration = value + text: Settings.data.notifications.lowUrgencyDuration + "s" } - - RowLayout { - spacing: Style.marginL - Layout.fillWidth: true - - NValueSlider { - Layout.fillWidth: true - from: 1 - to: 30 - stepSize: 1 - value: Settings.data.notifications.lowUrgencyDuration - onMoved: value => Settings.data.notifications.lowUrgencyDuration = value - text: Settings.data.notifications.lowUrgencyDuration + "s" - } - // Reset button container - Item { - Layout.preferredWidth: 30 * Style.uiScaleRatio - Layout.preferredHeight: 30 * Style.uiScaleRatio + // Reset button container + Item { + Layout.preferredWidth: 30 * Style.uiScaleRatio + Layout.preferredHeight: 30 * Style.uiScaleRatio NIconButton { - icon: "refresh" + icon: "restore" baseSize: Style.baseWidgetSize * 0.8 tooltipText: I18n.tr("settings.notifications.duration.reset") onClicked: Settings.data.notifications.lowUrgencyDuration = 3 - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - } + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter } } } // Normal Urgency Duration - ColumnLayout { - spacing: Style.marginXXS + RowLayout { + spacing: Style.marginL Layout.fillWidth: true - NLabel { + NValueSlider { + Layout.fillWidth: true label: I18n.tr("settings.notifications.duration.normal-urgency.label") description: I18n.tr("settings.notifications.duration.normal-urgency.description") + from: 1 + to: 30 + stepSize: 1 + value: Settings.data.notifications.normalUrgencyDuration + isSettings: true + defaultValue: Settings.getDefaultValue("notifications.normalUrgencyDuration") + onMoved: value => Settings.data.notifications.normalUrgencyDuration = value + text: Settings.data.notifications.normalUrgencyDuration + "s" } - RowLayout { - spacing: Style.marginL - Layout.fillWidth: true - - NValueSlider { - Layout.fillWidth: true - from: 1 - to: 30 - stepSize: 1 - value: Settings.data.notifications.normalUrgencyDuration - onMoved: value => Settings.data.notifications.normalUrgencyDuration = value - text: Settings.data.notifications.normalUrgencyDuration + "s" - } - - // Reset button container - Item { - Layout.preferredWidth: 30 * Style.uiScaleRatio - Layout.preferredHeight: 30 * Style.uiScaleRatio + // Reset button container + Item { + Layout.preferredWidth: 30 * Style.uiScaleRatio + Layout.preferredHeight: 30 * Style.uiScaleRatio NIconButton { - icon: "refresh" + icon: "restore" baseSize: Style.baseWidgetSize * 0.8 tooltipText: I18n.tr("settings.notifications.duration.reset") onClicked: Settings.data.notifications.normalUrgencyDuration = 8 - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - } + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter } } } // Critical Urgency Duration - ColumnLayout { - spacing: Style.marginXXS + RowLayout { + spacing: Style.marginL Layout.fillWidth: true - NLabel { + NValueSlider { + Layout.fillWidth: true label: I18n.tr("settings.notifications.duration.critical-urgency.label") description: I18n.tr("settings.notifications.duration.critical-urgency.description") + from: 1 + to: 30 + stepSize: 1 + value: Settings.data.notifications.criticalUrgencyDuration + isSettings: true + defaultValue: Settings.getDefaultValue("notifications.criticalUrgencyDuration") + onMoved: value => Settings.data.notifications.criticalUrgencyDuration = value + text: Settings.data.notifications.criticalUrgencyDuration + "s" } - - RowLayout { - spacing: Style.marginL - Layout.fillWidth: true - - NValueSlider { - Layout.fillWidth: true - from: 1 - to: 30 - stepSize: 1 - value: Settings.data.notifications.criticalUrgencyDuration - onMoved: value => Settings.data.notifications.criticalUrgencyDuration = value - text: Settings.data.notifications.criticalUrgencyDuration + "s" - } - // Reset button container - Item { - Layout.preferredWidth: 30 * Style.uiScaleRatio - Layout.preferredHeight: 30 * Style.uiScaleRatio + // Reset button container + Item { + Layout.preferredWidth: 30 * Style.uiScaleRatio + Layout.preferredHeight: 30 * Style.uiScaleRatio NIconButton { - icon: "refresh" + icon: "restore" baseSize: Style.baseWidgetSize * 0.8 tooltipText: I18n.tr("settings.notifications.duration.reset") onClicked: Settings.data.notifications.criticalUrgencyDuration = 15 - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - } + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter } } } @@ -509,6 +496,8 @@ ColumnLayout { label: I18n.tr("settings.notifications.toast.keyboard.label") description: I18n.tr("settings.notifications.toast.keyboard.description") checked: Settings.data.notifications.enableKeyboardLayoutToast + isSettings: true + defaultValue: Settings.getDefaultValue("notifications.enableKeyboardLayoutToast") onToggled: checked => Settings.data.notifications.enableKeyboardLayoutToast = checked } } diff --git a/Modules/Panels/Settings/Tabs/OsdTab.qml b/Modules/Panels/Settings/Tabs/OsdTab.qml index 0e7a5848b..a5ff77094 100644 --- a/Modules/Panels/Settings/Tabs/OsdTab.qml +++ b/Modules/Panels/Settings/Tabs/OsdTab.qml @@ -77,6 +77,8 @@ ColumnLayout { } ] currentKey: Settings.data.osd.location || "top_right" + isSettings: true + defaultValue: Settings.getDefaultValue("osd.location") || "top_right" onSelected: key => Settings.data.osd.location = key } } @@ -101,6 +103,8 @@ ColumnLayout { label: I18n.tr("settings.osd.enabled.label") description: I18n.tr("settings.osd.enabled.description") checked: Settings.data.osd.enabled + isSettings: true + defaultValue: Settings.getDefaultValue("osd.enabled") onToggled: checked => Settings.data.osd.enabled = checked } @@ -108,35 +112,35 @@ ColumnLayout { label: I18n.tr("settings.osd.always-on-top.label") description: I18n.tr("settings.osd.always-on-top.description") checked: Settings.data.osd.overlayLayer + isSettings: true + defaultValue: Settings.getDefaultValue("osd.overlayLayer") onToggled: checked => Settings.data.osd.overlayLayer = checked } - NLabel { - label: I18n.tr("settings.osd.background-opacity.label", "Background opacity") - description: I18n.tr("settings.osd.background-opacity.description", "Controls the transparency of the OSD background.") - } - NValueSlider { Layout.fillWidth: true + label: I18n.tr("settings.osd.background-opacity.label", "Background opacity") + description: I18n.tr("settings.osd.background-opacity.description", "Controls the transparency of the OSD background.") from: 0 to: 100 stepSize: 1 value: Settings.data.osd.backgroundOpacity * 100 + isSettings: true + defaultValue: (Settings.getDefaultValue("osd.backgroundOpacity") || 1) * 100 onMoved: value => Settings.data.osd.backgroundOpacity = value / 100 text: Math.round(Settings.data.osd.backgroundOpacity * 100) + "%" } - NLabel { - label: I18n.tr("settings.osd.duration.auto-hide.label") - description: I18n.tr("settings.osd.duration.auto-hide.description") - } - NValueSlider { Layout.fillWidth: true + label: I18n.tr("settings.osd.duration.auto-hide.label") + description: I18n.tr("settings.osd.duration.auto-hide.description") from: 500 to: 5000 stepSize: 100 value: Settings.data.osd.autoHideMs + isSettings: true + defaultValue: Settings.getDefaultValue("osd.autoHideMs") onMoved: value => Settings.data.osd.autoHideMs = value text: Math.round(Settings.data.osd.autoHideMs / 1000 * 10) / 10 + "s" } diff --git a/Modules/Panels/Settings/Tabs/SystemMonitorTab.qml b/Modules/Panels/Settings/Tabs/SystemMonitorTab.qml index 36cc5f2d3..ee54d571a 100644 --- a/Modules/Panels/Settings/Tabs/SystemMonitorTab.qml +++ b/Modules/Panels/Settings/Tabs/SystemMonitorTab.qml @@ -24,6 +24,8 @@ ColumnLayout { label: I18n.tr("settings.system-monitor.enable-nvidia-gpu.label") description: I18n.tr("settings.system-monitor.enable-nvidia-gpu.description") checked: Settings.data.systemMonitor.enableNvidiaGpu + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.enableNvidiaGpu") onToggled: checked => Settings.data.systemMonitor.enableNvidiaGpu = checked } @@ -36,6 +38,8 @@ ColumnLayout { label: I18n.tr("settings.system-monitor.use-custom-highlight-colors.label") description: I18n.tr("settings.system-monitor.use-custom-highlight-colors.description") checked: Settings.data.systemMonitor.useCustomColors + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.useCustomColors") onToggled: { // If enabling custom colors and no custom color is saved, persist current theme colors if (checked) { @@ -136,6 +140,8 @@ ColumnLayout { to: 100 stepSize: 5 value: Settings.data.systemMonitor.cpuWarningThreshold + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.cpuWarningThreshold") onValueChanged: { Settings.data.systemMonitor.cpuWarningThreshold = value; // Ensure critical >= warning @@ -164,6 +170,8 @@ ColumnLayout { to: 100 stepSize: 5 value: Settings.data.systemMonitor.cpuCriticalThreshold + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.cpuCriticalThreshold") onValueChanged: Settings.data.systemMonitor.cpuCriticalThreshold = value suffix: "%" } @@ -186,6 +194,8 @@ ColumnLayout { to: 10000 stepSize: 250 value: Settings.data.systemMonitor.cpuPollingInterval + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.cpuPollingInterval") onValueChanged: Settings.data.systemMonitor.cpuPollingInterval = value suffix: " ms" } @@ -221,6 +231,8 @@ ColumnLayout { to: 100 stepSize: 5 value: Settings.data.systemMonitor.tempWarningThreshold + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.tempWarningThreshold") onValueChanged: { Settings.data.systemMonitor.tempWarningThreshold = value; if (Settings.data.systemMonitor.tempCriticalThreshold < value) { @@ -248,6 +260,8 @@ ColumnLayout { to: 100 stepSize: 5 value: Settings.data.systemMonitor.tempCriticalThreshold + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.tempCriticalThreshold") onValueChanged: Settings.data.systemMonitor.tempCriticalThreshold = value suffix: "°C" } @@ -270,6 +284,8 @@ ColumnLayout { to: 10000 stepSize: 250 value: Settings.data.systemMonitor.tempPollingInterval + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.tempPollingInterval") onValueChanged: Settings.data.systemMonitor.tempPollingInterval = value suffix: " ms" } @@ -307,6 +323,8 @@ ColumnLayout { to: 120 stepSize: 5 value: Settings.data.systemMonitor.gpuWarningThreshold + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.gpuWarningThreshold") onValueChanged: { Settings.data.systemMonitor.gpuWarningThreshold = value; if (Settings.data.systemMonitor.gpuCriticalThreshold < value) { @@ -334,6 +352,8 @@ ColumnLayout { to: 120 stepSize: 5 value: Settings.data.systemMonitor.gpuCriticalThreshold + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.gpuCriticalThreshold") onValueChanged: Settings.data.systemMonitor.gpuCriticalThreshold = value suffix: "°C" } @@ -356,6 +376,8 @@ ColumnLayout { to: 10000 stepSize: 250 value: Settings.data.systemMonitor.gpuPollingInterval + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.gpuPollingInterval") onValueChanged: Settings.data.systemMonitor.gpuPollingInterval = value suffix: " ms" } @@ -391,6 +413,8 @@ ColumnLayout { to: 100 stepSize: 5 value: Settings.data.systemMonitor.memWarningThreshold + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.memWarningThreshold") onValueChanged: { Settings.data.systemMonitor.memWarningThreshold = value; if (Settings.data.systemMonitor.memCriticalThreshold < value) { @@ -418,6 +442,8 @@ ColumnLayout { to: 100 stepSize: 5 value: Settings.data.systemMonitor.memCriticalThreshold + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.memCriticalThreshold") onValueChanged: Settings.data.systemMonitor.memCriticalThreshold = value suffix: "%" } @@ -440,6 +466,8 @@ ColumnLayout { to: 10000 stepSize: 250 value: Settings.data.systemMonitor.memPollingInterval + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.memPollingInterval") onValueChanged: Settings.data.systemMonitor.memPollingInterval = value suffix: " ms" } @@ -475,6 +503,8 @@ ColumnLayout { to: 100 stepSize: 5 value: Settings.data.systemMonitor.diskWarningThreshold + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.diskWarningThreshold") onValueChanged: { Settings.data.systemMonitor.diskWarningThreshold = value; if (Settings.data.systemMonitor.diskCriticalThreshold < value) { @@ -502,6 +532,8 @@ ColumnLayout { to: 100 stepSize: 5 value: Settings.data.systemMonitor.diskCriticalThreshold + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.diskCriticalThreshold") onValueChanged: Settings.data.systemMonitor.diskCriticalThreshold = value suffix: "%" } @@ -524,6 +556,8 @@ ColumnLayout { to: 10000 stepSize: 250 value: Settings.data.systemMonitor.diskPollingInterval + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.diskPollingInterval") onValueChanged: Settings.data.systemMonitor.diskPollingInterval = value suffix: " ms" } @@ -559,6 +593,8 @@ ColumnLayout { to: 10000 stepSize: 250 value: Settings.data.systemMonitor.networkPollingInterval + isSettings: true + defaultValue: Settings.getDefaultValue("systemMonitor.networkPollingInterval") onValueChanged: Settings.data.systemMonitor.networkPollingInterval = value suffix: " ms" } diff --git a/Modules/Panels/Settings/Tabs/UserInterfaceTab.qml b/Modules/Panels/Settings/Tabs/UserInterfaceTab.qml index a0ed49972..32d9a6492 100644 --- a/Modules/Panels/Settings/Tabs/UserInterfaceTab.qml +++ b/Modules/Panels/Settings/Tabs/UserInterfaceTab.qml @@ -23,6 +23,8 @@ ColumnLayout { label: I18n.tr("settings.user-interface.panels-attached-to-bar.label") description: I18n.tr("settings.user-interface.panels-attached-to-bar.description") checked: Settings.data.ui.panelsAttachedToBar + isSettings: true + defaultValue: Settings.getDefaultValue("ui.panelsAttachedToBar") onToggled: checked => Settings.data.ui.panelsAttachedToBar = checked } @@ -31,6 +33,8 @@ ColumnLayout { label: I18n.tr("settings.user-interface.allow-panels-without-bar.label") description: I18n.tr("settings.user-interface.allow-panels-without-bar.description") checked: Settings.data.general.allowPanelsOnScreenWithoutBar + isSettings: true + defaultValue: Settings.getDefaultValue("general.allowPanelsOnScreenWithoutBar") onToggled: checked => Settings.data.general.allowPanelsOnScreenWithoutBar = checked } @@ -54,49 +58,39 @@ ColumnLayout { } ] currentKey: Settings.data.ui.settingsPanelMode + isSettings: true + defaultValue: Settings.getDefaultValue("ui.settingsPanelMode") onSelected: key => Settings.data.ui.settingsPanelMode = key } // Panel Background Opacity - ColumnLayout { - spacing: Style.marginXXS + NValueSlider { Layout.fillWidth: true - - NLabel { - label: I18n.tr("settings.user-interface.panel-background-opacity.label") - description: I18n.tr("settings.user-interface.panel-background-opacity.description") - } - - NValueSlider { - Layout.fillWidth: true - from: 0.4 - to: 1 - stepSize: 0.01 - value: Settings.data.ui.panelBackgroundOpacity - onMoved: value => Settings.data.ui.panelBackgroundOpacity = value - text: Math.floor(Settings.data.ui.panelBackgroundOpacity * 100) + "%" - } + label: I18n.tr("settings.user-interface.panel-background-opacity.label") + description: I18n.tr("settings.user-interface.panel-background-opacity.description") + from: 0.4 + to: 1 + stepSize: 0.01 + value: Settings.data.ui.panelBackgroundOpacity + isSettings: true + defaultValue: Settings.getDefaultValue("ui.panelBackgroundOpacity") + onMoved: value => Settings.data.ui.panelBackgroundOpacity = value + text: Math.floor(Settings.data.ui.panelBackgroundOpacity * 100) + "%" } // Dim desktop opacity - ColumnLayout { - spacing: Style.marginXXS + NValueSlider { Layout.fillWidth: true - - NLabel { - label: I18n.tr("settings.user-interface.dimmer-opacity.label") - description: I18n.tr("settings.user-interface.dimmer-opacity.description") - } - - NValueSlider { - Layout.fillWidth: true - from: 0 - to: 1 - stepSize: 0.01 - value: Settings.data.general.dimmerOpacity - onMoved: value => Settings.data.general.dimmerOpacity = value - text: Math.floor(Settings.data.general.dimmerOpacity * 100) + "%" - } + label: I18n.tr("settings.user-interface.dimmer-opacity.label") + description: I18n.tr("settings.user-interface.dimmer-opacity.description") + from: 0 + to: 1 + stepSize: 0.01 + value: Settings.data.general.dimmerOpacity + isSettings: true + defaultValue: Settings.getDefaultValue("general.dimmerOpacity") + onMoved: value => Settings.data.general.dimmerOpacity = value + text: Math.floor(Settings.data.general.dimmerOpacity * 100) + "%" } NDivider { @@ -109,6 +103,8 @@ ColumnLayout { label: I18n.tr("settings.user-interface.tooltips.label") description: I18n.tr("settings.user-interface.tooltips.description") checked: Settings.data.ui.tooltipsEnabled + isSettings: true + defaultValue: Settings.getDefaultValue("ui.tooltipsEnabled") onToggled: checked => Settings.data.ui.tooltipsEnabled = checked } @@ -116,6 +112,8 @@ ColumnLayout { label: I18n.tr("settings.user-interface.shadows.label") description: I18n.tr("settings.user-interface.shadows.description") checked: Settings.data.general.enableShadows + isSettings: true + defaultValue: Settings.getDefaultValue("general.enableShadows") onToggled: checked => Settings.data.general.enableShadows = checked } @@ -173,6 +171,8 @@ ColumnLayout { }) currentKey: Settings.data.general.shadowDirection + isSettings: true + defaultValue: Settings.getDefaultValue("general.shadowDirection") onSelected: function (key) { var opt = shadowOptionsMap[key]; @@ -195,21 +195,20 @@ ColumnLayout { spacing: Style.marginXXS Layout.fillWidth: true - NLabel { - label: I18n.tr("settings.user-interface.scaling.label") - description: I18n.tr("settings.user-interface.scaling.description") - } - RowLayout { spacing: Style.marginL Layout.fillWidth: true NValueSlider { Layout.fillWidth: true + label: I18n.tr("settings.user-interface.scaling.label") + description: I18n.tr("settings.user-interface.scaling.description") from: 0.8 to: 1.2 stepSize: 0.05 value: Settings.data.general.scaleRatio + isSettings: true + defaultValue: Settings.getDefaultValue("general.scaleRatio") onMoved: value => Settings.data.general.scaleRatio = value text: Math.floor(Settings.data.general.scaleRatio * 100) + "%" } @@ -220,7 +219,7 @@ ColumnLayout { Layout.preferredHeight: 30 * Style.uiScaleRatio NIconButton { - icon: "refresh" + icon: "restore" baseSize: Style.baseWidgetSize * 0.8 tooltipText: I18n.tr("settings.user-interface.scaling.reset-scaling") onClicked: Settings.data.general.scaleRatio = 1.0 @@ -232,83 +231,71 @@ ColumnLayout { } // Container Border Radius - ColumnLayout { - spacing: Style.marginXXS + RowLayout { + spacing: Style.marginL Layout.fillWidth: true - NLabel { + NValueSlider { + Layout.fillWidth: true label: I18n.tr("settings.user-interface.box-border-radius.label") description: I18n.tr("settings.user-interface.box-border-radius.description") + from: 0 + to: 2 + stepSize: 0.01 + value: Settings.data.general.radiusRatio + isSettings: true + defaultValue: Settings.getDefaultValue("general.radiusRatio") + onMoved: value => Settings.data.general.radiusRatio = value + text: Math.floor(Settings.data.general.radiusRatio * 100) + "%" } - RowLayout { - spacing: Style.marginL - Layout.fillWidth: true + // Reset button container + Item { + Layout.preferredWidth: 30 * Style.uiScaleRatio + Layout.preferredHeight: 30 * Style.uiScaleRatio - NValueSlider { - Layout.fillWidth: true - from: 0 - to: 2 - stepSize: 0.01 - value: Settings.data.general.radiusRatio - onMoved: value => Settings.data.general.radiusRatio = value - text: Math.floor(Settings.data.general.radiusRatio * 100) + "%" - } - - // Reset button container - Item { - Layout.preferredWidth: 30 * Style.uiScaleRatio - Layout.preferredHeight: 30 * Style.uiScaleRatio - - NIconButton { - icon: "refresh" - baseSize: Style.baseWidgetSize * 0.8 - tooltipText: I18n.tr("settings.user-interface.box-border-radius.reset") - onClicked: Settings.data.general.radiusRatio = 1.0 - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - } + NIconButton { + icon: "restore" + baseSize: Style.baseWidgetSize * 0.8 + tooltipText: I18n.tr("settings.user-interface.box-border-radius.reset") + onClicked: Settings.data.general.radiusRatio = 1.0 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter } } } // Control Border Radius (for UI components) - ColumnLayout { - spacing: Style.marginXXS + RowLayout { + spacing: Style.marginL Layout.fillWidth: true - NLabel { + NValueSlider { + Layout.fillWidth: true label: I18n.tr("settings.user-interface.control-border-radius.label") description: I18n.tr("settings.user-interface.control-border-radius.description") + from: 0 + to: 2 + stepSize: 0.01 + value: Settings.data.general.iRadiusRatio + isSettings: true + defaultValue: Settings.getDefaultValue("general.iRadiusRatio") + onMoved: value => Settings.data.general.iRadiusRatio = value + text: Math.floor(Settings.data.general.iRadiusRatio * 100) + "%" } - RowLayout { - spacing: Style.marginL - Layout.fillWidth: true + // Reset button container + Item { + Layout.preferredWidth: 30 * Style.uiScaleRatio + Layout.preferredHeight: 30 * Style.uiScaleRatio - NValueSlider { - Layout.fillWidth: true - from: 0 - to: 2 - stepSize: 0.01 - value: Settings.data.general.iRadiusRatio - onMoved: value => Settings.data.general.iRadiusRatio = value - text: Math.floor(Settings.data.general.iRadiusRatio * 100) + "%" - } - - // Reset button container - Item { - Layout.preferredWidth: 30 * Style.uiScaleRatio - Layout.preferredHeight: 30 * Style.uiScaleRatio - - NIconButton { - icon: "refresh" - baseSize: Style.baseWidgetSize * 0.8 - tooltipText: I18n.tr("settings.user-interface.control-border-radius.reset") - onClicked: Settings.data.general.iRadiusRatio = 1.0 - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - } + NIconButton { + icon: "restore" + baseSize: Style.baseWidgetSize * 0.8 + tooltipText: I18n.tr("settings.user-interface.control-border-radius.reset") + onClicked: Settings.data.general.iRadiusRatio = 1.0 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter } } } @@ -323,21 +310,20 @@ ColumnLayout { Layout.fillWidth: true visible: !Settings.data.general.animationDisabled - NLabel { - label: I18n.tr("settings.user-interface.animation-speed.label") - description: I18n.tr("settings.user-interface.animation-speed.description") - } - RowLayout { spacing: Style.marginL Layout.fillWidth: true NValueSlider { Layout.fillWidth: true + label: I18n.tr("settings.user-interface.animation-speed.label") + description: I18n.tr("settings.user-interface.animation-speed.description") from: 0 to: 2.0 stepSize: 0.01 value: Settings.data.general.animationSpeed + isSettings: true + defaultValue: Settings.getDefaultValue("general.animationSpeed") onMoved: value => Settings.data.general.animationSpeed = Math.max(value, 0.05) text: Math.round(Settings.data.general.animationSpeed * 100) + "%" } @@ -348,7 +334,7 @@ ColumnLayout { Layout.preferredHeight: 30 * Style.uiScaleRatio NIconButton { - icon: "refresh" + icon: "restore" baseSize: Style.baseWidgetSize * 0.8 tooltipText: I18n.tr("settings.user-interface.animation-speed.reset") onClicked: Settings.data.general.animationSpeed = 1.0 @@ -363,6 +349,8 @@ ColumnLayout { label: I18n.tr("settings.user-interface.animation-disable.label") description: I18n.tr("settings.user-interface.animation-disable.description") checked: Settings.data.general.animationDisabled + isSettings: true + defaultValue: Settings.getDefaultValue("general.animationDisabled") onToggled: checked => Settings.data.general.animationDisabled = checked } } @@ -388,6 +376,8 @@ ColumnLayout { label: I18n.tr("settings.general.screen-corners.show-corners.label") description: I18n.tr("settings.general.screen-corners.show-corners.description") checked: Settings.data.general.showScreenCorners + isSettings: true + defaultValue: Settings.getDefaultValue("general.showScreenCorners") onToggled: checked => Settings.data.general.showScreenCorners = checked } @@ -395,6 +385,8 @@ ColumnLayout { label: I18n.tr("settings.general.screen-corners.solid-black.label") description: I18n.tr("settings.general.screen-corners.solid-black.description") checked: Settings.data.general.forceBlackScreenCorners + isSettings: true + defaultValue: Settings.getDefaultValue("general.forceBlackScreenCorners") onToggled: checked => Settings.data.general.forceBlackScreenCorners = checked } @@ -402,21 +394,20 @@ ColumnLayout { spacing: Style.marginXXS Layout.fillWidth: true - NLabel { - label: I18n.tr("settings.general.screen-corners.radius.label") - description: I18n.tr("settings.general.screen-corners.radius.description") - } - RowLayout { spacing: Style.marginL Layout.fillWidth: true NValueSlider { Layout.fillWidth: true + label: I18n.tr("settings.general.screen-corners.radius.label") + description: I18n.tr("settings.general.screen-corners.radius.description") from: 0 to: 2 stepSize: 0.01 value: Settings.data.general.screenRadiusRatio + isSettings: true + defaultValue: Settings.getDefaultValue("general.screenRadiusRatio") onMoved: value => Settings.data.general.screenRadiusRatio = value text: Math.floor(Settings.data.general.screenRadiusRatio * 100) + "%" } @@ -427,7 +418,7 @@ ColumnLayout { Layout.preferredHeight: 30 * Style.uiScaleRatio NIconButton { - icon: "refresh" + icon: "restore" baseSize: Style.baseWidgetSize * 0.8 tooltipText: I18n.tr("settings.general.screen-corners.radius.reset") onClicked: Settings.data.general.screenRadiusRatio = 1.0 diff --git a/Modules/Panels/Settings/Tabs/WallpaperTab.qml b/Modules/Panels/Settings/Tabs/WallpaperTab.qml index 138334f24..3020e53a0 100644 --- a/Modules/Panels/Settings/Tabs/WallpaperTab.qml +++ b/Modules/Panels/Settings/Tabs/WallpaperTab.qml @@ -237,13 +237,10 @@ ColumnLayout { // Transition Duration ColumnLayout { - NLabel { - label: I18n.tr("settings.wallpaper.look-feel.transition-duration.label") - description: I18n.tr("settings.wallpaper.look-feel.transition-duration.description") - } - NValueSlider { Layout.fillWidth: true + label: I18n.tr("settings.wallpaper.look-feel.transition-duration.label") + description: I18n.tr("settings.wallpaper.look-feel.transition-duration.description") from: 500 to: 10000 stepSize: 100 @@ -255,13 +252,10 @@ ColumnLayout { // Edge Smoothness ColumnLayout { - NLabel { - label: I18n.tr("settings.wallpaper.look-feel.edge-smoothness.label") - description: I18n.tr("settings.wallpaper.look-feel.edge-smoothness.description") - } - NValueSlider { Layout.fillWidth: true + label: I18n.tr("settings.wallpaper.look-feel.edge-smoothness.label") + description: I18n.tr("settings.wallpaper.look-feel.edge-smoothness.description") from: 0.0 to: 1.0 value: Settings.data.wallpaper.transitionEdgeSmoothness diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml index 6d0d5f255..1b2cdea12 100644 --- a/Widgets/NComboBox.qml +++ b/Widgets/NComboBox.qml @@ -15,6 +15,9 @@ RowLayout { property var model property string currentKey: "" property string placeholder: "" + property bool isSettings: false + property var defaultValue: "" + property string settingsPath: "" readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * Style.uiScaleRatio readonly property var comboBox: combo @@ -25,6 +28,71 @@ RowLayout { Layout.fillWidth: true opacity: enabled ? 1.0 : 0.6 + readonly property bool isValueChanged: isSettings && (currentKey !== defaultValue) + readonly property string indicatorTooltip: { + if (!isSettings) return ""; + var displayValue = ""; + if (defaultValue === "") { + // Try to find the display name for empty key in the model + var found = false; + if (root.model) { + if (Array.isArray(root.model)) { + for (var i = 0; i < root.model.length; i++) { + var item = root.model[i]; + if (item && item.key === "") { + displayValue = item.name || I18n.tr("settings.indicator.system-default"); + found = true; + break; + } + } + } else if (typeof root.model.get === 'function') { + for (var i = 0; i < root.itemCount(); i++) { + var item = root.getItem(i); + if (item && item.key === "") { + displayValue = item.name || I18n.tr("settings.indicator.system-default"); + found = true; + break; + } + } + } + } + // If not found in model, show "System Default" instead of "(empty)" + if (!found) { + displayValue = I18n.tr("settings.indicator.system-default"); + } + } else { + // Try to find the display name for the default key in the model + var found = false; + if (root.model) { + if (Array.isArray(root.model)) { + for (var i = 0; i < root.model.length; i++) { + var item = root.model[i]; + if (item && item.key === defaultValue) { + displayValue = item.name || String(defaultValue); + found = true; + break; + } + } + } else if (typeof root.model.get === 'function') { + for (var i = 0; i < root.itemCount(); i++) { + var item = root.getItem(i); + if (item && item.key === defaultValue) { + displayValue = item.name || String(defaultValue); + found = true; + break; + } + } + } + } + if (!found) { + displayValue = String(defaultValue); + } + } + return I18n.tr("settings.indicator.default-value", { + "value": displayValue + }); + } + function itemCount() { if (!root.model) return 0; @@ -57,6 +125,8 @@ RowLayout { NLabel { label: root.label description: root.description + showIndicator: root.isSettings && root.isValueChanged + indicatorTooltip: root.indicatorTooltip } ComboBox { @@ -234,4 +304,5 @@ RowLayout { } } } + } diff --git a/Widgets/NLabel.qml b/Widgets/NLabel.qml index 9cdc7bb9d..2f95d1849 100644 --- a/Widgets/NLabel.qml +++ b/Widgets/NLabel.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Layouts import qs.Commons +import qs.Widgets ColumnLayout { id: root @@ -9,17 +10,38 @@ ColumnLayout { property string description: "" property color labelColor: Color.mOnSurface property color descriptionColor: Color.mOnSurfaceVariant + property bool showIndicator: false + property string indicatorTooltip: "" spacing: Style.marginXXS Layout.fillWidth: true - NText { - text: label - pointSize: Style.fontSizeL - font.weight: Style.fontWeightSemiBold - color: labelColor - visible: label !== "" + RowLayout { + spacing: Style.marginXS Layout.fillWidth: true + visible: label !== "" + + NText { + text: label + pointSize: Style.fontSizeL + font.weight: Style.fontWeightSemiBold + color: labelColor + } + + // Settings indicator + Loader { + active: showIndicator + sourceComponent: indicatorComponent + } + } + + Component { + id: indicatorComponent + NSettingsIndicator { + show: true + tooltipText: root.indicatorTooltip || "" + Layout.alignment: Qt.AlignVCenter + } } NText { diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml index d55b7f251..55fc780d7 100644 --- a/Widgets/NSearchableComboBox.qml +++ b/Widgets/NSearchableComboBox.qml @@ -18,6 +18,9 @@ RowLayout { property string placeholder: "" property string searchPlaceholder: I18n.tr("placeholders.search") property Component delegate: null + property bool isSettings: false + property var defaultValue: "" + property string settingsPath: "" readonly property real preferredHeight: Style.baseWidgetSize * 1.1 @@ -26,6 +29,49 @@ RowLayout { spacing: Style.marginL Layout.fillWidth: true + readonly property bool isValueChanged: isSettings && (currentKey !== defaultValue) + readonly property string indicatorTooltip: { + if (!isSettings) return ""; + var displayValue = ""; + if (defaultValue === "") { + // Try to find the display name for empty key in the model + if (model && model.count > 0) { + for (var i = 0; i < model.count; i++) { + var item = model.get(i); + if (item && item.key === "") { + displayValue = item.name || I18n.tr("settings.indicator.system-default"); + break; + } + } + // If not found in model, show "System Default" instead of "(empty)" + if (displayValue === "") { + displayValue = I18n.tr("settings.indicator.system-default"); + } + } else { + displayValue = I18n.tr("settings.indicator.system-default"); + } + } else { + // Try to find the display name for the default key in the model + if (model && model.count > 0) { + for (var i = 0; i < model.count; i++) { + var item = model.get(i); + if (item && item.key === defaultValue) { + displayValue = item.name || String(defaultValue); + break; + } + } + if (displayValue === "") { + displayValue = String(defaultValue); + } + } else { + displayValue = String(defaultValue); + } + } + return I18n.tr("settings.indicator.default-value", { + "value": displayValue + }); + } + // Filtered model for search results property ListModel filteredModel: ListModel {} property string searchText: "" @@ -113,6 +159,8 @@ RowLayout { NLabel { label: root.label description: root.description + showIndicator: root.isSettings && root.isValueChanged + indicatorTooltip: root.indicatorTooltip } Item { @@ -339,4 +387,5 @@ RowLayout { } } } + } diff --git a/Widgets/NSettingsIndicator.qml b/Widgets/NSettingsIndicator.qml new file mode 100644 index 000000000..ae2ac5b30 --- /dev/null +++ b/Widgets/NSettingsIndicator.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Layouts +import qs.Commons +import qs.Services.UI + +Rectangle { + id: root + + property bool show: false + property string tooltipText: "" + + implicitWidth: root.show ? 6 * Style.uiScaleRatio : 0 + implicitHeight: root.show ? 6 * Style.uiScaleRatio : 0 + width: root.show ? 6 * Style.uiScaleRatio : 0 + height: root.show ? 6 * Style.uiScaleRatio : 0 + radius: width / 2 + color: Color.mOnSurfaceVariant + opacity: 0.6 + + visible: root.show + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + + MouseArea { + enabled: root.show && root.tooltipText !== "" + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + cursorShape: Qt.PointingHandCursor + + onEntered: { + if (root.tooltipText) { + TooltipService.show(root, root.tooltipText); + } + } + + onExited: { + if (root.tooltipText) { + TooltipService.hide(); + } + } + } +} + diff --git a/Widgets/NSpinBox.qml b/Widgets/NSpinBox.qml index a6424294b..65bbcbb0a 100644 --- a/Widgets/NSpinBox.qml +++ b/Widgets/NSpinBox.qml @@ -19,6 +19,9 @@ RowLayout { property bool enabled: true property bool hovering: false property int baseSize: Style.baseWidgetSize + property bool isSettings: false + property var defaultValue: 0 + property string settingsPath: "" // Convenience properties for common naming property alias minimum: root.from @@ -37,6 +40,11 @@ RowLayout { Layout.fillWidth: true + readonly property bool isValueChanged: isSettings && (value !== defaultValue) + readonly property string indicatorTooltip: isSettings ? I18n.tr("settings.indicator.default-value", { + "value": String(defaultValue) + }) : "" + Timer { id: repeatTimer repeat: true @@ -78,6 +86,8 @@ RowLayout { NLabel { label: root.label description: root.description + showIndicator: root.isSettings && root.isValueChanged + indicatorTooltip: root.indicatorTooltip } // Main spinbox container @@ -405,4 +415,5 @@ RowLayout { } } } + } diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index f036f3839..c4e54d7ad 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons +import qs.Widgets ColumnLayout { id: root @@ -16,6 +17,9 @@ ColumnLayout { property string fontFamily: Settings.data.ui.fontDefault property real fontSize: Style.fontSizeS property int fontWeight: Style.fontWeightRegular + property bool isSettings: false + property var defaultValue: "" + property string settingsPath: "" property alias text: input.text property alias placeholderText: input.placeholderText @@ -26,6 +30,11 @@ ColumnLayout { spacing: Style.marginS + readonly property bool isValueChanged: isSettings && (text !== defaultValue) + readonly property string indicatorTooltip: isSettings ? I18n.tr("settings.indicator.default-value", { + "value": defaultValue === "" ? "(empty)" : String(defaultValue) + }) : "" + NLabel { label: root.label description: root.description @@ -33,6 +42,8 @@ ColumnLayout { descriptionColor: root.descriptionColor visible: root.label !== "" || root.description !== "" Layout.fillWidth: true + showIndicator: root.isSettings && root.isValueChanged + indicatorTooltip: root.indicatorTooltip } // An active control that blocks input, to avoid events leakage and dragging stuff in the background. @@ -219,4 +230,5 @@ ColumnLayout { } } } + } diff --git a/Widgets/NToggle.qml b/Widgets/NToggle.qml index 85fb3cf3b..a0d2d0502 100644 --- a/Widgets/NToggle.qml +++ b/Widgets/NToggle.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.Commons +import qs.Widgets RowLayout { id: root @@ -12,6 +13,9 @@ RowLayout { property bool checked: false property bool hovering: false property int baseSize: Math.round(Style.baseWidgetSize * 0.8 * Style.uiScaleRatio) + property bool isSettings: false + property var defaultValue: false + property string settingsPath: "" signal toggled(bool checked) signal entered @@ -22,10 +26,17 @@ RowLayout { opacity: enabled ? 1.0 : 0.6 spacing: Style.marginM + readonly property bool isValueChanged: isSettings && (checked !== defaultValue) + readonly property string indicatorTooltip: isSettings ? I18n.tr("settings.indicator.default-value", { + "value": typeof defaultValue === "boolean" ? (defaultValue ? "true" : "false") : String(defaultValue) + }) : "" + NLabel { label: root.label description: root.description visible: root.label !== "" || root.description !== "" + showIndicator: root.isSettings && root.isValueChanged + indicatorTooltip: root.indicatorTooltip } Rectangle { @@ -96,4 +107,5 @@ RowLayout { } } } + } diff --git a/Widgets/NValueSlider.qml b/Widgets/NValueSlider.qml index 620067808..90ad7fa17 100644 --- a/Widgets/NValueSlider.qml +++ b/Widgets/NValueSlider.qml @@ -4,7 +4,7 @@ import QtQuick.Layouts import qs.Commons import qs.Widgets -RowLayout { +ColumnLayout { id: root property real from: 0 @@ -18,35 +18,73 @@ RowLayout { property real textSize: Style.fontSizeM property real customHeight: -1 property real customHeightRatio: -1 + property string label: "" + property string description: "" + property bool isSettings: false + property var defaultValue: 0 // Signals signal moved(real value) signal pressedChanged(bool pressed, real value) - spacing: Style.marginL - implicitHeight: root.customHeight > 0 ? root.customHeight : slider.implicitHeight + spacing: Style.marginS + Layout.fillWidth: true - NSlider { - id: slider - Layout.fillWidth: true - from: root.from - to: root.to - value: root.value - stepSize: root.stepSize - cutoutColor: root.cutoutColor - snapAlways: root.snapAlways - heightRatio: root.customHeightRatio > 0 ? root.customHeightRatio : root.heightRatio - onMoved: root.moved(value) - onPressedChanged: root.pressedChanged(pressed, value) + readonly property bool isValueChanged: isSettings && (value !== defaultValue) + readonly property string indicatorTooltip: { + if (!isSettings) return ""; + var defaultVal = defaultValue; + if (typeof defaultVal === "number") { + // If it's a decimal between 0 and 1, format as percentage + if (defaultVal > 0 && defaultVal <= 1 && from >= 0 && from < 1) { + return I18n.tr("settings.indicator.default-value", { + "value": Math.floor(defaultVal * 100) + "%" + }); + } + return I18n.tr("settings.indicator.default-value", { + "value": String(defaultVal) + }); + } + return I18n.tr("settings.indicator.default-value", { + "value": String(defaultVal) + }); } - NText { - visible: root.text !== "" - text: root.text - pointSize: root.textSize - family: Settings.data.ui.fontFixed - Layout.alignment: Qt.AlignVCenter - Layout.preferredWidth: 45 * Style.uiScaleRatio - horizontalAlignment: Text.AlignRight + NLabel { + label: root.label + description: root.description + visible: root.label !== "" || root.description !== "" + showIndicator: root.isSettings && root.isValueChanged + indicatorTooltip: root.indicatorTooltip + Layout.fillWidth: true + } + + RowLayout { + spacing: Style.marginL + Layout.fillWidth: true + + NSlider { + id: slider + Layout.fillWidth: true + from: root.from + to: root.to + value: root.value + stepSize: root.stepSize + cutoutColor: root.cutoutColor + snapAlways: root.snapAlways + heightRatio: root.customHeightRatio > 0 ? root.customHeightRatio : root.heightRatio + onMoved: root.moved(value) + onPressedChanged: root.pressedChanged(pressed, value) + } + + NText { + visible: root.text !== "" + text: root.text + pointSize: root.textSize + family: Settings.data.ui.fontFixed + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: 45 * Style.uiScaleRatio + horizontalAlignment: Text.AlignRight + } } }