From 8b561bccd4758dfc14ac06ad76024f90644de806 Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Tue, 7 Oct 2025 13:01:56 -0400 Subject: [PATCH 001/106] feat: Merge duplicate MPRIS players when browser dual report as their base suchas Librewolf as Firefox --- Services/MediaService.qml | 178 +++++++++++++++++++++++++------------- 1 file changed, 117 insertions(+), 61 deletions(-) diff --git a/Services/MediaService.qml b/Services/MediaService.qml index 4a6e0b68..97b59e53 100644 --- a/Services/MediaService.qml +++ b/Services/MediaService.qml @@ -32,67 +32,127 @@ Singleton { function getAvailablePlayers() { if (!Mpris.players || !Mpris.players.values) { - return [] + return []; } - let allPlayers = Mpris.players.values - let controllablePlayers = [] + let allPlayers = Mpris.players.values; + let finalPlayers = []; + const genericBrowsers = ["firefox", "chromium", "chrome"]; - // Apply blacklist and controllable filter - const blacklist = (Settings.data.audio && Settings.data.audio.mprisBlacklist) ? Settings.data.audio.mprisBlacklist : [] + // 1. Separate players into specific and generic lists + let specificPlayers = []; + let genericPlayers = []; for (var i = 0; i < allPlayers.length; i++) { - let player = allPlayers[i] - if (!player) - continue - const identity = String(player.identity || "") - const busName = String(player.busName || "") - const desktop = String(player.desktopEntry || "") - const idKey = identity.toLowerCase() - const match = blacklist.find(b => { - const s = String(b || "").toLowerCase() - return s && (idKey.includes(s) || busName.toLowerCase().includes(s) || desktop.toLowerCase().includes(s)) - }) - if (match) - continue - if (player.canControl) - controllablePlayers.push(player) + const identity = String(allPlayers[i].identity || "").toLowerCase(); + if (genericBrowsers.some(b => identity.includes(b))) { + genericPlayers.push(allPlayers[i]); + } else { + specificPlayers.push(allPlayers[i]); + } } - return controllablePlayers + let matchedGenericIndices = {}; + + // 2. For each specific player, try to find and pair it with a generic partner + for (var i = 0; i < specificPlayers.length; i++) { + let specificPlayer = specificPlayers[i]; + let title1 = String(specificPlayer.trackTitle || "").trim(); + let wasMatched = false; + + if (title1) { + for (var j = 0; j < genericPlayers.length; j++) { + if (matchedGenericIndices[j]) continue; + let genericPlayer = genericPlayers[j]; + let title2 = String(genericPlayer.trackTitle || "").trim(); + + if (title2 && (title1.includes(title2) || title2.includes(title1))) { + let dataPlayer = genericPlayer; + let identityPlayer = specificPlayer; + + let scoreSpecific = (specificPlayer.trackArtUrl ? 1 : 0); + let scoreGeneric = (genericPlayer.trackArtUrl ? 1 : 0); + if(scoreSpecific > scoreGeneric){ dataPlayer = specificPlayer; } + + let virtualPlayer = { + identity: identityPlayer.identity, + desktopEntry: identityPlayer.desktopEntry, + trackTitle: dataPlayer.trackTitle, + trackArtist: dataPlayer.trackArtist, + trackAlbum: dataPlayer.trackAlbum, + trackArtUrl: dataPlayer.trackArtUrl, + length: dataPlayer.length || 0, + position: dataPlayer.position || 0, + playbackState: dataPlayer.playbackState, + isPlaying: dataPlayer.isPlaying || false, + canPlay: dataPlayer.canPlay || false, + canPause: dataPlayer.canPause || false, + canGoNext: dataPlayer.canGoNext || false, + canGoPrevious: dataPlayer.canGoPrevious || false, + canSeek: dataPlayer.canSeek || false, + canControl: dataPlayer.canControl || false, + _stateSource: dataPlayer, + _controlTarget: identityPlayer + }; + finalPlayers.push(virtualPlayer); + matchedGenericIndices[j] = true; + wasMatched = true; + break; + } + } + } + if (!wasMatched) { + finalPlayers.push(specificPlayer); + } + } + + // 3. Add any generic players that were not matched + for (var i = 0; i < genericPlayers.length; i++) { + if (!matchedGenericIndices[i]) { + finalPlayers.push(genericPlayers[i]); + } + } + + // 4. Filter for controllable players + let controllablePlayers = []; + for (var i = 0; i < finalPlayers.length; i++) { + let player = finalPlayers[i]; + if (player && player.canControl) { + controllablePlayers.push(player); + } + } + return controllablePlayers; } function findActivePlayer() { let availablePlayers = getAvailablePlayers() if (availablePlayers.length === 0) { + Logger.log("Media", "No active player found") return null } - // First, check if any player is currently playing + // --- NEW: Prioritize the actively playing player --- for (var i = 0; i < availablePlayers.length; i++) { - const p = availablePlayers[i] - if (p.isPlaying && p.playbackState === MprisPlaybackState.Playing) { - selectedPlayerIndex = i - return p + if (availablePlayers[i] && availablePlayers[i].playbackState === MprisPlaybackState.Playing) { + Logger.log("Media", "Found actively playing player: " + availablePlayers[i].identity); + selectedPlayerIndex = i; + return availablePlayers[i]; } } - // If no player is playing, use preferred player logic + // --- OLD LOGIC (used as a fallback if nothing is playing) --- const preferred = (Settings.data.audio.preferredPlayer || "") if (preferred !== "") { for (var i = 0; i < availablePlayers.length; i++) { const p = availablePlayers[i] const identity = String(p.identity || "").toLowerCase() - const busName = String(p.busName || "").toLowerCase() - const desktop = String(p.desktopEntry || "").toLowerCase() const pref = preferred.toLowerCase() - if (identity.includes(pref) || busName.includes(pref) || desktop.includes(pref)) { + if (identity.includes(pref)) { selectedPlayerIndex = i return p } } } - // Fallback to selected index or first player if (selectedPlayerIndex < availablePlayers.length) { return availablePlayers[selectedPlayerIndex] } else { @@ -107,46 +167,54 @@ Singleton { if (newPlayer !== currentPlayer) { currentPlayer = newPlayer currentPosition = currentPlayer ? currentPlayer.position : 0 + Logger.log("Media", "Switching player") } } function playPause() { if (currentPlayer) { - if (currentPlayer.isPlaying) { - currentPlayer.pause() + let stateSource = currentPlayer._stateSource || currentPlayer + let controlTarget = currentPlayer._controlTarget || currentPlayer + if (stateSource.playbackState === MprisPlaybackState.Playing) { + controlTarget.pause() } else { - currentPlayer.play() + controlTarget.play() } } } function play() { - if (currentPlayer && currentPlayer.canPlay) { - currentPlayer.play() + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null + if (target && target.canPlay) { + target.play() } } function pause() { - if (currentPlayer && currentPlayer.canPause) { - currentPlayer.pause() + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null + if (target && target.canPause) { + target.pause() } } function next() { - if (currentPlayer && currentPlayer.canGoNext) { - currentPlayer.next() + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null + if (target && target.canGoNext) { + target.next() } } function previous() { - if (currentPlayer && currentPlayer.canGoPrevious) { - currentPlayer.previous() + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null + if (target && target.canGoPrevious) { + target.previous() } } function seek(position) { - if (currentPlayer && currentPlayer.canSeek) { - currentPlayer.position = position + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null + if (target && target.canSeek) { + target.position = position currentPosition = position } } @@ -161,9 +229,10 @@ Singleton { // Seek to position based on ratio (0.0 to 1.0) function seekByRatio(ratio) { - if (currentPlayer && currentPlayer.canSeek && currentPlayer.length > 0) { - let seekPosition = ratio * currentPlayer.length - currentPlayer.position = seekPosition + let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null + if (target && target.canSeek && target.length > 0) { + let seekPosition = ratio * target.length + target.position = seekPosition currentPosition = seekPosition } } @@ -209,21 +278,8 @@ Singleton { Connections { target: Mpris.players function onValuesChanged() { + Logger.log("Media", "Players changed") updateCurrentPlayer() } } - - // Monitor playback state changes across all players to switch to playing ones - Timer { - id: playerStateMonitor - interval: 2000 // Check every 2 seconds - repeat: true - running: true - onTriggered: { - // Only update if we don't have a playing player or if current player is paused - if (!currentPlayer || !currentPlayer.isPlaying || currentPlayer.playbackState !== MprisPlaybackState.Playing) { - updateCurrentPlayer() - } - } - } } From 668a94d4df2ea681f48a5307d566f0cbd8e080f8 Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Tue, 7 Oct 2025 23:43:39 -0400 Subject: [PATCH 002/106] Did some cleanup --- Services/MediaService.qml | 90 ++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/Services/MediaService.qml b/Services/MediaService.qml index 97b59e53..52417b43 100644 --- a/Services/MediaService.qml +++ b/Services/MediaService.qml @@ -32,46 +32,46 @@ Singleton { function getAvailablePlayers() { if (!Mpris.players || !Mpris.players.values) { - return []; + return [] } - let allPlayers = Mpris.players.values; - let finalPlayers = []; - const genericBrowsers = ["firefox", "chromium", "chrome"]; + let allPlayers = Mpris.players.values + let finalPlayers = [] + const genericBrowsers = ["firefox", "chromium", "chrome"] - // 1. Separate players into specific and generic lists - let specificPlayers = []; - let genericPlayers = []; + // Separate players into specific and generic lists + let specificPlayers = [] + let genericPlayers = [] for (var i = 0; i < allPlayers.length; i++) { - const identity = String(allPlayers[i].identity || "").toLowerCase(); + const identity = String(allPlayers[i].identity || "").toLowerCase() if (genericBrowsers.some(b => identity.includes(b))) { - genericPlayers.push(allPlayers[i]); + genericPlayers.push(allPlayers[i]) } else { - specificPlayers.push(allPlayers[i]); + specificPlayers.push(allPlayers[i]) } } - let matchedGenericIndices = {}; + let matchedGenericIndices = {} - // 2. For each specific player, try to find and pair it with a generic partner + // For each specific player, try to find and pair it with a generic partner for (var i = 0; i < specificPlayers.length; i++) { - let specificPlayer = specificPlayers[i]; - let title1 = String(specificPlayer.trackTitle || "").trim(); - let wasMatched = false; + let specificPlayer = specificPlayers[i] + let title1 = String(specificPlayer.trackTitle || "").trim() + let wasMatched = false if (title1) { for (var j = 0; j < genericPlayers.length; j++) { - if (matchedGenericIndices[j]) continue; - let genericPlayer = genericPlayers[j]; - let title2 = String(genericPlayer.trackTitle || "").trim(); + if (matchedGenericIndices[j]) continue + let genericPlayer = genericPlayers[j] + let title2 = String(genericPlayer.trackTitle || "").trim() if (title2 && (title1.includes(title2) || title2.includes(title1))) { - let dataPlayer = genericPlayer; - let identityPlayer = specificPlayer; + let dataPlayer = genericPlayer + let identityPlayer = specificPlayer - let scoreSpecific = (specificPlayer.trackArtUrl ? 1 : 0); - let scoreGeneric = (genericPlayer.trackArtUrl ? 1 : 0); - if(scoreSpecific > scoreGeneric){ dataPlayer = specificPlayer; } + let scoreSpecific = (specificPlayer.trackArtUrl ? 1 : 0) + let scoreGeneric = (genericPlayer.trackArtUrl ? 1 : 0) + if(scoreSpecific > scoreGeneric){ dataPlayer = specificPlayer } let virtualPlayer = { identity: identityPlayer.identity, @@ -92,35 +92,35 @@ Singleton { canControl: dataPlayer.canControl || false, _stateSource: dataPlayer, _controlTarget: identityPlayer - }; - finalPlayers.push(virtualPlayer); - matchedGenericIndices[j] = true; - wasMatched = true; - break; + } + finalPlayers.push(virtualPlayer) + matchedGenericIndices[j] = true + wasMatched = true + break } } } if (!wasMatched) { - finalPlayers.push(specificPlayer); + finalPlayers.push(specificPlayer) } } - // 3. Add any generic players that were not matched + // Add any generic players that were not matched for (var i = 0; i < genericPlayers.length; i++) { if (!matchedGenericIndices[i]) { - finalPlayers.push(genericPlayers[i]); + finalPlayers.push(genericPlayers[i]) } } - // 4. Filter for controllable players - let controllablePlayers = []; + // Filter for controllable players + let controllablePlayers = [] for (var i = 0; i < finalPlayers.length; i++) { - let player = finalPlayers[i]; + let player = finalPlayers[i] if (player && player.canControl) { - controllablePlayers.push(player); + controllablePlayers.push(player) } } - return controllablePlayers; + return controllablePlayers } function findActivePlayer() { @@ -130,16 +130,16 @@ Singleton { return null } - // --- NEW: Prioritize the actively playing player --- + // Prioritize the actively playing player --- for (var i = 0; i < availablePlayers.length; i++) { if (availablePlayers[i] && availablePlayers[i].playbackState === MprisPlaybackState.Playing) { - Logger.log("Media", "Found actively playing player: " + availablePlayers[i].identity); - selectedPlayerIndex = i; - return availablePlayers[i]; + Logger.log("Media", "Found actively playing player: " + availablePlayers[i].identity) + selectedPlayerIndex = i + return availablePlayers[i] } } - // --- OLD LOGIC (used as a fallback if nothing is playing) --- + // fallback if nothing is playing) const preferred = (Settings.data.audio.preferredPlayer || "") if (preferred !== "") { for (var i = 0; i < availablePlayers.length; i++) { @@ -219,14 +219,6 @@ Singleton { } } - function seekRelative(offset) { - if (currentPlayer && currentPlayer.canSeek) { - var newPosition = currentPlayer.position + offset - currentPlayer.position = newPosition - currentPosition = newPosition - } - } - // Seek to position based on ratio (0.0 to 1.0) function seekByRatio(ratio) { let target = currentPlayer ? (currentPlayer._controlTarget || currentPlayer) : null From 6f0d076d80b3463519a277665fb3c487e70a26db Mon Sep 17 00:00:00 2001 From: Kainoa Kanter Date: Wed, 8 Oct 2025 17:46:45 -0700 Subject: [PATCH 003/106] spaces in uptime --- Commons/Time.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Commons/Time.qml b/Commons/Time.qml index 9c01cc96..16dbfdca 100644 --- a/Commons/Time.qml +++ b/Commons/Time.qml @@ -41,7 +41,7 @@ Singleton { return `${year}${month}${day}-${hours}${minutes}${seconds}` } - // Format an easy to read approximate duration ex: 4h32m + // Format an easy to read approximate duration ex: 4h 32m // Used to display the time remaining on the Battery widget, computer uptime, etc.. function formatVagueHumanReadableDuration(totalSeconds) { if (typeof totalSeconds !== 'number' || totalSeconds < 0) { @@ -69,7 +69,7 @@ Singleton { parts.push(`${seconds}s`) } - return parts.join('') + return parts.join(' ') } // Format a date into From bff195309ac4fbb1ca6ece6ddcbe47a88675f466 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Wed, 8 Oct 2025 23:05:52 -0400 Subject: [PATCH 004/106] QuickSettings: editable widgets/button section in the ControlCenter --- Commons/Settings.qml | 10 + Modules/Bar/Extras/BarWidgetLoader.qml | 2 +- Modules/ControlCenter/Cards/TopCard.qml | 220 ++---------------- Modules/ControlCenter/Cards/WeatherCard.qml | 130 ----------- Modules/ControlCenter/ControlCenterPanel.qml | 18 +- .../Extras/ControlCenterWidgetLoader.qml | 74 ++++++ Modules/ControlCenter/Widgets/Bluetooth.qml | 17 ++ Modules/ControlCenter/Widgets/KeepAwake.qml | 17 ++ Modules/ControlCenter/Widgets/NightLight.qml | 33 +++ .../ControlCenter/Widgets/PowerProfile.qml | 23 ++ .../ControlCenter/Widgets/ScreenRecorder.qml | 24 ++ .../Widgets/WallpaperSelector.qml | 20 ++ Modules/ControlCenter/Widgets/WiFi.qml | 42 ++++ .../SectionEditor.qml} | 21 +- Modules/Settings/SettingsPanel.qml | 10 + Modules/Settings/Tabs/BarTab.qml | 14 +- Modules/Settings/Tabs/ControlCenterTab.qml | 140 +++++++++++ Services/ControlCenterWidgetRegistry.qml | 71 ++++++ Widgets/NButton.qml | 13 +- 19 files changed, 542 insertions(+), 357 deletions(-) delete mode 100644 Modules/ControlCenter/Cards/WeatherCard.qml create mode 100644 Modules/ControlCenter/Extras/ControlCenterWidgetLoader.qml create mode 100644 Modules/ControlCenter/Widgets/Bluetooth.qml create mode 100644 Modules/ControlCenter/Widgets/KeepAwake.qml create mode 100644 Modules/ControlCenter/Widgets/NightLight.qml create mode 100644 Modules/ControlCenter/Widgets/PowerProfile.qml create mode 100644 Modules/ControlCenter/Widgets/ScreenRecorder.qml create mode 100644 Modules/ControlCenter/Widgets/WallpaperSelector.qml create mode 100644 Modules/ControlCenter/Widgets/WiFi.qml rename Modules/Settings/{Bar/BarSectionEditor.qml => Extras/SectionEditor.qml} (96%) create mode 100644 Modules/Settings/Tabs/ControlCenterTab.qml create mode 100644 Services/ControlCenterWidgetRegistry.qml diff --git a/Commons/Settings.qml b/Commons/Settings.qml index c0939caf..61b4d184 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -246,6 +246,16 @@ Singleton { property JsonObject controlCenter: JsonObject { // Position: close_to_bar_button, center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center property string position: "close_to_bar_button" + property JsonObject widgets + widgets: JsonObject { + property list quickSettings: [{ + "id": "Bluetooth" + }, { + "id": "WiFi" + }, { + "id": "PowerProfile" + }] + } } // dock diff --git a/Modules/Bar/Extras/BarWidgetLoader.qml b/Modules/Bar/Extras/BarWidgetLoader.qml index 8697db5b..69b08623 100644 --- a/Modules/Bar/Extras/BarWidgetLoader.qml +++ b/Modules/Bar/Extras/BarWidgetLoader.qml @@ -77,7 +77,7 @@ Item { // Error handling onWidgetIdChanged: { if (widgetId && !BarWidgetRegistry.hasWidget(widgetId)) { - Logger.warn("BarWidgetLoader", "Widget not found in bar registry:", widgetId) + Logger.warn("BarWidgetLoader", "Widget not found in registry:", widgetId) } } } diff --git a/Modules/ControlCenter/Cards/TopCard.qml b/Modules/ControlCenter/Cards/TopCard.qml index d68d84be..1bb36148 100644 --- a/Modules/ControlCenter/Cards/TopCard.qml +++ b/Modules/ControlCenter/Cards/TopCard.qml @@ -4,9 +4,9 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Widgets -import Quickshell.Services.UPower import qs.Modules.Settings import qs.Modules.ControlCenter +import qs.Modules.ControlCenter.Extras import qs.Commons import qs.Services import qs.Widgets @@ -17,7 +17,6 @@ NBox { property string uptimeText: "--" property real spacing: Style.marginS * scaling - readonly property bool hasPP: PowerProfileService.available ColumnLayout { anchors.fill: parent @@ -97,205 +96,34 @@ NBox { } } - RowLayout { - id: utilitiesRow - Layout.alignment: Qt.AlignVCenter + NDivider { + Layout.fillWidth: true Layout.topMargin: Style.marginM * scaling Layout.bottomMargin: Style.marginM * scaling + } + + GridLayout { + id: grid Layout.fillWidth: true + columns: 2 + columnSpacing: Style.marginL * scaling + rowSpacing: Style.marginM * scaling - // Left group - Media & Display - Rectangle { - color: Color.mSurface - radius: Style.radiusM * scaling - Layout.preferredHeight: Style.baseWidgetSize * 1.2 * scaling - Layout.preferredWidth: childrenRect.width + (Style.marginS * scaling * 2) - - RowLayout { - anchors.centerIn: parent - spacing: Style.marginM * scaling - - // Screen Recorder - NIconButton { - baseSize: Style.baseWidgetSize * 0.9 - icon: "camera-video" - visible: ProgramCheckerService.gpuScreenRecorderAvailable - tooltipText: ScreenRecorderService.isRecording ? I18n.tr("tooltips.stop-screen-recording") : I18n.tr("tooltips.start-screen-recording") - colorBg: ScreenRecorderService.isRecording ? Color.mPrimary : Color.mSurfaceVariant - colorFg: ScreenRecorderService.isRecording ? Color.mOnPrimary : Color.mPrimary - onClicked: { - ScreenRecorderService.toggleRecording() - if (!ScreenRecorderService.isRecording) { - var panel = PanelService.getPanel("controlCenterPanel") - panel?.close() - } - } - } - - // Wallpaper - NIconButton { - baseSize: Style.baseWidgetSize * 0.9 - visible: Settings.data.wallpaper.enabled - icon: "wallpaper-selector" - tooltipText: I18n.tr("tooltips.wallpaper-selector") - onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this) - onRightClicked: WallpaperService.setRandomWallpaper() - } - - // Night Light - NIconButton { - baseSize: Style.baseWidgetSize * 0.9 - visible: ProgramCheckerService.wlsunsetAvailable - colorBg: Settings.data.nightLight.forced ? Color.mPrimary : Color.transparent - colorFg: Settings.data.nightLight.forced ? Color.mOnPrimary : Color.mPrimary - icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off" - tooltipText: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? I18n.tr("tooltips.night-light-forced") : I18n.tr("tooltips.night-light-enabled")) : I18n.tr("tooltips.night-light-disabled") - onClicked: { - if (!Settings.data.nightLight.enabled) { - Settings.data.nightLight.enabled = true - Settings.data.nightLight.forced = false - } else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) { - Settings.data.nightLight.forced = true - } else { - Settings.data.nightLight.enabled = false - Settings.data.nightLight.forced = false - } - } - - onRightClicked: { - var settingsPanel = PanelService.getPanel("settingsPanel") - settingsPanel.requestedTab = SettingsPanel.Tab.Display - settingsPanel.open() - } - } - } - } - - // Spacer - Item { - Layout.fillWidth: true - } - - // Center group - Network & Caffeine - Rectangle { - color: Color.mSurface - radius: Style.radiusM * scaling - Layout.preferredHeight: Style.baseWidgetSize * 1.2 * scaling - Layout.preferredWidth: childrenRect.width + (Style.marginS * scaling * 2) - - RowLayout { - anchors.centerIn: parent - spacing: Style.marginM * scaling - - // Wifi - NIconButton { - id: wifiButton - baseSize: Style.baseWidgetSize * 0.9 - tooltipText: I18n.tr("tooltips.manage-wifi") - icon: { - try { - if (NetworkService.ethernetConnected) { - return "ethernet" - } - let connected = false - let signalStrength = 0 - for (const net in NetworkService.networks) { - if (NetworkService.networks[net].connected) { - connected = true - signalStrength = NetworkService.networks[net].signal - break - } - } - return connected ? NetworkService.signalIcon(signalStrength) : "wifi-off" - } catch (error) { - Logger.error("Wi-Fi", "Error getting icon:", error) - return "signal_wifi_bad" - } - } - onClicked: PanelService.getPanel("wifiPanel")?.toggle(this) - onRightClicked: PanelService.getPanel("wifiPanel")?.toggle(this) - } - - // Bluetooth - NIconButton { - baseSize: Style.baseWidgetSize * 0.9 - tooltipText: I18n.tr("tooltips.bluetooth-devices") - icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off" - onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this) - onRightClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this) - } - - // Caffeine (Keep Awake) - NIconButton { - baseSize: Style.baseWidgetSize * 0.9 - icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off" - tooltipText: IdleInhibitorService.isInhibited ? I18n.tr("tooltips.disable-keep-awake") : I18n.tr("tooltips.enable-keep-awake") - colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mSurfaceVariant - colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mPrimary - onClicked: { - IdleInhibitorService.manualToggle() - } - } - } - } - - // Spacer - Item { - Layout.fillWidth: true - } - - // Right group - Power Profiles - Rectangle { - color: Color.mSurface - radius: Style.radiusM * scaling - Layout.preferredHeight: Style.baseWidgetSize * 1.2 * scaling - Layout.preferredWidth: childrenRect.width + (Style.marginS * scaling * 2) - - RowLayout { - anchors.centerIn: parent - spacing: Style.marginM * scaling - - // Performance - NIconButton { - baseSize: Style.baseWidgetSize * 0.9 - icon: PowerProfileService.getIcon(PowerProfile.Performance) - tooltipText: I18n.tr("tooltips.set-power-profile", { - "profile": PowerProfileService.getName(PowerProfile.Performance) - }) - enabled: hasPP - opacity: enabled ? Style.opacityFull : Style.opacityMedium - colorBg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mPrimary : Color.mSurfaceVariant - colorFg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mOnPrimary : Color.mPrimary - onClicked: PowerProfileService.setProfile(PowerProfile.Performance) - } - - // Balanced - NIconButton { - baseSize: Style.baseWidgetSize * 0.9 - icon: PowerProfileService.getIcon(PowerProfile.Balanced) - tooltipText: I18n.tr("tooltips.set-power-profile", { - "profile": PowerProfileService.getName(PowerProfile.Balanced) - }) - enabled: hasPP - opacity: enabled ? Style.opacityFull : Style.opacityMedium - colorBg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mPrimary : Color.mSurfaceVariant - colorFg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnPrimary : Color.mPrimary - onClicked: PowerProfileService.setProfile(PowerProfile.Balanced) - } - - // Eco - NIconButton { - baseSize: Style.baseWidgetSize * 0.9 - icon: PowerProfileService.getIcon(PowerProfile.PowerSaver) - tooltipText: I18n.tr("tooltips.set-power-profile", { - "profile": PowerProfileService.getName(PowerProfile.PowerSaver) - }) - enabled: hasPP - opacity: enabled ? Style.opacityFull : Style.opacityMedium - colorBg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mPrimary : Color.mSurfaceVariant - colorFg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mOnPrimary : Color.mPrimary - onClicked: PowerProfileService.setProfile(PowerProfile.PowerSaver) + Repeater { + model: Settings.data.controlCenter.widgets.quickSettings + delegate: ControlCenterWidgetLoader { + Layout.fillWidth: true + Layout.preferredWidth: (grid.width - grid.columnSpacing) / 2 + widgetId: (modelData.id !== undefined ? modelData.id : "") + widgetProps: { + "screen": root.modelData || null, + "scaling": ScalingService.getScreenScale(screen), + "widgetId": modelData.id, + "section": "quickSettings", + "sectionWidgetIndex": index, + "sectionWidgetsCount": Settings.data.controlCenter.widgets.quickSettings.length } + Layout.alignment: Qt.AlignVCenter } } } diff --git a/Modules/ControlCenter/Cards/WeatherCard.qml b/Modules/ControlCenter/Cards/WeatherCard.qml deleted file mode 100644 index d9e8510a..00000000 --- a/Modules/ControlCenter/Cards/WeatherCard.qml +++ /dev/null @@ -1,130 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import Quickshell -import qs.Commons -import qs.Services -import qs.Widgets - -// Weather overview card (placeholder data) -NBox { - id: root - - readonly property bool weatherReady: (LocationService.data.weather !== null) - - ColumnLayout { - id: content - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: Style.marginM * scaling - spacing: Style.marginM * scaling - clip: true - - RowLayout { - spacing: Style.marginS * scaling - NIcon { - Layout.alignment: Qt.AlignVCenter - icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : "" - pointSize: Style.fontSizeXXXL * 1.75 * scaling - color: Color.mPrimary - } - - ColumnLayout { - spacing: Style.marginXXS * scaling - NText { - text: { - // Ensure the name is not too long if one had to specify the country - const chunks = Settings.data.location.name.split(",") - return chunks[0] - } - pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - } - - RowLayout { - NText { - visible: weatherReady - text: { - if (!weatherReady) { - return "" - } - var temp = LocationService.data.weather.current_weather.temperature - var suffix = "C" - if (Settings.data.location.useFahrenheit) { - temp = LocationService.celsiusToFahrenheit(temp) - var suffix = "F" - } - temp = Math.round(temp) - return `${temp}°${suffix}` - } - pointSize: Style.fontSizeXL * scaling - font.weight: Style.fontWeightBold - } - - NText { - text: weatherReady ? `(${LocationService.data.weather.timezone_abbreviation})` : "" - pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - visible: LocationService.data.weather - } - } - } - } - - NDivider { - visible: weatherReady - Layout.fillWidth: true - } - - RowLayout { - visible: weatherReady - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - spacing: Style.marginL * scaling - Repeater { - model: weatherReady ? LocationService.data.weather.daily.time : [] - delegate: ColumnLayout { - Layout.alignment: Qt.AlignHCenter - spacing: Style.marginS * scaling - NText { - text: { - var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")) - return Qt.locale().toString(weatherDate, "ddd") - } - color: Color.mOnSurface - Layout.alignment: Qt.AlignHCenter - } - NIcon { - Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter - icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index]) - pointSize: Style.fontSizeXXL * 1.6 * scaling - color: Color.mPrimary - } - NText { - Layout.alignment: Qt.AlignHCenter - text: { - var max = LocationService.data.weather.daily.temperature_2m_max[index] - var min = LocationService.data.weather.daily.temperature_2m_min[index] - if (Settings.data.location.useFahrenheit) { - max = LocationService.celsiusToFahrenheit(max) - min = LocationService.celsiusToFahrenheit(min) - } - max = Math.round(max) - min = Math.round(min) - return `${max}°/${min}°` - } - pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - } - } - } - } - - RowLayout { - visible: !weatherReady - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter - NBusyIndicator {} - } - } -} diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index c2290cc0..e47c593c 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -10,8 +10,8 @@ import qs.Widgets NPanel { id: root - preferredWidth: 480 - preferredHeight: 580 + preferredWidth: 440 + preferredHeight: 540 panelKeyboardFocus: true // Positioning @@ -38,13 +38,7 @@ NPanel { // Top Card: profile + utilities TopCard { Layout.fillWidth: true - Layout.preferredHeight: Math.max(124 * scaling) - } - - // Weather - WeatherCard { - Layout.fillWidth: true - Layout.preferredHeight: Math.max(196 * scaling) + Layout.preferredHeight: Math.max(280 * scaling) } // Media + stats column @@ -55,13 +49,13 @@ NPanel { // Media card MediaCard { - Layout.preferredWidth: Math.max(270 * scaling) - Layout.fillHeight: true + Layout.preferredWidth: Math.max(250 * scaling) + Layout.preferredHeight: Math.max(196 * scaling) } // System monitors combined in one card SystemMonitorCard { - Layout.preferredWidth: Math.max(160 * scaling) + Layout.preferredWidth: Math.max(140 * scaling) Layout.preferredHeight: Math.max(196 * scaling) } } diff --git a/Modules/ControlCenter/Extras/ControlCenterWidgetLoader.qml b/Modules/ControlCenter/Extras/ControlCenterWidgetLoader.qml new file mode 100644 index 00000000..4e7577ee --- /dev/null +++ b/Modules/ControlCenter/Extras/ControlCenterWidgetLoader.qml @@ -0,0 +1,74 @@ +import QtQuick +import Quickshell +import qs.Services +import qs.Commons + +Item { + id: root + + property string widgetId: "" + property var widgetProps: ({}) + property string screenName: widgetProps && widgetProps.screen ? widgetProps.screen.name : "" + property string section: widgetProps && widgetProps.section || "" + property int sectionIndex: widgetProps && widgetProps.sectionWidgetIndex || 0 + + // Don't reserve space unless the loaded widget is really visible + implicitWidth: getImplicitSize(loader.item, "implicitWidth") + implicitHeight: getImplicitSize(loader.item, "implicitHeight") + + Connections { + target: ScalingService + enabled: loader.item && (loader.item.screen !== undefined) + function onScaleChanged(aScreenName, scale) { + if (loader.item && loader.item.screen && aScreenName === screenName) { + loader.item['scaling'] = scale + } + } + } + + function getImplicitSize(item, prop) { + return (item && item.visible) ? item[prop] : 0 + } + + Loader { + id: loader + anchors.fill: parent + active: widgetId !== "" + asynchronous: false + sourceComponent: { + if (!active) { + return null + } + return ControlCenterWidgetRegistry.getWidget(widgetId) + } + + onLoaded: { + if (item && widgetProps) { + // Apply properties to loaded widget + for (var prop in widgetProps) { + if (item.hasOwnProperty(prop)) { + item[prop] = widgetProps[prop] + } + } + } + + if (item.hasOwnProperty("onLoaded")) { + item.onLoaded() + } + + //Logger.log("ControlCenterWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name) + } + + Component.onDestruction: { + // Explicitly clear references + widgetProps = null + } + } + + // Error handling + onWidgetIdChanged: { + if (widgetId && !ControlCenterWidgetRegistry.hasWidget(widgetId)) { + Logger.warn("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId) + } + } +} diff --git a/Modules/ControlCenter/Widgets/Bluetooth.qml b/Modules/ControlCenter/Widgets/Bluetooth.qml new file mode 100644 index 00000000..7129ba64 --- /dev/null +++ b/Modules/ControlCenter/Widgets/Bluetooth.qml @@ -0,0 +1,17 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +NButton { + property ShellScreen screen + property real scaling: 1.0 + + outlined: true + text: "Bluetooth" + fontSize: Style.fontSizeS * scaling + fontWeight: Style.fontWeightRegular + icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off" + onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this) +} diff --git a/Modules/ControlCenter/Widgets/KeepAwake.qml b/Modules/ControlCenter/Widgets/KeepAwake.qml new file mode 100644 index 00000000..9840c5b3 --- /dev/null +++ b/Modules/ControlCenter/Widgets/KeepAwake.qml @@ -0,0 +1,17 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +NButton { + property ShellScreen screen + property real scaling: 1.0 + + outlined: true + text: IdleInhibitorService.isInhibited ? "Keep-awake" : "Keep-awake" + fontSize: Style.fontSizeS * scaling + fontWeight: Style.fontWeightRegular + icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off" + onClicked: IdleInhibitorService.manualToggle() +} diff --git a/Modules/ControlCenter/Widgets/NightLight.qml b/Modules/ControlCenter/Widgets/NightLight.qml new file mode 100644 index 00000000..841cc560 --- /dev/null +++ b/Modules/ControlCenter/Widgets/NightLight.qml @@ -0,0 +1,33 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +NButton { + property ShellScreen screen + property real scaling: 1.0 + + outlined: true + enabled: ProgramCheckerService.wlsunsetAvailable + text: "Night Light" + fontSize: Style.fontSizeS * scaling + fontWeight: Style.fontWeightRegular + icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off" + onClicked: { + if (!Settings.data.nightLight.enabled) { + Settings.data.nightLight.enabled = true + Settings.data.nightLight.forced = false + } else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) { + Settings.data.nightLight.forced = true + } else { + Settings.data.nightLight.enabled = false + Settings.data.nightLight.forced = false + } + } + onRightClicked: { + var settingsPanel = PanelService.getPanel("settingsPanel") + settingsPanel.requestedTab = SettingsPanel.Tab.Display + settingsPanel.open() + } +} diff --git a/Modules/ControlCenter/Widgets/PowerProfile.qml b/Modules/ControlCenter/Widgets/PowerProfile.qml new file mode 100644 index 00000000..b5b0df8d --- /dev/null +++ b/Modules/ControlCenter/Widgets/PowerProfile.qml @@ -0,0 +1,23 @@ +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.UPower +import qs.Commons +import qs.Services +import qs.Widgets + +// Performance +NButton { + property ShellScreen screen + property real scaling: 1.0 + readonly property bool hasPP: PowerProfileService.available + + enabled: hasPP + outlined: true + text: PowerProfileService.getName() + fontSize: Style.fontSizeS * scaling + fontWeight: Style.fontWeightRegular + icon: PowerProfileService.getIcon() + onClicked: { + PowerProfileService.cycleProfile() + } +} diff --git a/Modules/ControlCenter/Widgets/ScreenRecorder.qml b/Modules/ControlCenter/Widgets/ScreenRecorder.qml new file mode 100644 index 00000000..fb4b0825 --- /dev/null +++ b/Modules/ControlCenter/Widgets/ScreenRecorder.qml @@ -0,0 +1,24 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +NButton { + + property ShellScreen screen + property real scaling: 1.0 + + enabled: ProgramCheckerService.gpuScreenRecorderAvailable + outlined: true + icon: "camera-video" + text: "Screen Recorder" + fontWeight: Style.fontWeightRegular + onClicked: { + ScreenRecorderService.toggleRecording() + if (!ScreenRecorderService.isRecording) { + var panel = PanelService.getPanel("controlCenterPanel") + panel?.close() + } + } +} diff --git a/Modules/ControlCenter/Widgets/WallpaperSelector.qml b/Modules/ControlCenter/Widgets/WallpaperSelector.qml new file mode 100644 index 00000000..fd5a7b81 --- /dev/null +++ b/Modules/ControlCenter/Widgets/WallpaperSelector.qml @@ -0,0 +1,20 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +NButton { + property ShellScreen screen + property real scaling: 1.0 + + + enabled: Settings.data.wallpaper.enabled + outlined: true + icon: "wallpaper-selector" + text: "Wallpaper" + fontSize: Style.fontSizeS * scaling + fontWeight: Style.fontWeightRegular + onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this) + onRightClicked: WallpaperService.setRandomWallpaper() +} diff --git a/Modules/ControlCenter/Widgets/WiFi.qml b/Modules/ControlCenter/Widgets/WiFi.qml new file mode 100644 index 00000000..28d4e047 --- /dev/null +++ b/Modules/ControlCenter/Widgets/WiFi.qml @@ -0,0 +1,42 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +NButton { + property ShellScreen screen + property real scaling: 1.0 + + + outlined: true + icon: { + try { + if (NetworkService.ethernetConnected) { + return "ethernet" + } + let connected = false + let signalStrength = 0 + for (const net in NetworkService.networks) { + if (NetworkService.networks[net].connected) { + connected = true + signalStrength = NetworkService.networks[net].signal + break + } + } + return connected ? NetworkService.signalIcon(signalStrength) : "wifi-off" + } catch (error) { + Logger.error("Wi-Fi", "Error getting icon:", error) + return "signal_wifi_bad" + } + } + text: { + if (NetworkService.ethernetConnected) { + return "Network" + } + return "Wi-Fi" + } + fontSize: Style.fontSizeS * scaling + fontWeight: Style.fontWeightRegular + onClicked: PanelService.getPanel("wifiPanel")?.toggle(this) +} diff --git a/Modules/Settings/Bar/BarSectionEditor.qml b/Modules/Settings/Extras/SectionEditor.qml similarity index 96% rename from Modules/Settings/Bar/BarSectionEditor.qml rename to Modules/Settings/Extras/SectionEditor.qml index a36a1b17..af5a8380 100644 --- a/Modules/Settings/Bar/BarSectionEditor.qml +++ b/Modules/Settings/Extras/SectionEditor.qml @@ -13,6 +13,10 @@ NBox { property string sectionId: "" property var widgetModel: [] property var availableWidgets: [] + property bool enableMoveBetweenSections: true + + property var widgetRegistry: null + property string settingsDialogComponent: "BarWidgetSettingsDialog.qml" readonly property real miniButtonSize: Style.baseWidgetSize * 0.65 @@ -154,7 +158,7 @@ NBox { // Store the widget index for drag operations property int widgetIndex: index readonly property int buttonsWidth: Math.round(20 * scaling) - readonly property int buttonsCount: 1 + BarWidgetRegistry.widgetHasUserSettings(modelData.id) + readonly property int buttonsCount: 1 + (root.widgetRegistry ? root.widgetRegistry.widgetHasUserSettings(modelData.id) : 0) // Visual feedback during drag opacity: flowDragArea.draggedIndex === index ? 0.5 : 1.0 @@ -197,9 +201,10 @@ NBox { onTriggered: action => root.moveWidget(root.sectionId, index, action) } - // Update the MouseArea to use the new context menu + // MouseArea for the context menu MouseArea { id: contextMouseArea + enabled: enableMoveBetweenSections anchors.fill: parent acceptedButtons: Qt.RightButton z: -1 // Below the buttons but above background @@ -209,9 +214,7 @@ NBox { // Check if click is not on the buttons area const localX = mouse.x const buttonsStartX = parent.width - (parent.buttonsCount * parent.buttonsWidth) - if (localX < buttonsStartX) { - // Use the helper function to open at mouse position contextMenu.openAtItem(widgetItem, mouse.x, mouse.y) } } @@ -236,7 +239,7 @@ NBox { Layout.preferredWidth: buttonsCount * buttonsWidth Loader { - active: BarWidgetRegistry.widgetHasUserSettings(modelData.id) + active: root.widgetRegistry && root.widgetRegistry.widgetHasUserSettings(modelData.id) sourceComponent: NIconButton { icon: "settings" tooltipText: I18n.tr("tooltips.widget-settings") @@ -247,7 +250,7 @@ NBox { colorBgHover: Qt.alpha(Color.mOnPrimary, Style.opacityLight) colorFgHover: Color.mOnPrimary onClicked: { - var component = Qt.createComponent(Qt.resolvedUrl("BarWidgetSettingsDialog.qml")) + var component = Qt.createComponent(Qt.resolvedUrl(root.settingsDialogComponent)) function instantiateAndOpen() { var dialog = component.createObject(root, { "widgetIndex": index, @@ -258,19 +261,19 @@ NBox { if (dialog) { dialog.open() } else { - Logger.error("BarSectionEditor", "Failed to create settings dialog instance") + Logger.error("WidgetSectionEditor", "Failed to create settings dialog instance") } } if (component.status === Component.Ready) { instantiateAndOpen() } else if (component.status === Component.Error) { - Logger.error("BarSectionEditor", component.errorString()) + Logger.error("WidgetSectionEditor", component.errorString()) } else { component.statusChanged.connect(function () { if (component.status === Component.Ready) { instantiateAndOpen() } else if (component.status === Component.Error) { - Logger.error("BarSectionEditor", component.errorString()) + Logger.error("WidgetSectionEditor", component.errorString()) } }) } diff --git a/Modules/Settings/SettingsPanel.qml b/Modules/Settings/SettingsPanel.qml index d13f5e9e..cb76dff4 100644 --- a/Modules/Settings/SettingsPanel.qml +++ b/Modules/Settings/SettingsPanel.qml @@ -29,6 +29,7 @@ NPanel { Audio, Bar, ColorScheme, + ControlCenter, OSD, Display, Dock, @@ -111,6 +112,10 @@ NPanel { id: notificationsTab NotificationsTab {} } + Component { + id: controlCenterTab + ControlCenterTab {} + } // Order *DOES* matter function updateTabsModel() { @@ -124,6 +129,11 @@ NPanel { "label": "settings.bar.title", "icon": "settings-bar", "source": barTab + }, { + "id": SettingsPanel.Tab.ControlCenter, + "label": "settings.control-center.title", + "icon": "settings-bar", + "source": controlCenterTab }, { "id": SettingsPanel.Tab.Dock, "label": "settings.dock.title", diff --git a/Modules/Settings/Tabs/BarTab.qml b/Modules/Settings/Tabs/BarTab.qml index 31405f5c..fff686ca 100644 --- a/Modules/Settings/Tabs/BarTab.qml +++ b/Modules/Settings/Tabs/BarTab.qml @@ -5,7 +5,7 @@ import Quickshell import qs.Commons import qs.Services import qs.Widgets -import qs.Modules.Settings.Bar +import qs.Modules.Settings.Extras ColumnLayout { id: root @@ -201,9 +201,11 @@ ColumnLayout { spacing: Style.marginM * scaling // Left Section - BarSectionEditor { + SectionEditor { sectionName: "Left" sectionId: "left" + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/Bar/BarWidgetSettingsDialog.qml") + widgetRegistry: BarWidgetRegistry widgetModel: Settings.data.bar.widgets.left availableWidgets: availableWidgets onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section) @@ -216,9 +218,11 @@ ColumnLayout { } // Center Section - BarSectionEditor { + SectionEditor { sectionName: "Center" sectionId: "center" + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/Bar/BarWidgetSettingsDialog.qml") + widgetRegistry: BarWidgetRegistry widgetModel: Settings.data.bar.widgets.center availableWidgets: availableWidgets onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section) @@ -231,9 +235,11 @@ ColumnLayout { } // Right Section - BarSectionEditor { + SectionEditor { sectionName: "Right" sectionId: "right" + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/Bar/BarWidgetSettingsDialog.qml") + widgetRegistry: BarWidgetRegistry widgetModel: Settings.data.bar.widgets.right availableWidgets: availableWidgets onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section) diff --git a/Modules/Settings/Tabs/ControlCenterTab.qml b/Modules/Settings/Tabs/ControlCenterTab.qml new file mode 100644 index 00000000..9f432137 --- /dev/null +++ b/Modules/Settings/Tabs/ControlCenterTab.qml @@ -0,0 +1,140 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets +import qs.Modules.Settings.Extras + +ColumnLayout { + id: root + spacing: Style.marginL * scaling + + // Handler for drag start - disables panel background clicks + function handleDragStart() { + var panel = PanelService.getPanel("settingsPanel") + if (panel && panel.disableBackgroundClick) { + panel.disableBackgroundClick() + } + } + + // Handler for drag end - re-enables panel background clicks + function handleDragEnd() { + var panel = PanelService.getPanel("settingsPanel") + if (panel && panel.enableBackgroundClick) { + panel.enableBackgroundClick() + } + } + + // Widgets Management Section + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NHeader { + label: I18n.tr("settings.controlCenter.widgets.section.label") + description: I18n.tr("settings.controlCenter.widgets.section.description") + } + + // Bar Sections + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.topMargin: Style.marginM * scaling + spacing: Style.marginM * scaling + + // Quick Settings + SectionEditor { + sectionName: "Quick Settings" + sectionId: "quickSettings" + settingsDialogComponent: "" + widgetRegistry: ControlCenterWidgetRegistry + widgetModel: Settings.data.controlCenter.widgets["quickSettings"] + availableWidgets: availableWidgets + enableMoveBetweenSections: false + onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) + onDragPotentialStarted: root.handleDragStart() + onDragPotentialEnded: root.handleDragEnd() + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // --------------------------------- + // Signal functions + // --------------------------------- + function _addWidgetToSection(widgetId, section) { + var newWidget = { + "id": widgetId + } + if (ControlCenterWidgetRegistry.widgetHasUserSettings(widgetId)) { + var metadata = ControlCenterWidgetRegistry.widgetMetadata[widgetId] + if (metadata) { + Object.keys(metadata).forEach(function (key) { + if (key !== "allowUserSettings") { + newWidget[key] = metadata[key] + } + }) + } + } + Settings.data.controlCenter.widgets[section].push(newWidget) + } + + function _removeWidgetFromSection(section, index) { + if (index >= 0 && index < Settings.data.controlCenter.widgets[section].length) { + var newArray = Settings.data.controlCenter.widgets[section].slice() + var removedWidgets = newArray.splice(index, 1) + Settings.data.controlCenter.widgets[section] = newArray + + // Check that we still have a control center + if (removedWidgets[0].id === "ControlCenter" && BarService.lookupWidget("ControlCenter") === undefined) { + ToastService.showWarning(I18n.tr("toast.missing-control-center.label"), I18n.tr("toast.missing-control-center.description"), 12000) + } + } + } + + function _reorderWidgetInSection(section, fromIndex, toIndex) { + if (fromIndex >= 0 && fromIndex < Settings.data.controlCenter.widgets[section].length && toIndex >= 0 && toIndex < Settings.data.controlCenter.widgets[section].length) { + + // Create a new array to avoid modifying the original + var newArray = Settings.data.controlCenter.widgets[section].slice() + var item = newArray[fromIndex] + newArray.splice(fromIndex, 1) + newArray.splice(toIndex, 0, item) + + Settings.data.controlCenter.widgets[section] = newArray + //Logger.log("BarTab", "Widget reordered. New array:", JSON.stringify(newArray)) + } + } + + function _updateWidgetSettingsInSection(section, index, settings) { + // Update the widget settings in the Settings data + Settings.data.controlCenter.widgets[section][index] = settings + //Logger.log("BarTab", `Updated widget settings for ${settings.id} in ${section} section`) + } + + // Base list model for all combo boxes + ListModel { + id: availableWidgets + } + + Component.onCompleted: { + // Fill out availableWidgets ListModel + availableWidgets.clear() + ControlCenterWidgetRegistry.getAvailableWidgets().forEach(entry => { + availableWidgets.append({ + "key": entry, + "name": entry + }) + }) + } +} diff --git a/Services/ControlCenterWidgetRegistry.qml b/Services/ControlCenterWidgetRegistry.qml new file mode 100644 index 00000000..5fac8e00 --- /dev/null +++ b/Services/ControlCenterWidgetRegistry.qml @@ -0,0 +1,71 @@ +pragma Singleton + +import QtQuick +import Quickshell +import qs.Commons +import qs.Modules.ControlCenter.Widgets + +Singleton { + id: root + + // Widget registry object mapping widget names to components + property var widgets: ({ + "Bluetooth": bluetoothComponent, + "KeepAwake": keepAwakeComponent, + "NightLight": nightLightComponent, + "PowerProfile": powerProfileComponent, + "ScreenRecorder": screenRecorderComponent, + "WiFi": wiFiComponent, + "WallpaperSelector": wallpaperSelectorComponent + }) + + property var widgetMetadata: ({}) + + // Component definitions - these are loaded once at startup + property Component bluetoothComponent: Component { + Bluetooth {} + } + property Component keepAwakeComponent: Component { + KeepAwake {} + } + property Component nightLightComponent: Component { + NightLight {} + } + property Component powerProfileComponent: Component { + PowerProfile {} + } + property Component screenRecorderComponent: Component { + ScreenRecorder {} + } + property Component wiFiComponent: Component { + WiFi {} + } + property Component wallpaperSelectorComponent: Component { + WallpaperSelector {} + } + + function init() { + Logger.log("ControlCenterWidgetRegistry", "Service started") + } + + // ------------------------------ + // Helper function to get widget component by name + function getWidget(id) { + return widgets[id] || null + } + + // Helper function to check if widget exists + function hasWidget(id) { + return id in widgets + } + + // Get list of available widget id + function getAvailableWidgets() { + return Object.keys(widgets) + } + + // Helper function to check if widget has user settings + function widgetHasUserSettings(id) { + return (widgetMetadata[id] !== undefined) && (widgetMetadata[id].allowUserSettings === true) + } +} diff --git a/Widgets/NButton.qml b/Widgets/NButton.qml index 6ce1c75b..2f4f5daf 100644 --- a/Widgets/NButton.qml +++ b/Widgets/NButton.qml @@ -19,6 +19,7 @@ Rectangle { property int fontWeight: Style.fontWeightBold property real iconSize: Style.fontSizeL * scaling property bool outlined: false + property int horizontalAlignment: Qt.AlignHCenter // Signals signal clicked @@ -27,7 +28,6 @@ Rectangle { // Internal properties property bool hovered: false - property bool pressed: false // Dimensions implicitWidth: contentRow.implicitWidth + (Style.marginL * 2 * scaling) @@ -47,7 +47,7 @@ Rectangle { border.color: { if (!enabled) return Color.mOutline - if (pressed || hovered) + if (hovered) return backgroundColor return outlined ? backgroundColor : Color.transparent } @@ -71,7 +71,10 @@ Rectangle { // Content RowLayout { id: contentRow - anchors.centerIn: parent + anchors.verticalCenter: parent.verticalCenter + anchors.left: root.horizontalAlignment === Qt.AlignLeft ? parent.left : undefined + anchors.horizontalCenter: root.horizontalAlignment === Qt.AlignHCenter ? parent.horizontalCenter : undefined + anchors.leftMargin: root.horizontalAlignment === Qt.AlignLeft ? Style.marginL * scaling : 0 spacing: Style.marginXS * scaling // Icon (optional) @@ -84,8 +87,8 @@ Rectangle { if (!root.enabled) return Color.mOnSurfaceVariant if (root.outlined) { - if (root.pressed || root.hovered) - return root.backgroundColor + if (root.hovered) + return root.textColor return root.backgroundColor } return root.textColor From a90bca23aa5c277f5c73015eb45bba1b23fa3ab2 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Wed, 8 Oct 2025 23:24:22 -0400 Subject: [PATCH 005/106] QuickSettings: 3 columns + added Do not disturb --- Modules/ControlCenter/Cards/TopCard.qml | 7 +++---- Modules/ControlCenter/ControlCenterPanel.qml | 2 +- Modules/ControlCenter/Widgets/DoNotDisturb.qml | 17 +++++++++++++++++ .../ControlCenter/Widgets/ScreenRecorder.qml | 3 ++- Services/ControlCenterWidgetRegistry.qml | 4 ++++ 5 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 Modules/ControlCenter/Widgets/DoNotDisturb.qml diff --git a/Modules/ControlCenter/Cards/TopCard.qml b/Modules/ControlCenter/Cards/TopCard.qml index 1bb36148..a7123bfb 100644 --- a/Modules/ControlCenter/Cards/TopCard.qml +++ b/Modules/ControlCenter/Cards/TopCard.qml @@ -98,14 +98,14 @@ NBox { NDivider { Layout.fillWidth: true - Layout.topMargin: Style.marginM * scaling - Layout.bottomMargin: Style.marginM * scaling + Layout.topMargin: Style.marginS * scaling + Layout.bottomMargin: Style.marginS * scaling } GridLayout { id: grid Layout.fillWidth: true - columns: 2 + columns: 3 columnSpacing: Style.marginL * scaling rowSpacing: Style.marginM * scaling @@ -113,7 +113,6 @@ NBox { model: Settings.data.controlCenter.widgets.quickSettings delegate: ControlCenterWidgetLoader { Layout.fillWidth: true - Layout.preferredWidth: (grid.width - grid.columnSpacing) / 2 widgetId: (modelData.id !== undefined ? modelData.id : "") widgetProps: { "screen": root.modelData || null, diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index e47c593c..999f0071 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -38,7 +38,7 @@ NPanel { // Top Card: profile + utilities TopCard { Layout.fillWidth: true - Layout.preferredHeight: Math.max(280 * scaling) + Layout.preferredHeight: Math.max(230 * scaling) } // Media + stats column diff --git a/Modules/ControlCenter/Widgets/DoNotDisturb.qml b/Modules/ControlCenter/Widgets/DoNotDisturb.qml new file mode 100644 index 00000000..fed665d6 --- /dev/null +++ b/Modules/ControlCenter/Widgets/DoNotDisturb.qml @@ -0,0 +1,17 @@ +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +NButton { + property ShellScreen screen + property real scaling: 1.0 + + outlined: true + text: "Do not Disturb" + fontSize: Style.fontSizeS * scaling + fontWeight: Style.fontWeightRegular + icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell" + onClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb +} diff --git a/Modules/ControlCenter/Widgets/ScreenRecorder.qml b/Modules/ControlCenter/Widgets/ScreenRecorder.qml index fb4b0825..fbcb9bd9 100644 --- a/Modules/ControlCenter/Widgets/ScreenRecorder.qml +++ b/Modules/ControlCenter/Widgets/ScreenRecorder.qml @@ -12,7 +12,8 @@ NButton { enabled: ProgramCheckerService.gpuScreenRecorderAvailable outlined: true icon: "camera-video" - text: "Screen Recorder" + text: "Screen Rec." + fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightRegular onClicked: { ScreenRecorderService.toggleRecording() diff --git a/Services/ControlCenterWidgetRegistry.qml b/Services/ControlCenterWidgetRegistry.qml index 5fac8e00..4ed3d2ae 100644 --- a/Services/ControlCenterWidgetRegistry.qml +++ b/Services/ControlCenterWidgetRegistry.qml @@ -11,6 +11,7 @@ Singleton { // Widget registry object mapping widget names to components property var widgets: ({ "Bluetooth": bluetoothComponent, + "DoNotDisturb": doNotDisturbComponent, "KeepAwake": keepAwakeComponent, "NightLight": nightLightComponent, "PowerProfile": powerProfileComponent, @@ -25,6 +26,9 @@ Singleton { property Component bluetoothComponent: Component { Bluetooth {} } + property Component doNotDisturbComponent: Component { + DoNotDisturb {} + } property Component keepAwakeComponent: Component { KeepAwake {} } From f77efc409b34a23dc9f45fcdf3fd7fef62011fe1 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 9 Oct 2025 13:56:35 +0200 Subject: [PATCH 006/106] QuickSettings: customization!? --- Assets/Translations/en.json | 22 ++ Assets/settings-default.json | 31 +- Commons/Settings.qml | 1 + Modules/ControlCenter/Cards/TopCard.qml | 4 +- Modules/ControlCenter/Widgets/Bluetooth.qml | 9 +- .../ControlCenter/Widgets/DoNotDisturb.qml | 11 +- Modules/ControlCenter/Widgets/KeepAwake.qml | 11 +- Modules/ControlCenter/Widgets/NightLight.qml | 18 +- .../ControlCenter/Widgets/PowerProfile.qml | 9 +- .../ControlCenter/Widgets/ScreenRecorder.qml | 10 +- .../Widgets/WallpaperSelector.qml | 10 +- Modules/ControlCenter/Widgets/WiFi.qml | 40 ++- Modules/Settings/Tabs/ControlCenterTab.qml | 35 ++ Widgets/NButton.qml | 2 +- Widgets/NQuickSetting.qml | 327 ++++++++++++++++++ 15 files changed, 507 insertions(+), 33 deletions(-) create mode 100644 Widgets/NQuickSetting.qml diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index ff91166a..62d3e9c7 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -95,6 +95,24 @@ "label": "Position", "description": "Choose where the Control Center panel appears when opened." } + }, + "controlCenter": { + "quickSettingsStyle": { + "section": { + "label": "Quick Settings Style", + "description": "Choose the visual style for quick settings buttons." + }, + "style": { + "label": "Display Style", + "description": "Select between modern card-style buttons or classic icon buttons." + } + }, + "widgets": { + "section": { + "label": "Widgets", + "description": "Manage and configure Control Center widgets." + } + } } }, "audio": { @@ -1241,6 +1259,10 @@ "bottom_right": "Bottom right", "bottom_center": "Bottom center", "top_center": "Top center" + }, + "quickSettingsStyle": { + "modern": "Modern Cards", + "classic": "Classic Icons" } }, "osd": { diff --git a/Assets/settings-default.json b/Assets/settings-default.json index a80e2bb4..05675bd4 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -112,7 +112,36 @@ "terminalCommand": "xterm -e" }, "controlCenter": { - "position": "close_to_bar_button" + "position": "close_to_bar_button", + "quickSettingsStyle": "modern", + "widgets": { + "quickSettings": [ + { + "id": "WiFi" + }, + { + "id": "Bluetooth" + }, + { + "id": "DoNotDisturb" + }, + { + "id": "NightLight" + }, + { + "id": "KeepAwake" + }, + { + "id": "PowerProfile" + }, + { + "id": "ScreenRecorder" + }, + { + "id": "WallpaperSelector" + } + ] + } }, "dock": { "displayMode": "always_visible", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 61b4d184..8ab90c93 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -246,6 +246,7 @@ Singleton { property JsonObject controlCenter: JsonObject { // Position: close_to_bar_button, center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center property string position: "close_to_bar_button" + property string quickSettingsStyle: "modern" // "modern" or "classic" property JsonObject widgets widgets: JsonObject { property list quickSettings: [{ diff --git a/Modules/ControlCenter/Cards/TopCard.qml b/Modules/ControlCenter/Cards/TopCard.qml index a7123bfb..51aeb619 100644 --- a/Modules/ControlCenter/Cards/TopCard.qml +++ b/Modules/ControlCenter/Cards/TopCard.qml @@ -106,8 +106,8 @@ NBox { id: grid Layout.fillWidth: true columns: 3 - columnSpacing: Style.marginL * scaling - rowSpacing: Style.marginM * scaling + columnSpacing: Style.marginM * scaling + rowSpacing: Style.marginS * scaling Repeater { model: Settings.data.controlCenter.widgets.quickSettings diff --git a/Modules/ControlCenter/Widgets/Bluetooth.qml b/Modules/ControlCenter/Widgets/Bluetooth.qml index 7129ba64..9b1cd17d 100644 --- a/Modules/ControlCenter/Widgets/Bluetooth.qml +++ b/Modules/ControlCenter/Widgets/Bluetooth.qml @@ -4,14 +4,17 @@ import qs.Commons import qs.Services import qs.Widgets -NButton { +NQuickSetting { property ShellScreen screen property real scaling: 1.0 - outlined: true text: "Bluetooth" fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightRegular + fontWeight: Style.fontWeightMedium icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off" + active: BluetoothService.enabled + tooltipText: BluetoothService.enabled ? "Bluetooth enabled" : "Bluetooth disabled" + style: Settings.data.controlCenter.quickSettingsStyle || "modern" + onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this) } diff --git a/Modules/ControlCenter/Widgets/DoNotDisturb.qml b/Modules/ControlCenter/Widgets/DoNotDisturb.qml index fed665d6..cc2a1d1a 100644 --- a/Modules/ControlCenter/Widgets/DoNotDisturb.qml +++ b/Modules/ControlCenter/Widgets/DoNotDisturb.qml @@ -4,14 +4,17 @@ import qs.Commons import qs.Services import qs.Widgets -NButton { +NQuickSetting { property ShellScreen screen property real scaling: 1.0 - outlined: true text: "Do not Disturb" fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightRegular - icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell" + fontWeight: Style.fontWeightMedium + icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell" + active: Settings.data.notifications.doNotDisturb + tooltipText: Settings.data.notifications.doNotDisturb ? "Turn off Do Not Disturb" : "Turn on Do Not Disturb" + style: Settings.data.controlCenter.quickSettingsStyle || "modern" + onClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb } diff --git a/Modules/ControlCenter/Widgets/KeepAwake.qml b/Modules/ControlCenter/Widgets/KeepAwake.qml index 9840c5b3..a254f8df 100644 --- a/Modules/ControlCenter/Widgets/KeepAwake.qml +++ b/Modules/ControlCenter/Widgets/KeepAwake.qml @@ -4,14 +4,17 @@ import qs.Commons import qs.Services import qs.Widgets -NButton { +NQuickSetting { property ShellScreen screen property real scaling: 1.0 - outlined: true - text: IdleInhibitorService.isInhibited ? "Keep-awake" : "Keep-awake" + text: "Keep-awake" fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightRegular + fontWeight: Style.fontWeightMedium icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off" + active: IdleInhibitorService.isInhibited + tooltipText: IdleInhibitorService.isInhibited ? "Disable keep-awake" : "Enable keep-awake" + style: Settings.data.controlCenter.quickSettingsStyle || "modern" + onClicked: IdleInhibitorService.manualToggle() } diff --git a/Modules/ControlCenter/Widgets/NightLight.qml b/Modules/ControlCenter/Widgets/NightLight.qml index 841cc560..21077f37 100644 --- a/Modules/ControlCenter/Widgets/NightLight.qml +++ b/Modules/ControlCenter/Widgets/NightLight.qml @@ -4,16 +4,27 @@ import qs.Commons import qs.Services import qs.Widgets -NButton { +NQuickSetting { property ShellScreen screen property real scaling: 1.0 - outlined: true enabled: ProgramCheckerService.wlsunsetAvailable text: "Night Light" fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightRegular + fontWeight: Style.fontWeightMedium icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off" + active: Settings.data.nightLight.enabled + style: Settings.data.controlCenter.quickSettingsStyle || "modern" + tooltipText: { + if (!Settings.data.nightLight.enabled) { + return "Turn on Night Light" + } else if (Settings.data.nightLight.forced) { + return "Night Light forced on" + } else { + return "Turn off Night Light" + } + } + onClicked: { if (!Settings.data.nightLight.enabled) { Settings.data.nightLight.enabled = true @@ -25,6 +36,7 @@ NButton { Settings.data.nightLight.forced = false } } + onRightClicked: { var settingsPanel = PanelService.getPanel("settingsPanel") settingsPanel.requestedTab = SettingsPanel.Tab.Display diff --git a/Modules/ControlCenter/Widgets/PowerProfile.qml b/Modules/ControlCenter/Widgets/PowerProfile.qml index b5b0df8d..2636d40e 100644 --- a/Modules/ControlCenter/Widgets/PowerProfile.qml +++ b/Modules/ControlCenter/Widgets/PowerProfile.qml @@ -6,17 +6,20 @@ import qs.Services import qs.Widgets // Performance -NButton { +NQuickSetting { property ShellScreen screen property real scaling: 1.0 readonly property bool hasPP: PowerProfileService.available enabled: hasPP - outlined: true text: PowerProfileService.getName() fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightRegular + fontWeight: Style.fontWeightMedium icon: PowerProfileService.getIcon() + active: hasPP + tooltipText: hasPP ? "Current: " + PowerProfileService.getName() : "Power profiles not available" + style: Settings.data.controlCenter.quickSettingsStyle || "modern" + onClicked: { PowerProfileService.cycleProfile() } diff --git a/Modules/ControlCenter/Widgets/ScreenRecorder.qml b/Modules/ControlCenter/Widgets/ScreenRecorder.qml index fbcb9bd9..0523bf7e 100644 --- a/Modules/ControlCenter/Widgets/ScreenRecorder.qml +++ b/Modules/ControlCenter/Widgets/ScreenRecorder.qml @@ -4,17 +4,19 @@ import qs.Commons import qs.Services import qs.Widgets -NButton { - +NQuickSetting { property ShellScreen screen property real scaling: 1.0 enabled: ProgramCheckerService.gpuScreenRecorderAvailable - outlined: true icon: "camera-video" text: "Screen Rec." fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightRegular + fontWeight: Style.fontWeightMedium + active: ScreenRecorderService.isRecording + tooltipText: ScreenRecorderService.isRecording ? "Stop recording" : "Start screen recording" + style: Settings.data.controlCenter.quickSettingsStyle || "modern" + onClicked: { ScreenRecorderService.toggleRecording() if (!ScreenRecorderService.isRecording) { diff --git a/Modules/ControlCenter/Widgets/WallpaperSelector.qml b/Modules/ControlCenter/Widgets/WallpaperSelector.qml index fd5a7b81..9ec4db51 100644 --- a/Modules/ControlCenter/Widgets/WallpaperSelector.qml +++ b/Modules/ControlCenter/Widgets/WallpaperSelector.qml @@ -4,17 +4,19 @@ import qs.Commons import qs.Services import qs.Widgets -NButton { +NQuickSetting { property ShellScreen screen property real scaling: 1.0 - enabled: Settings.data.wallpaper.enabled - outlined: true icon: "wallpaper-selector" text: "Wallpaper" fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightRegular + fontWeight: Style.fontWeightMedium + active: Settings.data.wallpaper.enabled + tooltipText: "Open wallpaper selector" + style: Settings.data.controlCenter.quickSettingsStyle || "modern" + onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this) onRightClicked: WallpaperService.setRandomWallpaper() } diff --git a/Modules/ControlCenter/Widgets/WiFi.qml b/Modules/ControlCenter/Widgets/WiFi.qml index 28d4e047..30a4f635 100644 --- a/Modules/ControlCenter/Widgets/WiFi.qml +++ b/Modules/ControlCenter/Widgets/WiFi.qml @@ -4,12 +4,10 @@ import qs.Commons import qs.Services import qs.Widgets -NButton { +NQuickSetting { property ShellScreen screen property real scaling: 1.0 - - outlined: true icon: { try { if (NetworkService.ethernetConnected) { @@ -30,13 +28,47 @@ NButton { return "signal_wifi_bad" } } + text: { if (NetworkService.ethernetConnected) { return "Network" } return "Wi-Fi" } + fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightRegular + fontWeight: Style.fontWeightMedium + style: Settings.data.controlCenter.quickSettingsStyle || "modern" + + active: { + if (NetworkService.ethernetConnected) { + return true + } + try { + for (const net in NetworkService.networks) { + if (NetworkService.networks[net].connected) { + return true + } + } + return false + } catch (error) { + return false + } + } + + tooltipText: { + if (NetworkService.ethernetConnected) { + return "Ethernet connected" + } + let connected = false + for (const net in NetworkService.networks) { + if (NetworkService.networks[net].connected) { + connected = true + break + } + } + return connected ? "Wi-Fi connected" : "Wi-Fi disconnected" + } + onClicked: PanelService.getPanel("wifiPanel")?.toggle(this) } diff --git a/Modules/Settings/Tabs/ControlCenterTab.qml b/Modules/Settings/Tabs/ControlCenterTab.qml index 9f432137..6a6a3b16 100644 --- a/Modules/Settings/Tabs/ControlCenterTab.qml +++ b/Modules/Settings/Tabs/ControlCenterTab.qml @@ -27,6 +27,41 @@ ColumnLayout { } } + // Quick Settings Style Section + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NHeader { + label: I18n.tr("settings.controlCenter.quickSettingsStyle.section.label") + description: I18n.tr("settings.controlCenter.quickSettingsStyle.section.description") + } + + NComboBox { + id: quickSettingsStyle + label: I18n.tr("settings.controlCenter.quickSettingsStyle.style.label") + description: I18n.tr("settings.controlCenter.quickSettingsStyle.style.description") + Layout.fillWidth: true + model: [{ + "key": "modern", + "name": I18n.tr("options.controlCenter.quickSettingsStyle.modern") + }, { + "key": "classic", + "name": I18n.tr("options.controlCenter.quickSettingsStyle.classic") + }] + currentKey: Settings.data.controlCenter.quickSettingsStyle || "modern" + onSelected: function (key) { + Settings.data.controlCenter.quickSettingsStyle = key + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + // Widgets Management Section ColumnLayout { spacing: Style.marginXXS * scaling diff --git a/Widgets/NButton.qml b/Widgets/NButton.qml index 2f4f5daf..3d1b05a3 100644 --- a/Widgets/NButton.qml +++ b/Widgets/NButton.qml @@ -19,7 +19,7 @@ Rectangle { property int fontWeight: Style.fontWeightBold property real iconSize: Style.fontSizeL * scaling property bool outlined: false - property int horizontalAlignment: Qt.AlignHCenter + property int horizontalAlignment: Qt.AlignHCenter // Signals signal clicked diff --git a/Widgets/NQuickSetting.qml b/Widgets/NQuickSetting.qml new file mode 100644 index 00000000..3c199c16 --- /dev/null +++ b/Widgets/NQuickSetting.qml @@ -0,0 +1,327 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Effects +import qs.Commons +import qs.Services + +Rectangle { + id: root + + // Public properties + property string text: "" + property string icon: "" + property string tooltipText: "" + property bool enabled: true + property bool active: false + property bool compact: false + property string style: "modern" // "modern" or "classic" + + // Styling properties + property real fontSize: Style.fontSizeS * scaling + property int fontWeight: Style.fontWeightMedium + property real iconSize: Style.fontSizeL * scaling + property real cornerRadius: Style.radiusM * scaling + + // Colors - Style-dependent colors + property color backgroundColor: style === "classic" ? Color.mSurfaceVariant : Color.mSurface + property color textColor: Color.mOnSurface + property color iconColor: style === "classic" ? Color.mPrimary : (active ? Color.mPrimary : Color.mOnSurface) + property color borderColor: Color.mOutline + property color hoverColor: style === "classic" ? Color.mTertiary : Color.mPrimary + property color pressedColor: style === "classic" ? Color.mTertiary : Qt.darker(Color.mPrimary, 1.1) + property color hoverTextColor: Color.mOnPrimary + property color hoverIconColor: style === "classic" ? Color.mOnTertiary : Color.mOnPrimary + + // Signals + signal clicked + signal rightClicked + signal middleClicked + + // Internal properties + property bool hovered: false + property bool pressed: false + property real scaling: 1.0 + + // Dimensions - Style-dependent sizing + implicitWidth: { + if (style === "classic") { + return Style.baseWidgetSize * scaling + } + return compact ? Math.max(100 * scaling, contentRow.implicitWidth + (Style.marginL * scaling)) : Math.max(120 * scaling, contentRow.implicitWidth + (Style.marginL * scaling)) + } + implicitHeight: { + if (style === "classic") { + return Style.baseWidgetSize * scaling + } + return compact ? Math.max(48 * scaling, contentRow.implicitHeight + (Style.marginM * scaling)) : Math.max(56 * scaling, contentRow.implicitHeight + (Style.marginL * scaling)) + } + + // Appearance - Style-dependent styling + radius: style === "classic" ? width * 0.5 : cornerRadius + color: { + if (!enabled) + return Qt.lighter(Color.mSurface, 1.1) + if (pressed) + return pressedColor + if (hovered) + return hoverColor + return backgroundColor + } + + border.width: style === "classic" ? Math.max(1, Style.borderS * scaling) : 0 + border.color: style === "classic" ? borderColor : "transparent" + + opacity: enabled ? (style === "classic" ? Style.opacityFull : 1.0) : (style === "classic" ? Style.opacityMedium : 0.6) + + // Smooth animations + Behavior on color { + ColorAnimation { + duration: style === "classic" ? Style.animationNormal : Style.animationFast + easing.type: style === "classic" ? Easing.InOutQuad : Easing.OutCubic + } + } + + Behavior on border.color { + ColorAnimation { + duration: style === "classic" ? Style.animationNormal : Style.animationFast + easing.type: style === "classic" ? Easing.InOutQuad : Easing.OutCubic + } + } + + Behavior on scale { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + // Hover scale effect + scale: hovered ? 1.02 : 1.0 + + // Subtle shadow/elevation effect + Rectangle { + anchors.fill: parent + radius: parent.radius + color: Qt.rgba(0, 0, 0, 0.1) + visible: active + z: -1 + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + // Modern style - icon above text + ColumnLayout { + id: contentRow + anchors.centerIn: parent + spacing: Style.marginXXS * scaling + visible: root.style !== "classic" + + // Icon + NIcon { + Layout.alignment: Qt.AlignHCenter + visible: root.icon !== "" + icon: root.icon + pointSize: root.iconSize + color: { + if (!root.enabled) + return Color.mOnSurfaceVariant + if (root.hovered) + return root.hoverIconColor + return root.iconColor + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + // Text content + NText { + Layout.alignment: Qt.AlignHCenter + visible: root.text !== "" && !compact + text: root.text + pointSize: root.fontSize + font.weight: root.fontWeight + color: { + if (!root.enabled) + return Color.mOnSurfaceVariant + if (root.hovered) + return root.hoverTextColor + return root.textColor + } + elide: Text.ElideRight + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + } + + // Classic style - EXACTLY like NIconButton (icon + text) + RowLayout { + anchors.centerIn: parent + visible: root.style === "classic" + spacing: Style.marginXS * scaling + + NIcon { + visible: root.icon !== "" + icon: root.icon + pointSize: Style.fontSizeM * scaling + color: { + if (!root.enabled) + return Color.mOnSurfaceVariant + if (root.hovered) + return root.hoverIconColor + return root.iconColor + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + NText { + visible: root.text !== "" + text: root.text + pointSize: root.fontSize + font.weight: root.fontWeight + color: { + if (!root.enabled) + return Color.mOnSurfaceVariant + if (root.hovered) + return root.hoverTextColor + return root.textColor + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + } + + // Mouse interaction with enhanced feedback + MouseArea { + id: mouseArea + anchors.fill: parent + enabled: root.enabled + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + + onEntered: { + root.hovered = true + if (tooltipText) { + TooltipService.show(Screen, root, root.tooltipText) + } + } + + onExited: { + root.hovered = false + if (tooltipText) { + TooltipService.hide() + } + } + + onPressed: mouse => { + root.pressed = true + root.scale = 0.95 + if (tooltipText) { + TooltipService.hide() + } + } + + onReleased: mouse => { + root.pressed = false + root.scale = 1.0 + + if (mouse.button === Qt.LeftButton) { + root.clicked() + } else if (mouse.button === Qt.RightButton) { + root.rightClicked() + } else if (mouse.button === Qt.MiddleButton) { + root.middleClicked() + } + } + + onCanceled: { + root.hovered = false + root.pressed = false + root.scale = 1.0 + if (tooltipText) { + TooltipService.hide() + } + } + } + + // Ripple effect for M3-style interaction feedback + Rectangle { + id: ripple + anchors.fill: parent + radius: parent.radius + color: Qt.rgba(1, 1, 1, 0.2) + scale: 0 + opacity: 0 + visible: false + + SequentialAnimation { + id: rippleAnimation + running: false + + ParallelAnimation { + NumberAnimation { + target: ripple + property: "scale" + from: 0 + to: 1.2 + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + NumberAnimation { + target: ripple + property: "opacity" + from: 0.6 + to: 0 + duration: Style.animationNormal + easing.type: Easing.OutCubic + } + } + } + } + + // Trigger ripple effect on click + Connections { + target: root + function onClicked() { + ripple.visible = true + rippleAnimation.start() + } + } + + // Clean up ripple after animation + Connections { + target: rippleAnimation + function onFinished() { + ripple.visible = false + ripple.scale = 0 + ripple.opacity = 0 + } + } +} From a5ff7cfe6b26f197c5da70e0e5a0bd042b4caea6 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 08:59:26 -0400 Subject: [PATCH 007/106] ControlCenter: improved height computation --- Modules/ControlCenter/ControlCenterPanel.qml | 27 ++++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 999f0071..2e8d5375 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -11,9 +11,24 @@ NPanel { id: root preferredWidth: 440 - preferredHeight: 540 + preferredHeight: topHeight + bottomHeight + Math.round(Style.marginL * scaling * 3) panelKeyboardFocus: true + readonly property int bottomHeight: Math.round(Math.max(196 * scaling)) + readonly property int topHeight: { + const rowsCount = Math.ceil(Settings.data.controlCenter.widgets.quickSettings.length / 3) + + var buttonHeight; + if (Settings.data.controlCenter.quickSettingsStyle === "classic") { + buttonHeight = Style.baseWidgetSize + } + else { + buttonHeight = 56 + } + + return (rowsCount * buttonHeight) + (120 * scaling) + } + // Positioning readonly property string controlCenterPosition: Settings.data.controlCenter.position panelAnchorHorizontalCenter: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_center") @@ -37,26 +52,28 @@ NPanel { // Top Card: profile + utilities TopCard { + id: topCard Layout.fillWidth: true - Layout.preferredHeight: Math.max(230 * scaling) + Layout.preferredHeight: topHeight } // Media + stats column RowLayout { + id: bottomCard Layout.fillWidth: true - Layout.preferredHeight: Math.max(196 * scaling) + Layout.preferredHeight: bottomHeight spacing: content.cardSpacing // Media card MediaCard { Layout.preferredWidth: Math.max(250 * scaling) - Layout.preferredHeight: Math.max(196 * scaling) + Layout.preferredHeight: bottomHeight } // System monitors combined in one card SystemMonitorCard { Layout.preferredWidth: Math.max(140 * scaling) - Layout.preferredHeight: Math.max(196 * scaling) + Layout.preferredHeight: bottomHeight } } } From 95dcded6b7540fe975cdc2448f10c87ddd990ce6 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 9 Oct 2025 15:17:23 +0200 Subject: [PATCH 008/106] QuickSettings: add compact version i18n: add translations --- Assets/Translations/de.json | 41 +++++++++ Assets/Translations/en.json | 37 ++++++-- Assets/Translations/es.json | 41 +++++++++ Assets/Translations/fr.json | 41 +++++++++ Assets/Translations/pt.json | 41 +++++++++ Assets/Translations/zh-CN.json | 41 +++++++++ Modules/Settings/Tabs/ControlCenterTab.qml | 19 ++-- Widgets/NQuickSetting.qml | 100 +++++++++++++++++---- 8 files changed, 329 insertions(+), 32 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 021a823f..eb2f15ce 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -85,6 +85,7 @@ "reset-scaling": "Skalierung zurücksetzen" }, "control-center": { + "title": "Kontrollzentrum", "section": { "label": "Kontrollzentrum", "description": "Konfigurieren Sie die Positionierung und das Verhalten des Kontrollzentrum-Panels." @@ -92,6 +93,22 @@ "position": { "label": "Position", "description": "Wählen Sie, wo das Kontrollzentrum-Panel beim Öffnen erscheint." + }, + "quickSettingsStyle": { + "section": { + "label": "Schnelleinstellungen-Stil", + "description": "Passen Sie das Aussehen und Verhalten der Schnelleinstellungen im Kontrollzentrum an." + }, + "style": { + "label": "Schnelleinstellungen-Stil", + "description": "Wählen Sie den visuellen Stil für Schnelleinstellungs-Schalter und -Steuerungen." + } + }, + "widgets": { + "section": { + "label": "Widgets", + "description": "Konfigurieren und verwalten Sie Kontrollzentrum-Widgets und deren Anzeigeoptionen." + } } } }, @@ -709,6 +726,25 @@ } } }, + "control-center": { + "title": "Kontrollzentrum", + "quickSettingsStyle": { + "section": { + "label": "Schnelleinstellungen-Stil", + "description": "Passen Sie das Aussehen und Verhalten der Schnelleinstellungen im Kontrollzentrum an." + }, + "style": { + "label": "Schnelleinstellungen-Stil", + "description": "Wählen Sie den visuellen Stil für Schnelleinstellungs-Schalter und -Steuerungen." + } + }, + "widgets": { + "section": { + "label": "Widgets", + "description": "Konfigurieren und verwalten Sie Kontrollzentrum-Widgets und deren Anzeigeoptionen." + } + } + }, "hooks": { "title": "Hooks", "system-hooks": { @@ -1259,6 +1295,11 @@ "bottom_right": "Unten rechts", "bottom_center": "Unten mittig", "top_center": "Oben mittig" + }, + "quickSettingsStyle": { + "modern": "Modern", + "classic": "Klassisch", + "compact": "Kompakt" } }, "osd": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 62d3e9c7..4d7e9b13 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -87,6 +87,7 @@ } }, "control-center": { + "title": "Control Center", "section": { "label": "Control Center", "description": "Configure the Control Center panel positioning and behavior." @@ -94,23 +95,21 @@ "position": { "label": "Position", "description": "Choose where the Control Center panel appears when opened." - } - }, - "controlCenter": { + }, "quickSettingsStyle": { "section": { "label": "Quick Settings Style", - "description": "Choose the visual style for quick settings buttons." + "description": "Customize the appearance and behavior of quick settings in the Control Center." }, "style": { - "label": "Display Style", - "description": "Select between modern card-style buttons or classic icon buttons." + "label": "Quick Settings Style", + "description": "Choose the visual style for quick settings toggles and controls." } }, "widgets": { "section": { "label": "Widgets", - "description": "Manage and configure Control Center widgets." + "description": "Configure and manage Control Center widgets and their display options." } } } @@ -725,6 +724,25 @@ } } }, + "control-center": { + "title": "Control Center", + "quickSettingsStyle": { + "section": { + "label": "Quick Settings Style", + "description": "Customize the appearance and behavior of quick settings in the Control Center." + }, + "style": { + "label": "Quick Settings Style", + "description": "Choose the visual style for quick settings toggles and controls." + } + }, + "widgets": { + "section": { + "label": "Widgets", + "description": "Configure and manage Control Center widgets and their display options." + } + } + }, "hooks": { "title": "Hooks", "system-hooks": { @@ -1261,8 +1279,9 @@ "top_center": "Top center" }, "quickSettingsStyle": { - "modern": "Modern Cards", - "classic": "Classic Icons" + "modern": "Modern", + "classic": "Classic", + "compact": "Compact" } }, "osd": { diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index f33d2e0c..5b904ade 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -85,6 +85,7 @@ "reset-scaling": "Restablecer la escala" }, "control-center": { + "title": "Centro de control", "section": { "label": "Centro de control", "description": "Configurar el posicionamiento y comportamiento del panel del centro de control." @@ -92,6 +93,22 @@ "position": { "label": "Posición", "description": "Elige dónde aparece el panel del centro de control cuando se abre." + }, + "quickSettingsStyle": { + "section": { + "label": "Estilo de configuración rápida", + "description": "Personaliza la apariencia y el comportamiento de las configuraciones rápidas en el centro de control." + }, + "style": { + "label": "Estilo de configuración rápida", + "description": "Elige el estilo visual para los interruptores y controles de configuración rápida." + } + }, + "widgets": { + "section": { + "label": "Widgets", + "description": "Configura y gestiona los widgets del centro de control y sus opciones de visualización." + } } } }, @@ -705,6 +722,25 @@ } } }, + "control-center": { + "title": "Centro de control", + "quickSettingsStyle": { + "section": { + "label": "Estilo de configuración rápida", + "description": "Personaliza la apariencia y el comportamiento de las configuraciones rápidas en el centro de control." + }, + "style": { + "label": "Estilo de configuración rápida", + "description": "Elige el estilo visual para los interruptores y controles de configuración rápida." + } + }, + "widgets": { + "section": { + "label": "Widgets", + "description": "Configura y gestiona los widgets del centro de control y sus opciones de visualización." + } + } + }, "hooks": { "title": "Hooks", "system-hooks": { @@ -1238,6 +1274,11 @@ "bottom_right": "Inferior derecha", "bottom_center": "Inferior central", "top_center": "Superior central" + }, + "quickSettingsStyle": { + "modern": "Moderno", + "classic": "Clásico", + "compact": "Compacto" } }, "osd": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index a087896a..aab3dd41 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -85,6 +85,7 @@ "reset-scaling": "Réinitialiser l'échelle" }, "control-center": { + "title": "Centre de contrôle", "section": { "label": "Centre de contrôle", "description": "Configurer le positionnement et le comportement du panneau du centre de contrôle." @@ -92,6 +93,22 @@ "position": { "label": "Position", "description": "Choisissez où apparaît le panneau du centre de contrôle lors de l'ouverture." + }, + "quickSettingsStyle": { + "section": { + "label": "Style des paramètres rapides", + "description": "Personnalisez l'apparence et le comportement des paramètres rapides dans le centre de contrôle." + }, + "style": { + "label": "Style des paramètres rapides", + "description": "Choisissez le style visuel pour les commutateurs et contrôles des paramètres rapides." + } + }, + "widgets": { + "section": { + "label": "Widgets", + "description": "Configurez et gérez les widgets du centre de contrôle et leurs options d'affichage." + } } } }, @@ -705,6 +722,25 @@ } } }, + "control-center": { + "title": "Centre de contrôle", + "quickSettingsStyle": { + "section": { + "label": "Style des paramètres rapides", + "description": "Personnalisez l'apparence et le comportement des paramètres rapides dans le centre de contrôle." + }, + "style": { + "label": "Style des paramètres rapides", + "description": "Choisissez le style visuel pour les commutateurs et contrôles des paramètres rapides." + } + }, + "widgets": { + "section": { + "label": "Widgets", + "description": "Configurez et gérez les widgets du centre de contrôle et leurs options d'affichage." + } + } + }, "hooks": { "title": "Hooks", "system-hooks": { @@ -1238,6 +1274,11 @@ "bottom_right": "En bas à droite", "bottom_center": "En bas au centre", "top_center": "En haut au centre" + }, + "quickSettingsStyle": { + "modern": "Moderne", + "classic": "Classique", + "compact": "Compact" } }, "osd": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index d9b1d538..ca66a0b4 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -85,6 +85,7 @@ "reset-scaling": "Redefinir escala" }, "control-center": { + "title": "Centro de controle", "section": { "label": "Centro de controle", "description": "Configurar o posicionamento e comportamento do painel do centro de controle." @@ -92,6 +93,22 @@ "position": { "label": "Posição", "description": "Escolha onde o painel do centro de controle aparece quando aberto." + }, + "quickSettingsStyle": { + "section": { + "label": "Estilo de configurações rápidas", + "description": "Personalize a aparência e o comportamento das configurações rápidas no centro de controle." + }, + "style": { + "label": "Estilo de configurações rápidas", + "description": "Escolha o estilo visual para os interruptores e controles de configurações rápidas." + } + }, + "widgets": { + "section": { + "label": "Widgets", + "description": "Configure e gerencie os widgets do centro de controle e suas opções de exibição." + } } } }, @@ -671,6 +688,25 @@ } } }, + "control-center": { + "title": "Centro de controle", + "quickSettingsStyle": { + "section": { + "label": "Estilo de configurações rápidas", + "description": "Personalize a aparência e o comportamento das configurações rápidas no centro de controle." + }, + "style": { + "label": "Estilo de configurações rápidas", + "description": "Escolha o estilo visual para os interruptores e controles de configurações rápidas." + } + }, + "widgets": { + "section": { + "label": "Widgets", + "description": "Configure e gerencie os widgets do centro de controle e suas opções de exibição." + } + } + }, "hooks": { "title": "Hooks", "system-hooks": { @@ -1237,6 +1273,11 @@ "bottom_right": "Inferior direito", "bottom_center": "Centro inferior", "top_center": "Centro superior" + }, + "quickSettingsStyle": { + "modern": "Moderno", + "classic": "Clássico", + "compact": "Compacto" } }, "bar": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 8bdfbce4..f5ee975e 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -85,6 +85,7 @@ } }, "control-center": { + "title": "控制中心", "section": { "label": "控制中心", "description": "配置控制中心面板的定位和行为。" @@ -92,6 +93,22 @@ "position": { "label": "位置", "description": "选择控制中心面板打开时出现的位置。" + }, + "quickSettingsStyle": { + "section": { + "label": "快速设置样式", + "description": "自定义控制中心中快速设置的外观和行为。" + }, + "style": { + "label": "快速设置样式", + "description": "选择快速设置开关和控件的视觉样式。" + } + }, + "widgets": { + "section": { + "label": "小部件", + "description": "配置和管理控制中心小部件及其显示选项。" + } } } }, @@ -705,6 +722,25 @@ } } }, + "control-center": { + "title": "控制中心", + "quickSettingsStyle": { + "section": { + "label": "快速设置样式", + "description": "自定义控制中心中快速设置的外观和行为。" + }, + "style": { + "label": "快速设置样式", + "description": "选择快速设置开关和控件的视觉样式。" + } + }, + "widgets": { + "section": { + "label": "小部件", + "description": "配置和管理控制中心小部件及其显示选项。" + } + } + }, "hooks": { "title": "钩子", "system-hooks": { @@ -1238,6 +1274,11 @@ "bottom_right": "右下角", "bottom_center": "底部居中", "top_center": "顶部居中" + }, + "quickSettingsStyle": { + "modern": "现代", + "classic": "经典", + "compact": "紧凑" } }, "osd": { diff --git a/Modules/Settings/Tabs/ControlCenterTab.qml b/Modules/Settings/Tabs/ControlCenterTab.qml index 6a6a3b16..044f13c8 100644 --- a/Modules/Settings/Tabs/ControlCenterTab.qml +++ b/Modules/Settings/Tabs/ControlCenterTab.qml @@ -33,21 +33,24 @@ ColumnLayout { Layout.fillWidth: true NHeader { - label: I18n.tr("settings.controlCenter.quickSettingsStyle.section.label") - description: I18n.tr("settings.controlCenter.quickSettingsStyle.section.description") + label: I18n.tr("settings.control-center.quickSettingsStyle.section.label") + description: I18n.tr("settings.control-center.quickSettingsStyle.section.description") } NComboBox { id: quickSettingsStyle - label: I18n.tr("settings.controlCenter.quickSettingsStyle.style.label") - description: I18n.tr("settings.controlCenter.quickSettingsStyle.style.description") + label: I18n.tr("settings.control-center.quickSettingsStyle.style.label") + description: I18n.tr("settings.control-center.quickSettingsStyle.style.description") Layout.fillWidth: true model: [{ "key": "modern", - "name": I18n.tr("options.controlCenter.quickSettingsStyle.modern") + "name": I18n.tr("options.control-center.quickSettingsStyle.modern") }, { "key": "classic", - "name": I18n.tr("options.controlCenter.quickSettingsStyle.classic") + "name": I18n.tr("options.control-center.quickSettingsStyle.classic") + }, { + "key": "compact", + "name": I18n.tr("options.control-center.quickSettingsStyle.compact") }] currentKey: Settings.data.controlCenter.quickSettingsStyle || "modern" onSelected: function (key) { @@ -68,8 +71,8 @@ ColumnLayout { Layout.fillWidth: true NHeader { - label: I18n.tr("settings.controlCenter.widgets.section.label") - description: I18n.tr("settings.controlCenter.widgets.section.description") + label: I18n.tr("settings.control-center.widgets.section.label") + description: I18n.tr("settings.control-center.widgets.section.description") } // Bar Sections diff --git a/Widgets/NQuickSetting.qml b/Widgets/NQuickSetting.qml index 3c199c16..75517718 100644 --- a/Widgets/NQuickSetting.qml +++ b/Widgets/NQuickSetting.qml @@ -15,7 +15,7 @@ Rectangle { property bool enabled: true property bool active: false property bool compact: false - property string style: "modern" // "modern" or "classic" + property string style: "modern" // "modern", "classic", or "compact" // Styling properties property real fontSize: Style.fontSizeS * scaling @@ -24,14 +24,44 @@ Rectangle { property real cornerRadius: Style.radiusM * scaling // Colors - Style-dependent colors - property color backgroundColor: style === "classic" ? Color.mSurfaceVariant : Color.mSurface + property color backgroundColor: { + if (style === "classic") + return Color.mSurfaceVariant + if (style === "compact") + return Color.mSurface + return Color.mSurface + } property color textColor: Color.mOnSurface - property color iconColor: style === "classic" ? Color.mPrimary : (active ? Color.mPrimary : Color.mOnSurface) + property color iconColor: { + if (style === "classic") + return Color.mPrimary + if (style === "compact") + return active ? Color.mPrimary : Color.mOnSurface + return active ? Color.mPrimary : Color.mOnSurface + } property color borderColor: Color.mOutline - property color hoverColor: style === "classic" ? Color.mTertiary : Color.mPrimary - property color pressedColor: style === "classic" ? Color.mTertiary : Qt.darker(Color.mPrimary, 1.1) + property color hoverColor: { + if (style === "classic") + return Color.mTertiary + if (style === "compact") + return Color.mPrimary + return Color.mPrimary + } + property color pressedColor: { + if (style === "classic") + return Color.mTertiary + if (style === "compact") + return Qt.darker(Color.mPrimary, 1.1) + return Qt.darker(Color.mPrimary, 1.1) + } property color hoverTextColor: Color.mOnPrimary - property color hoverIconColor: style === "classic" ? Color.mOnTertiary : Color.mOnPrimary + property color hoverIconColor: { + if (style === "classic") + return Color.mOnTertiary + if (style === "compact") + return Color.mOnPrimary + return Color.mOnPrimary + } // Signals signal clicked @@ -48,17 +78,29 @@ Rectangle { if (style === "classic") { return Style.baseWidgetSize * scaling } + if (style === "compact") { + return Style.baseWidgetSize * 0.8 * scaling + } return compact ? Math.max(100 * scaling, contentRow.implicitWidth + (Style.marginL * scaling)) : Math.max(120 * scaling, contentRow.implicitWidth + (Style.marginL * scaling)) } implicitHeight: { if (style === "classic") { return Style.baseWidgetSize * scaling } + if (style === "compact") { + return Style.baseWidgetSize * 0.8 * scaling + } return compact ? Math.max(48 * scaling, contentRow.implicitHeight + (Style.marginM * scaling)) : Math.max(56 * scaling, contentRow.implicitHeight + (Style.marginL * scaling)) } // Appearance - Style-dependent styling - radius: style === "classic" ? width * 0.5 : cornerRadius + radius: { + if (style === "classic") + return width * 0.5 + if (style === "compact") + return Style.radiusS * scaling // Smaller radius for compact + return cornerRadius + } color: { if (!enabled) return Qt.lighter(Color.mSurface, 1.1) @@ -69,12 +111,21 @@ Rectangle { return backgroundColor } - border.width: style === "classic" ? Math.max(1, Style.borderS * scaling) : 0 - border.color: style === "classic" ? borderColor : "transparent" + border.width: { + if (style === "classic") + return Math.max(1, Style.borderS * scaling) + if (style === "compact") + return 0 + return 0 + } + border.color: { + if (style === "classic") + return borderColor + return "transparent" + } opacity: enabled ? (style === "classic" ? Style.opacityFull : 1.0) : (style === "classic" ? Style.opacityMedium : 0.6) - // Smooth animations Behavior on color { ColorAnimation { duration: style === "classic" ? Style.animationNormal : Style.animationFast @@ -120,7 +171,7 @@ Rectangle { id: contentRow anchors.centerIn: parent spacing: Style.marginXXS * scaling - visible: root.style !== "classic" + visible: root.style !== "classic" && root.style !== "compact" // Icon NIcon { @@ -169,6 +220,29 @@ Rectangle { } } + // Compact style - icon only, small square button + NIcon { + id: compactIcon + anchors.centerIn: parent + visible: root.style === "compact" && root.icon !== "" + icon: root.icon + pointSize: Style.fontSizeM * scaling // Smaller icon for compact + color: { + if (!root.enabled) + return Color.mOnSurfaceVariant + if (root.hovered) + return root.hoverIconColor + return root.iconColor + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + // Classic style - EXACTLY like NIconButton (icon + text) RowLayout { anchors.centerIn: parent @@ -217,7 +291,6 @@ Rectangle { } } - // Mouse interaction with enhanced feedback MouseArea { id: mouseArea anchors.fill: parent @@ -271,7 +344,6 @@ Rectangle { } } - // Ripple effect for M3-style interaction feedback Rectangle { id: ripple anchors.fill: parent @@ -306,7 +378,6 @@ Rectangle { } } - // Trigger ripple effect on click Connections { target: root function onClicked() { @@ -315,7 +386,6 @@ Rectangle { } } - // Clean up ripple after animation Connections { target: rippleAnimation function onFinished() { From ab7099e491a8eb393a0927a79994c35f6cb3cd61 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 09:23:21 -0400 Subject: [PATCH 009/106] Tray: rounding size to avoid unwanted blur. --- Modules/Bar/Widgets/Tray.qml | 18 +++++++++--------- shell.qml | 7 ++++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index b93c4b7a..d3dc5dd7 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -19,7 +19,7 @@ Rectangle { readonly property string barPosition: Settings.data.bar.position readonly property bool isVertical: barPosition === "left" || barPosition === "right" readonly property bool compact: (Settings.data.bar.density === "compact") - readonly property real itemSize: isVertical ? width * 0.75 : height * 0.85 + readonly property real itemSize: isVertical ? Math.round(width * 0.7) : Math.round(height * 0.7) function onLoaded() { // When the widget is fully initialized with its props set the screen for the trayMenu @@ -39,7 +39,7 @@ Rectangle { Flow { id: trayFlow anchors.centerIn: parent - spacing: Style.marginS * scaling + spacing: Style.marginM * scaling flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight Repeater { @@ -56,11 +56,10 @@ Rectangle { property ShellScreen screen: root.screen - anchors.centerIn: parent - width: Style.marginL * scaling - height: Style.marginL * scaling - smooth: false + anchors.fill: parent asynchronous: true + smooth: false + mipmap: true backer.fillMode: Image.PreserveAspectFit source: { let icon = modelData?.icon || "" @@ -70,7 +69,6 @@ Rectangle { // Process icon path if (icon.includes("?path=")) { - // Seems qmlfmt does not support the following ES6 syntax: const[name, path] = icon.split const chunks = icon.split("?path=") const name = chunks[0] const path = chunks[1] @@ -80,9 +78,8 @@ Rectangle { return icon } opacity: status === Image.Ready ? 1 : 0 - } - MouseArea { + MouseArea { anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor @@ -144,6 +141,9 @@ Rectangle { } onExited: TooltipService.hide() } + } + + } } } diff --git a/shell.qml b/shell.qml index a32b7b40..b110ba32 100644 --- a/shell.qml +++ b/shell.qml @@ -77,9 +77,6 @@ ShellRoot { sourceComponent: Item { Component.onCompleted: { - // Save a ref. to our lockScreen so we can access it easily - PanelService.lockScreen = lockScreen - Logger.log("Shell", "---------------------------") WallpaperService.init() AppThemeService.init() @@ -104,6 +101,10 @@ ShellRoot { LockScreen { id: lockScreen + Component.onCompleted: { + // Save a ref. to our lockScreen so we can access it easily + PanelService.lockScreen = lockScreen + } } ToastOverlay {} From 516213a96ddf3202a45ec2f6d6ef32d403f291c6 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 9 Oct 2025 15:25:00 +0200 Subject: [PATCH 010/106] LockScreen: fix warning, make clock size uniform --- Modules/LockScreen/LockScreen.qml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index f7cb7a92..c100d344 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -105,13 +105,11 @@ Loader { property color cornerColor: Settings.data.general.forceBlackScreenCorners ? Qt.rgba(0, 0, 0, 1) : Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) property real cornerRadius: Style.screenRadius * scaling property real cornerSize: Style.screenRadius * scaling - property real barHeight: Style.barHeight * scaling // Top-left concave corner Canvas { anchors.top: parent.top anchors.left: parent.left - anchors.topMargin: Settings.data.bar.position === "top" ? barHeight : 0 width: parent.cornerSize height: parent.cornerSize antialiasing: true @@ -146,7 +144,6 @@ Loader { Canvas { anchors.top: parent.top anchors.right: parent.right - anchors.topMargin: Settings.data.bar.position === "top" ? barHeight : 0 width: parent.cornerSize height: parent.cornerSize antialiasing: true @@ -181,7 +178,6 @@ Loader { Canvas { anchors.bottom: parent.bottom anchors.left: parent.left - anchors.bottomMargin: Settings.data.bar.position === "bottom" ? barHeight : 0 width: parent.cornerSize height: parent.cornerSize antialiasing: true @@ -216,7 +212,6 @@ Loader { Canvas { anchors.bottom: parent.bottom anchors.right: parent.right - anchors.bottomMargin: Settings.data.bar.position === "bottom" ? barHeight : 0 width: parent.cornerSize height: parent.cornerSize antialiasing: true @@ -412,7 +407,7 @@ Loader { var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH") return t } - pointSize: Style.fontSizeXL * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface horizontalAlignment: Text.AlignHCenter From a25ea9fa776c3232771fcbfc969e6dde901e13f4 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 09:36:31 -0400 Subject: [PATCH 011/106] DigitalClock: improved accuracy, removed useless connections --- Modules/Bar/Calendar/CalendarPanel.qml | 3 ++- Modules/LockScreen/LockScreen.qml | 23 ++++++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index 277745b5..21246815 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -174,7 +174,8 @@ NPanel { Connections { target: Time function onDateChanged() { - secondsProgress.progress = Time.date.getSeconds() / 60 + const total = Time.date.getSeconds() * 1000 + Time.date.getMilliseconds() + secondsProgress.progress = total / 60000 } } diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index c100d344..38ef38ff 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -368,7 +368,8 @@ Loader { Connections { target: Time function onDateChanged() { - secondsProgress.progress = Time.date.getSeconds() / 60 + const total = Time.date.getSeconds() * 1000 + Time.date.getMilliseconds() + secondsProgress.progress = total / 60000 } } @@ -413,11 +414,11 @@ Loader { horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignHCenter - Connections { - target: Time - function onDateChanged() {// Trigger text update - } - } + // Connections { + // target: Time + // function onDateChanged() {// Trigger text update + // } + // } } NText { @@ -428,11 +429,11 @@ Loader { horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignHCenter - Connections { - target: Time - function onDateChanged() {// Trigger text update - } - } + // Connections { + // target: Time + // function onDateChanged() {// Trigger text update + // } + // } } } } From 5c5e4140722307173218e31bec90e35a7efa26d4 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 09:43:40 -0400 Subject: [PATCH 012/106] Auto formatting --- Modules/Bar/Widgets/Tray.qml | 108 +++++++++++++++--------------- Modules/LockScreen/LockScreen.qml | 12 ---- 2 files changed, 53 insertions(+), 67 deletions(-) diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index d3dc5dd7..30a3f7b9 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -79,71 +79,69 @@ Rectangle { } opacity: status === Image.Ready ? 1 : 0 - MouseArea { - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton - onClicked: mouse => { - if (!modelData) { - return - } - - if (mouse.button === Qt.LeftButton) { - // Close any open menu first - trayPanel.close() - - if (!modelData.onlyMenu) { - modelData.activate() - } - } else if (mouse.button === Qt.MiddleButton) { - // Close any open menu first - trayPanel.close() - - modelData.secondaryActivate && modelData.secondaryActivate() - } else if (mouse.button === Qt.RightButton) { - TooltipService.hideImmediately() - - // Close the menu if it was visible - if (trayPanel && trayPanel.visible) { - trayPanel.close() + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onClicked: mouse => { + if (!modelData) { return } - if (modelData.hasMenu && modelData.menu && trayMenu.item) { - trayPanel.open() + if (mouse.button === Qt.LeftButton) { + // Close any open menu first + trayPanel.close() - // Position menu based on bar position - let menuX, menuY - if (barPosition === "left") { - // For left bar: position menu to the right of the bar - menuX = width + Style.marginM * scaling - menuY = 0 - } else if (barPosition === "right") { - // For right bar: position menu to the left of the bar - menuX = -trayMenu.item.width - Style.marginM * scaling - menuY = 0 + if (!modelData.onlyMenu) { + modelData.activate() + } + } else if (mouse.button === Qt.MiddleButton) { + // Close any open menu first + trayPanel.close() + + modelData.secondaryActivate && modelData.secondaryActivate() + } else if (mouse.button === Qt.RightButton) { + TooltipService.hideImmediately() + + // Close the menu if it was visible + if (trayPanel && trayPanel.visible) { + trayPanel.close() + return + } + + if (modelData.hasMenu && modelData.menu && trayMenu.item) { + trayPanel.open() + + // Position menu based on bar position + let menuX, menuY + if (barPosition === "left") { + // For left bar: position menu to the right of the bar + menuX = width + Style.marginM * scaling + menuY = 0 + } else if (barPosition === "right") { + // For right bar: position menu to the left of the bar + menuX = -trayMenu.item.width - Style.marginM * scaling + menuY = 0 + } else { + // For horizontal bars: center horizontally and position below + menuX = (width / 2) - (trayMenu.item.width / 2) + menuY = Math.round(Style.barHeight * scaling) + } + trayMenu.item.menu = modelData.menu + trayMenu.item.showAt(parent, menuX, menuY) } else { - // For horizontal bars: center horizontally and position below - menuX = (width / 2) - (trayMenu.item.width / 2) - menuY = Math.round(Style.barHeight * scaling) + Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set") } - trayMenu.item.menu = modelData.menu - trayMenu.item.showAt(parent, menuX, menuY) - } else { - Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set") } } - } - onEntered: { - trayPanel.close() - TooltipService.show(Screen, trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection()) + onEntered: { + trayPanel.close() + TooltipService.show(Screen, trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection()) + } + onExited: TooltipService.hide() } - onExited: TooltipService.hide() } - } - - } } } diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 38ef38ff..f1849705 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -413,12 +413,6 @@ Loader { color: Color.mOnSurface horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignHCenter - - // Connections { - // target: Time - // function onDateChanged() {// Trigger text update - // } - // } } NText { @@ -428,12 +422,6 @@ Loader { color: Color.mOnSurfaceVariant horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignHCenter - - // Connections { - // target: Time - // function onDateChanged() {// Trigger text update - // } - // } } } } From 1386920a3fd5929e6a094ffab3408a63327f926e Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 9 Oct 2025 15:44:37 +0200 Subject: [PATCH 013/106] LockScreen: add ! after Welcome back user --- Modules/LockScreen/LockScreen.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index c100d344..a100e89a 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -329,7 +329,7 @@ Loader { // Welcome back + Username on one line NText { - text: I18n.tr("lock-screen.welcome-back") + " " + Quickshell.env("USER") + text: I18n.tr("lock-screen.welcome-back") + " " + Quickshell.env("USER") + "!" pointSize: Style.fontSizeXXXL * scaling font.weight: Font.Medium color: Color.mOnSurface From 7dbb3deeeaeb11fc1f3794ffdf4fb015fe9cdf11 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 9 Oct 2025 15:48:23 +0200 Subject: [PATCH 014/106] QuickSettings: compact version uses 4 per row --- Modules/ControlCenter/Cards/TopCard.qml | 2 +- Modules/ControlCenter/ControlCenterPanel.qml | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Modules/ControlCenter/Cards/TopCard.qml b/Modules/ControlCenter/Cards/TopCard.qml index 51aeb619..643200e2 100644 --- a/Modules/ControlCenter/Cards/TopCard.qml +++ b/Modules/ControlCenter/Cards/TopCard.qml @@ -105,7 +105,7 @@ NBox { GridLayout { id: grid Layout.fillWidth: true - columns: 3 + columns: (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 4 : 3 columnSpacing: Style.marginM * scaling rowSpacing: Style.marginS * scaling diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 2e8d5375..014c9a88 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -16,12 +16,16 @@ NPanel { readonly property int bottomHeight: Math.round(Math.max(196 * scaling)) readonly property int topHeight: { - const rowsCount = Math.ceil(Settings.data.controlCenter.widgets.quickSettings.length / 3) + const columns = (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 4 : 3 + const rowsCount = Math.ceil(Settings.data.controlCenter.widgets.quickSettings.length / columns) var buttonHeight; if (Settings.data.controlCenter.quickSettingsStyle === "classic") { buttonHeight = Style.baseWidgetSize } + else if (Settings.data.controlCenter.quickSettingsStyle === "compact") { + buttonHeight = Style.baseWidgetSize * 0.8 // Smaller for compact + } else { buttonHeight = 56 } From bfb57f13c60ad615e09fb288411ee1fa3515c8a8 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 9 Oct 2025 15:59:33 +0200 Subject: [PATCH 015/106] Settings: edit default bar & quick access Autoformat --- Commons/Settings.qml | 12 ++++++------ Modules/ControlCenter/ControlCenterPanel.qml | 10 ++++------ Modules/Settings/Tabs/ControlCenterTab.qml | 2 +- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 8ab90c93..ae0a17cb 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -162,10 +162,6 @@ Singleton { "id": "Tray" }, { "id": "NotificationHistory" - }, { - "id": "WiFi" - }, { - "id": "Bluetooth" }, { "id": "Battery" }, { @@ -246,13 +242,17 @@ Singleton { property JsonObject controlCenter: JsonObject { // Position: close_to_bar_button, center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center property string position: "close_to_bar_button" - property string quickSettingsStyle: "modern" // "modern" or "classic" + property string quickSettingsStyle: "compact" // "modern", "classic", or "compact" property JsonObject widgets widgets: JsonObject { property list quickSettings: [{ + "id": "WiFi" + }, { "id": "Bluetooth" }, { - "id": "WiFi" + "id": "DoNotDisturb" + }, { + "id": "ScreenRecorder" }, { "id": "PowerProfile" }] diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 014c9a88..4f76e029 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -11,7 +11,7 @@ NPanel { id: root preferredWidth: 440 - preferredHeight: topHeight + bottomHeight + Math.round(Style.marginL * scaling * 3) + preferredHeight: topHeight + bottomHeight + Math.round(Style.marginL * scaling * 3) panelKeyboardFocus: true readonly property int bottomHeight: Math.round(Math.max(196 * scaling)) @@ -19,14 +19,12 @@ NPanel { const columns = (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 4 : 3 const rowsCount = Math.ceil(Settings.data.controlCenter.widgets.quickSettings.length / columns) - var buttonHeight; + var buttonHeight if (Settings.data.controlCenter.quickSettingsStyle === "classic") { buttonHeight = Style.baseWidgetSize - } - else if (Settings.data.controlCenter.quickSettingsStyle === "compact") { + } else if (Settings.data.controlCenter.quickSettingsStyle === "compact") { buttonHeight = Style.baseWidgetSize * 0.8 // Smaller for compact - } - else { + } else { buttonHeight = 56 } diff --git a/Modules/Settings/Tabs/ControlCenterTab.qml b/Modules/Settings/Tabs/ControlCenterTab.qml index 044f13c8..fbc968f9 100644 --- a/Modules/Settings/Tabs/ControlCenterTab.qml +++ b/Modules/Settings/Tabs/ControlCenterTab.qml @@ -52,7 +52,7 @@ ColumnLayout { "key": "compact", "name": I18n.tr("options.control-center.quickSettingsStyle.compact") }] - currentKey: Settings.data.controlCenter.quickSettingsStyle || "modern" + currentKey: Settings.data.controlCenter.quickSettingsStyle || "compact" onSelected: function (key) { Settings.data.controlCenter.quickSettingsStyle = key } From c590c2c6d9cc3254052c852e0edaecd6c1f1d3f5 Mon Sep 17 00:00:00 2001 From: David Keijser Date: Thu, 9 Oct 2025 16:45:53 +0200 Subject: [PATCH 016/106] Use workspace.num as index for sway Was incorrectly using the internal sway id of the workspace which is not the same as the user facing id Fixes #442 --- Services/SwayService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/SwayService.qml b/Services/SwayService.qml index 01727855..ad54125c 100644 --- a/Services/SwayService.qml +++ b/Services/SwayService.qml @@ -71,7 +71,7 @@ Item { const wsData = { "id": i, - "idx": ws.id, + "idx": ws.num, "name": ws.name || "", "output": (ws.monitor && ws.monitor.name) ? ws.monitor.name : "", "isActive": ws.active === true, From 3178df204e3e431aa073f7fd7e02ece4bc4d77d7 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 9 Oct 2025 16:53:31 +0200 Subject: [PATCH 017/106] MediaService: bring back playerStateMonitor --- Services/MediaService.qml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Services/MediaService.qml b/Services/MediaService.qml index 52417b43..5127b162 100644 --- a/Services/MediaService.qml +++ b/Services/MediaService.qml @@ -266,6 +266,19 @@ Singleton { } } + Timer { + id: playerStateMonitor + interval: 2000 // Check every 2 seconds + repeat: true + running: true + onTriggered: { + // Only update if we don't have a playing player or if current player is paused + if (!currentPlayer || !currentPlayer.isPlaying || currentPlayer.playbackState !== MprisPlaybackState.Playing) { + updateCurrentPlayer() + } + } + } + // Update current player when available players change Connections { target: Mpris.players From 075c8f08f6eeccf4b8ea602ac6a7cc4a5e407870 Mon Sep 17 00:00:00 2001 From: lysec Date: Thu, 9 Oct 2025 17:56:49 +0200 Subject: [PATCH 018/106] quicksettings: replace hardcoded text with i18n, edit label & tooltip, force hover when recording --- Assets/Translations/de.json | 118 ++++++++++++- Assets/Translations/en.json | 75 ++++++++ Assets/Translations/es.json | 118 ++++++++++++- Assets/Translations/fr.json | 162 +++++++++++++++++- Assets/Translations/pt.json | 118 ++++++++++++- Assets/Translations/zh-CN.json | 118 ++++++++++++- Assets/settings-default.json | 2 +- Commons/I18n.qml | 18 +- Commons/Settings.qml | 2 +- Modules/ControlCenter/Widgets/Bluetooth.qml | 4 +- Modules/ControlCenter/Widgets/KeepAwake.qml | 4 +- Modules/ControlCenter/Widgets/NightLight.qml | 12 +- .../{DoNotDisturb.qml => Notifications.qml} | 7 +- .../ControlCenter/Widgets/PowerProfile.qml | 4 +- .../ControlCenter/Widgets/ScreenRecorder.qml | 8 +- .../Widgets/WallpaperSelector.qml | 4 +- Modules/ControlCenter/Widgets/WiFi.qml | 25 ++- Modules/Settings/Tabs/ControlCenterTab.qml | 2 +- Services/ControlCenterWidgetRegistry.qml | 6 +- 19 files changed, 754 insertions(+), 53 deletions(-) rename Modules/ControlCenter/Widgets/{DoNotDisturb.qml => Notifications.qml} (51%) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index eb2f15ce..6f530c47 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -744,8 +744,52 @@ "description": "Konfigurieren und verwalten Sie Kontrollzentrum-Widgets und deren Anzeigeoptionen." } } + }, + "quickSettings": { + "notifications": { + "label": "Benachrichtigungen", + "tooltip": { + "enable": "Nicht stören aktivieren", + "disable": "Nicht stören deaktivieren" + } }, - "hooks": { + "wifi": { + "label": { + "wifi": "Wi-Fi", + "ethernet": "Netzwerk" + }, + "tooltip": { + "wifi": { + "connected": "Wi-Fi verbunden", + "disconnected": "Wi-Fi getrennt" + }, + "ethernet": { + "connected": "Ethernet verbunden" + } + } + }, + "bluetooth": { + "label": "Bluetooth", + "tooltip": { + "enabled": "Bluetooth aktiviert", + "disabled": "Bluetooth deaktiviert" + } + }, + "screenRecorder": { + "label": "Bildschirm", + "tooltip": { + "start": "Bildschirmaufnahme starten", + "stop": "Aufnahme beenden" + } + }, + "powerProfile": { + "tooltip": { + "current": "Aktuell: {profile}", + "unavailable": "Energieprofile nicht verfügbar" + } + } + }, + "hooks": { "title": "Hooks", "system-hooks": { "section": { @@ -1432,6 +1476,78 @@ "restart": "Neu starten", "suspend": "Ruhezustand" }, + "quickSettings": { + "notifications": { + "label": { + "enabled": "Benachrichtigungen", + "disabled": "Nicht stören" + }, + "tooltip": { + "action": "Linksklick: Benachrichtigungsverlauf öffnen\nRechtsklick: Nicht stören umschalten" + } + }, + "screenRecorder": { + "label": { + "recording": "Stopp", + "stopped": "Aufnehmen" + }, + "tooltip": { + "action": "Klicken zum Starten/Stoppen der Bildschirmaufnahme" + } + }, + "powerProfile": { + "label": { + "unavailable": "Energieprofil" + }, + "tooltip": { + "action": "Klicken zum Wechseln des Energieprofils" + } + }, + "wifi": { + "label": { + "ethernet": "Ethernet", + "wifi": "Wi-Fi", + "disconnected": "Wi-Fi getrennt" + }, + "tooltip": { + "action": "Klicken zum Verwalten der Wi-Fi-Verbindungen" + } + }, + "bluetooth": { + "label": { + "enabled": "Bluetooth", + "disabled": "Bluetooth" + }, + "tooltip": { + "action": "Klicken zum Verwalten der Bluetooth-Geräte" + } + }, + "nightLight": { + "label": { + "enabled": "Nachtlicht", + "forced": "Nachtlicht", + "disabled": "Nachtlicht" + }, + "tooltip": { + "action": "Klicken zum Wechseln des Nachtlicht-Modus\nRechtsklick: Einstellungen öffnen" + } + }, + "wallpaperSelector": { + "label": "Hintergrundbild", + "tooltip": { + "action": "Linksklick: Hintergrundbildauswahl öffnen\nRechtsklick: Zufälliges Hintergrundbild setzen" + } + }, + "keepAwake": { + "label": { + "enabled": "Wach halten", + "disabled": "Wach halten" + }, + "tooltip": { + "action": "Klicken zum Umschalten des Wach-halten-Modus" + } + } + }, "toast": { "night-light": { "enabled": "Aktiviert", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 4d7e9b13..b9dd76b7 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -741,6 +741,9 @@ "label": "Widgets", "description": "Configure and manage Control Center widgets and their display options." } + }, + "quickSettings": { + "sectionName": "Quick Settings" } }, "hooks": { @@ -1403,6 +1406,78 @@ "restart": "Restart", "suspend": "Suspend" }, + "quickSettings": { + "notifications": { + "label": { + "enabled": "Notifications", + "disabled": "Do Not Disturb" + }, + "tooltip": { + "action": "Left click: Open notification history\nRight click: Toggle Do Not Disturb" + } + }, + "screenRecorder": { + "label": { + "recording": "Stop", + "stopped": "Record" + }, + "tooltip": { + "action": "Click to start/stop screen recording" + } + }, + "powerProfile": { + "label": { + "unavailable": "Power Profile" + }, + "tooltip": { + "action": "Click to cycle power profile" + } + }, + "wifi": { + "label": { + "ethernet": "Ethernet", + "wifi": "Wi-Fi", + "disconnected": "Wi-Fi Disconnected" + }, + "tooltip": { + "action": "Click to manage Wi-Fi connections" + } + }, + "bluetooth": { + "label": { + "enabled": "Bluetooth", + "disabled": "Bluetooth" + }, + "tooltip": { + "action": "Click to manage Bluetooth devices" + } + }, + "nightLight": { + "label": { + "enabled": "Night Light", + "forced": "Night Light", + "disabled": "Night Light" + }, + "tooltip": { + "action": "Click to cycle Night Light mode\nRight click: Open settings" + } + }, + "wallpaperSelector": { + "label": "Wallpaper", + "tooltip": { + "action": "Left click: Open wallpaper selector\nRight click: Set random wallpaper" + } + }, + "keepAwake": { + "label": { + "enabled": "Keep Awake", + "disabled": "Keep Awake" + }, + "tooltip": { + "action": "Click to toggle keep awake mode" + } + } + }, "toast": { "night-light": { "enabled": "Enabled", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 5b904ade..c7e094ca 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -740,8 +740,52 @@ "description": "Configura y gestiona los widgets del centro de control y sus opciones de visualización." } } + }, + "quickSettings": { + "notifications": { + "label": "Notificaciones", + "tooltip": { + "enable": "Activar No molestar", + "disable": "Desactivar No molestar" + } }, - "hooks": { + "wifi": { + "label": { + "wifi": "Wi-Fi", + "ethernet": "Red" + }, + "tooltip": { + "wifi": { + "connected": "Wi-Fi conectado", + "disconnected": "Wi-Fi desconectado" + }, + "ethernet": { + "connected": "Ethernet conectado" + } + } + }, + "bluetooth": { + "label": "Bluetooth", + "tooltip": { + "enabled": "Bluetooth habilitado", + "disabled": "Bluetooth deshabilitado" + } + }, + "screenRecorder": { + "label": "Pantalla", + "tooltip": { + "start": "Iniciar grabación de pantalla", + "stop": "Detener grabación" + } + }, + "powerProfile": { + "tooltip": { + "current": "Actual: {profile}", + "unavailable": "Perfiles de energía no disponibles" + } + } + }, + "hooks": { "title": "Hooks", "system-hooks": { "section": { @@ -1400,6 +1444,78 @@ "restart": "Reiniciar", "suspend": "Suspender" }, + "quickSettings": { + "notifications": { + "label": { + "enabled": "Notificaciones", + "disabled": "No molestar" + }, + "tooltip": { + "action": "Clic izquierdo: Abrir historial de notificaciones\nClic derecho: Alternar No molestar" + } + }, + "screenRecorder": { + "label": { + "recording": "Detener", + "stopped": "Grabar" + }, + "tooltip": { + "action": "Hacer clic para iniciar/detener la grabación de pantalla" + } + }, + "powerProfile": { + "label": { + "unavailable": "Perfil de energía" + }, + "tooltip": { + "action": "Hacer clic para cambiar el perfil de energía" + } + }, + "wifi": { + "label": { + "ethernet": "Ethernet", + "wifi": "Wi-Fi", + "disconnected": "Wi-Fi desconectado" + }, + "tooltip": { + "action": "Hacer clic para gestionar las conexiones Wi-Fi" + } + }, + "bluetooth": { + "label": { + "enabled": "Bluetooth", + "disabled": "Bluetooth" + }, + "tooltip": { + "action": "Hacer clic para gestionar los dispositivos Bluetooth" + } + }, + "nightLight": { + "label": { + "enabled": "Luz nocturna", + "forced": "Luz nocturna", + "disabled": "Luz nocturna" + }, + "tooltip": { + "action": "Hacer clic para alternar el modo Luz nocturna\nClic derecho: Abrir configuración" + } + }, + "wallpaperSelector": { + "label": "Fondo de pantalla", + "tooltip": { + "action": "Clic izquierdo: Abrir selector de fondo de pantalla\nClic derecho: Establecer fondo de pantalla aleatorio" + } + }, + "keepAwake": { + "label": { + "enabled": "Mantener despierto", + "disabled": "Mantener despierto" + }, + "tooltip": { + "action": "Hacer clic para alternar el modo Mantener despierto" + } + } + }, "toast": { "night-light": { "enabled": "Activada", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index aab3dd41..1a659cb7 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -741,7 +741,95 @@ } } }, - "hooks": { + "quickSettings": { + "notifications": { + "label": "Notifications", + "tooltip": { + "enable": "Activer Ne pas déranger", + "disable": "Désactiver Ne pas déranger" + } + }, + "wifi": { + "label": { + "wifi": "Wi-Fi", + "ethernet": "Réseau" + }, + "tooltip": { + "wifi": { + "connected": "Wi-Fi connecté", + "disconnected": "Wi-Fi déconnecté" + }, + "ethernet": { + "connected": "Ethernet connecté" + } + } + }, + "bluetooth": { + "label": "Bluetooth", + "tooltip": { + "enabled": "Bluetooth activé", + "disabled": "Bluetooth désactivé" + } + }, + "screenRecorder": { + "label": "Écran", + "tooltip": { + "start": "Démarrer l'enregistrement d'écran", + "stop": "Arrêter l'enregistrement" + } + }, + "powerProfile": { + "tooltip": { + "current": "Actuel : {profile}", + "unavailable": "Profils d'alimentation non disponibles" + } + } + }, + "quickSettings": { + "notifications": { + "label": "Notifications", + "tooltip": { + "enable": "Activer Ne pas déranger", + "disable": "Désactiver Ne pas déranger" + } + }, + "wifi": { + "label": { + "wifi": "Wi-Fi", + "ethernet": "Réseau" + }, + "tooltip": { + "wifi": { + "connected": "Wi-Fi connecté", + "disconnected": "Wi-Fi déconnecté" + }, + "ethernet": { + "connected": "Ethernet connecté" + } + } + }, + "bluetooth": { + "label": "Bluetooth", + "tooltip": { + "enabled": "Bluetooth activé", + "disabled": "Bluetooth désactivé" + } + }, + "screenRecorder": { + "label": "Écran", + "tooltip": { + "start": "Démarrer l'enregistrement d'écran", + "stop": "Arrêter l'enregistrement" + } + }, + "powerProfile": { + "tooltip": { + "current": "Actuel : {profile}", + "unavailable": "Profils d'alimentation non disponibles" + } + } + }, + "hooks": { "title": "Hooks", "system-hooks": { "section": { @@ -1400,6 +1488,78 @@ "restart": "Redémarrer", "suspend": "Mettre en veille" }, + "quickSettings": { + "notifications": { + "label": { + "enabled": "Notifications", + "disabled": "Ne pas déranger" + }, + "tooltip": { + "action": "Clic gauche : Ouvrir l'historique des notifications\nClic droit : Basculer Ne pas déranger" + } + }, + "screenRecorder": { + "label": { + "recording": "Arrêter", + "stopped": "Enregistrer" + }, + "tooltip": { + "action": "Cliquer pour démarrer/arrêter l'enregistrement d'écran" + } + }, + "powerProfile": { + "label": { + "unavailable": "Profil d'alimentation" + }, + "tooltip": { + "action": "Cliquer pour changer de profil d'alimentation" + } + }, + "wifi": { + "label": { + "ethernet": "Ethernet", + "wifi": "Wi-Fi", + "disconnected": "Wi-Fi déconnecté" + }, + "tooltip": { + "action": "Cliquer pour gérer les connexions Wi-Fi" + } + }, + "bluetooth": { + "label": { + "enabled": "Bluetooth", + "disabled": "Bluetooth" + }, + "tooltip": { + "action": "Cliquer pour gérer les appareils Bluetooth" + } + }, + "nightLight": { + "label": { + "enabled": "Lumière nocturne", + "forced": "Lumière nocturne", + "disabled": "Lumière nocturne" + }, + "tooltip": { + "action": "Cliquer pour basculer le mode Lumière nocturne\nClic droit : Ouvrir les paramètres" + } + }, + "wallpaperSelector": { + "label": "Fond d'écran", + "tooltip": { + "action": "Clic gauche : Ouvrir le sélecteur de fond d'écran\nClic droit : Définir un fond d'écran aléatoire" + } + }, + "keepAwake": { + "label": { + "enabled": "Rester éveillé", + "disabled": "Rester éveillé" + }, + "tooltip": { + "action": "Cliquer pour basculer le mode Rester éveillé" + } + } + }, "toast": { "night-light": { "enabled": "Activé", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index ca66a0b4..4dcb10c7 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -706,8 +706,52 @@ "description": "Configure e gerencie os widgets do centro de controle e suas opções de exibição." } } + }, + "quickSettings": { + "notifications": { + "label": "Notificações", + "tooltip": { + "enable": "Ativar Não perturbe", + "disable": "Desativar Não perturbe" + } }, - "hooks": { + "wifi": { + "label": { + "wifi": "Wi-Fi", + "ethernet": "Rede" + }, + "tooltip": { + "wifi": { + "connected": "Wi-Fi conectado", + "disconnected": "Wi-Fi desconectado" + }, + "ethernet": { + "connected": "Ethernet conectado" + } + } + }, + "bluetooth": { + "label": "Bluetooth", + "tooltip": { + "enabled": "Bluetooth habilitado", + "disabled": "Bluetooth desabilitado" + } + }, + "screenRecorder": { + "label": "Tela", + "tooltip": { + "start": "Iniciar gravação de tela", + "stop": "Parar gravação" + } + }, + "powerProfile": { + "tooltip": { + "current": "Atual: {profile}", + "unavailable": "Perfis de energia não disponíveis" + } + } + }, + "hooks": { "title": "Hooks", "system-hooks": { "section": { @@ -1400,6 +1444,78 @@ "restart": "Reiniciar", "suspend": "Suspender" }, + "quickSettings": { + "notifications": { + "label": { + "enabled": "Notificações", + "disabled": "Não perturbar" + }, + "tooltip": { + "action": "Clique esquerdo: Abrir histórico de notificações\nClique direito: Alternar Não perturbar" + } + }, + "screenRecorder": { + "label": { + "recording": "Parar", + "stopped": "Gravar" + }, + "tooltip": { + "action": "Clique para iniciar/parar a gravação da tela" + } + }, + "powerProfile": { + "label": { + "unavailable": "Perfil de energia" + }, + "tooltip": { + "action": "Clique para alternar o perfil de energia" + } + }, + "wifi": { + "label": { + "ethernet": "Ethernet", + "wifi": "Wi-Fi", + "disconnected": "Wi-Fi desconectado" + }, + "tooltip": { + "action": "Clique para gerenciar conexões Wi-Fi" + } + }, + "bluetooth": { + "label": { + "enabled": "Bluetooth", + "disabled": "Bluetooth" + }, + "tooltip": { + "action": "Clique para gerenciar dispositivos Bluetooth" + } + }, + "nightLight": { + "label": { + "enabled": "Luz noturna", + "forced": "Luz noturna", + "disabled": "Luz noturna" + }, + "tooltip": { + "action": "Clique para alternar o modo Luz noturna\nClique direito: Abrir configurações" + } + }, + "wallpaperSelector": { + "label": "Papel de parede", + "tooltip": { + "action": "Clique esquerdo: Abrir seletor de papel de parede\nClique direito: Definir papel de parede aleatório" + } + }, + "keepAwake": { + "label": { + "enabled": "Manter acordado", + "disabled": "Manter acordado" + }, + "tooltip": { + "action": "Clique para alternar o modo Manter acordado" + } + } + }, "toast": { "night-light": { "enabled": "Ativada", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index f5ee975e..6b21676c 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -740,8 +740,52 @@ "description": "配置和管理控制中心小部件及其显示选项。" } } + }, + "quickSettings": { + "notifications": { + "label": "通知", + "tooltip": { + "enable": "开启勿扰模式", + "disable": "关闭勿扰模式" + } }, - "hooks": { + "wifi": { + "label": { + "wifi": "Wi-Fi", + "ethernet": "网络" + }, + "tooltip": { + "wifi": { + "connected": "Wi-Fi 已连接", + "disconnected": "Wi-Fi 已断开" + }, + "ethernet": { + "connected": "以太网已连接" + } + } + }, + "bluetooth": { + "label": "蓝牙", + "tooltip": { + "enabled": "蓝牙已启用", + "disabled": "蓝牙已禁用" + } + }, + "screenRecorder": { + "label": "屏幕录制", + "tooltip": { + "start": "开始屏幕录制", + "stop": "停止录制" + } + }, + "powerProfile": { + "tooltip": { + "current": "当前:{profile}", + "unavailable": "电源配置文件不可用" + } + } + }, + "hooks": { "title": "钩子", "system-hooks": { "section": { @@ -1400,6 +1444,78 @@ "restart": "重启", "suspend": "挂起" }, + "quickSettings": { + "notifications": { + "label": { + "enabled": "通知", + "disabled": "勿扰模式" + }, + "tooltip": { + "action": "左键:打开通知历史\n右键:切换勿扰模式" + } + }, + "screenRecorder": { + "label": { + "recording": "停止", + "stopped": "录制" + }, + "tooltip": { + "action": "点击开始/停止屏幕录制" + } + }, + "powerProfile": { + "label": { + "unavailable": "电源模式" + }, + "tooltip": { + "action": "点击切换电源模式" + } + }, + "wifi": { + "label": { + "ethernet": "以太网", + "wifi": "Wi-Fi", + "disconnected": "Wi-Fi 已断开" + }, + "tooltip": { + "action": "点击管理 Wi-Fi 连接" + } + }, + "bluetooth": { + "label": { + "enabled": "蓝牙", + "disabled": "蓝牙" + }, + "tooltip": { + "action": "点击管理蓝牙设备" + } + }, + "nightLight": { + "label": { + "enabled": "夜间模式", + "forced": "夜间模式", + "disabled": "夜间模式" + }, + "tooltip": { + "action": "点击切换夜间模式\n右键:打开设置" + } + }, + "wallpaperSelector": { + "label": "壁纸", + "tooltip": { + "action": "左键:打开壁纸选择器\n右键:设置随机壁纸" + } + }, + "keepAwake": { + "label": { + "enabled": "保持唤醒", + "disabled": "保持唤醒" + }, + "tooltip": { + "action": "点击切换保持唤醒模式" + } + } + }, "toast": { "night-light": { "enabled": "已启用", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 05675bd4..fd3707c4 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -123,7 +123,7 @@ "id": "Bluetooth" }, { - "id": "DoNotDisturb" + "id": "Notifications" }, { "id": "NightLight" diff --git a/Commons/I18n.qml b/Commons/I18n.qml index 3a013c57..11180047 100644 --- a/Commons/I18n.qml +++ b/Commons/I18n.qml @@ -54,6 +54,9 @@ Singleton { var data = JSON.parse(text()) root.translations = data Logger.log("I18n", `Loaded translations for "${root.langCode}"`) + if (debug) { + Logger.log("I18n", `Available root keys: ${Object.keys(data).join(", ")}`) + } root.isLoaded = true root.translationsLoaded() @@ -279,9 +282,9 @@ Singleton { interpolations = {} if (!isLoaded) { - // if (debug) { - // Logger.warn("I18n", "Translations not loaded yet") - // } + if (debug) { + Logger.warn("I18n", "Translations not loaded yet") + } return key } @@ -291,12 +294,19 @@ Singleton { // Look-up translation in the active language var value = translations var notFound = false + if (debug) { + Logger.log("I18n", `Looking up key: "${key}"`) + } for (var i = 0; i < keys.length; i++) { if (value && typeof value === "object" && keys[i] in value) { value = value[keys[i]] + if (debug) { + Logger.log("I18n", `Found key part "${keys[i]}"`) + } } else { if (debug) { - Logger.warn("I18n", `Translation key "${key}" not found`) + Logger.warn("I18n", `Translation key "${key}" not found at part "${keys[i]}"`) + Logger.warn("I18n", `Available keys: ${Object.keys(value || {}).join(", ")}`) } notFound = true break diff --git a/Commons/Settings.qml b/Commons/Settings.qml index ae0a17cb..ffe9039a 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -250,7 +250,7 @@ Singleton { }, { "id": "Bluetooth" }, { - "id": "DoNotDisturb" + "id": "Notifications" }, { "id": "ScreenRecorder" }, { diff --git a/Modules/ControlCenter/Widgets/Bluetooth.qml b/Modules/ControlCenter/Widgets/Bluetooth.qml index 9b1cd17d..e44d9844 100644 --- a/Modules/ControlCenter/Widgets/Bluetooth.qml +++ b/Modules/ControlCenter/Widgets/Bluetooth.qml @@ -8,12 +8,12 @@ NQuickSetting { property ShellScreen screen property real scaling: 1.0 - text: "Bluetooth" + text: I18n.tr("quickSettings.bluetooth.label.enabled") fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off" active: BluetoothService.enabled - tooltipText: BluetoothService.enabled ? "Bluetooth enabled" : "Bluetooth disabled" + tooltipText: I18n.tr("quickSettings.bluetooth.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this) diff --git a/Modules/ControlCenter/Widgets/KeepAwake.qml b/Modules/ControlCenter/Widgets/KeepAwake.qml index a254f8df..e384272a 100644 --- a/Modules/ControlCenter/Widgets/KeepAwake.qml +++ b/Modules/ControlCenter/Widgets/KeepAwake.qml @@ -8,12 +8,12 @@ NQuickSetting { property ShellScreen screen property real scaling: 1.0 - text: "Keep-awake" + text: I18n.tr("quickSettings.keepAwake.label.enabled") fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off" active: IdleInhibitorService.isInhibited - tooltipText: IdleInhibitorService.isInhibited ? "Disable keep-awake" : "Enable keep-awake" + tooltipText: I18n.tr("quickSettings.keepAwake.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" onClicked: IdleInhibitorService.manualToggle() diff --git a/Modules/ControlCenter/Widgets/NightLight.qml b/Modules/ControlCenter/Widgets/NightLight.qml index 21077f37..a2d6bfc1 100644 --- a/Modules/ControlCenter/Widgets/NightLight.qml +++ b/Modules/ControlCenter/Widgets/NightLight.qml @@ -9,21 +9,13 @@ NQuickSetting { property real scaling: 1.0 enabled: ProgramCheckerService.wlsunsetAvailable - text: "Night Light" + text: I18n.tr("quickSettings.nightLight.label.enabled") fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off" active: Settings.data.nightLight.enabled style: Settings.data.controlCenter.quickSettingsStyle || "modern" - tooltipText: { - if (!Settings.data.nightLight.enabled) { - return "Turn on Night Light" - } else if (Settings.data.nightLight.forced) { - return "Night Light forced on" - } else { - return "Turn off Night Light" - } - } + tooltipText: I18n.tr("quickSettings.nightLight.tooltip.action") onClicked: { if (!Settings.data.nightLight.enabled) { diff --git a/Modules/ControlCenter/Widgets/DoNotDisturb.qml b/Modules/ControlCenter/Widgets/Notifications.qml similarity index 51% rename from Modules/ControlCenter/Widgets/DoNotDisturb.qml rename to Modules/ControlCenter/Widgets/Notifications.qml index cc2a1d1a..131db7f9 100644 --- a/Modules/ControlCenter/Widgets/DoNotDisturb.qml +++ b/Modules/ControlCenter/Widgets/Notifications.qml @@ -8,13 +8,14 @@ NQuickSetting { property ShellScreen screen property real scaling: 1.0 - text: "Do not Disturb" + text: Settings.data.notifications.doNotDisturb ? I18n.tr("quickSettings.notifications.label.disabled") : I18n.tr("quickSettings.notifications.label.enabled") fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell" active: Settings.data.notifications.doNotDisturb - tooltipText: Settings.data.notifications.doNotDisturb ? "Turn off Do Not Disturb" : "Turn on Do Not Disturb" + tooltipText: I18n.tr("quickSettings.notifications.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" - onClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb + onClicked: PanelService.getPanel("notificationHistoryPanel")?.toggle(this) + onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb } diff --git a/Modules/ControlCenter/Widgets/PowerProfile.qml b/Modules/ControlCenter/Widgets/PowerProfile.qml index 2636d40e..1c57f9fa 100644 --- a/Modules/ControlCenter/Widgets/PowerProfile.qml +++ b/Modules/ControlCenter/Widgets/PowerProfile.qml @@ -12,12 +12,12 @@ NQuickSetting { readonly property bool hasPP: PowerProfileService.available enabled: hasPP - text: PowerProfileService.getName() + text: hasPP ? PowerProfileService.getName() : I18n.tr("quickSettings.powerProfile.label.unavailable") fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium icon: PowerProfileService.getIcon() active: hasPP - tooltipText: hasPP ? "Current: " + PowerProfileService.getName() : "Power profiles not available" + tooltipText: I18n.tr("quickSettings.powerProfile.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" onClicked: { diff --git a/Modules/ControlCenter/Widgets/ScreenRecorder.qml b/Modules/ControlCenter/Widgets/ScreenRecorder.qml index 0523bf7e..a9560bdf 100644 --- a/Modules/ControlCenter/Widgets/ScreenRecorder.qml +++ b/Modules/ControlCenter/Widgets/ScreenRecorder.qml @@ -10,13 +10,17 @@ NQuickSetting { enabled: ProgramCheckerService.gpuScreenRecorderAvailable icon: "camera-video" - text: "Screen Rec." + text: ScreenRecorderService.isRecording ? I18n.tr("quickSettings.screenRecorder.label.recording") : I18n.tr("quickSettings.screenRecorder.label.stopped") fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium active: ScreenRecorderService.isRecording - tooltipText: ScreenRecorderService.isRecording ? "Stop recording" : "Start screen recording" + tooltipText: I18n.tr("quickSettings.screenRecorder.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" + // Force hover state when recording to get hover colors + property bool originalHovered: hovered + hovered: ScreenRecorderService.isRecording || originalHovered + onClicked: { ScreenRecorderService.toggleRecording() if (!ScreenRecorderService.isRecording) { diff --git a/Modules/ControlCenter/Widgets/WallpaperSelector.qml b/Modules/ControlCenter/Widgets/WallpaperSelector.qml index 9ec4db51..a4639383 100644 --- a/Modules/ControlCenter/Widgets/WallpaperSelector.qml +++ b/Modules/ControlCenter/Widgets/WallpaperSelector.qml @@ -10,11 +10,11 @@ NQuickSetting { enabled: Settings.data.wallpaper.enabled icon: "wallpaper-selector" - text: "Wallpaper" + text: I18n.tr("quickSettings.wallpaperSelector.label") fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium active: Settings.data.wallpaper.enabled - tooltipText: "Open wallpaper selector" + tooltipText: I18n.tr("quickSettings.wallpaperSelector.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this) diff --git a/Modules/ControlCenter/Widgets/WiFi.qml b/Modules/ControlCenter/Widgets/WiFi.qml index 30a4f635..9d896a5c 100644 --- a/Modules/ControlCenter/Widgets/WiFi.qml +++ b/Modules/ControlCenter/Widgets/WiFi.qml @@ -31,9 +31,16 @@ NQuickSetting { text: { if (NetworkService.ethernetConnected) { - return "Network" + return I18n.tr("quickSettings.wifi.label.ethernet") } - return "Wi-Fi" + let connected = false + for (const net in NetworkService.networks) { + if (NetworkService.networks[net].connected) { + connected = true + break + } + } + return connected ? I18n.tr("quickSettings.wifi.label.wifi") : I18n.tr("quickSettings.wifi.label.disconnected") } fontSize: Style.fontSizeS * scaling @@ -56,19 +63,7 @@ NQuickSetting { } } - tooltipText: { - if (NetworkService.ethernetConnected) { - return "Ethernet connected" - } - let connected = false - for (const net in NetworkService.networks) { - if (NetworkService.networks[net].connected) { - connected = true - break - } - } - return connected ? "Wi-Fi connected" : "Wi-Fi disconnected" - } + tooltipText: I18n.tr("quickSettings.wifi.tooltip.action") onClicked: PanelService.getPanel("wifiPanel")?.toggle(this) } diff --git a/Modules/Settings/Tabs/ControlCenterTab.qml b/Modules/Settings/Tabs/ControlCenterTab.qml index fbc968f9..8b7fe265 100644 --- a/Modules/Settings/Tabs/ControlCenterTab.qml +++ b/Modules/Settings/Tabs/ControlCenterTab.qml @@ -84,7 +84,7 @@ ColumnLayout { // Quick Settings SectionEditor { - sectionName: "Quick Settings" + sectionName: I18n.tr("settings.control-center.quickSettings.sectionName") sectionId: "quickSettings" settingsDialogComponent: "" widgetRegistry: ControlCenterWidgetRegistry diff --git a/Services/ControlCenterWidgetRegistry.qml b/Services/ControlCenterWidgetRegistry.qml index 4ed3d2ae..ce2fcbf1 100644 --- a/Services/ControlCenterWidgetRegistry.qml +++ b/Services/ControlCenterWidgetRegistry.qml @@ -11,7 +11,7 @@ Singleton { // Widget registry object mapping widget names to components property var widgets: ({ "Bluetooth": bluetoothComponent, - "DoNotDisturb": doNotDisturbComponent, + "Notifications": notificationsComponent, "KeepAwake": keepAwakeComponent, "NightLight": nightLightComponent, "PowerProfile": powerProfileComponent, @@ -26,8 +26,8 @@ Singleton { property Component bluetoothComponent: Component { Bluetooth {} } - property Component doNotDisturbComponent: Component { - DoNotDisturb {} + property Component notificationsComponent: Component { + Notifications {} } property Component keepAwakeComponent: Component { KeepAwake {} From b34f97130621961e7bce4f1923a853ece64f2302 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 01:00:46 +0800 Subject: [PATCH 019/106] feat(tray): Implement core blacklist filtering logic --- Modules/Bar/Widgets/Tray.qml | 68 ++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 30a3f7b9..85e42afc 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -19,7 +19,51 @@ Rectangle { readonly property string barPosition: Settings.data.bar.position readonly property bool isVertical: barPosition === "left" || barPosition === "right" readonly property bool compact: (Settings.data.bar.density === "compact") - readonly property real itemSize: isVertical ? Math.round(width * 0.7) : Math.round(height * 0.7) + property real itemSize: isVertical ? Math.round(width * 0.7) : Math.round(height * 0.7) + property list blacklist: Settings.data.bar.trayBlacklist || [] // Read from settings + property var filteredItems: [] + + function wildCardMatch(str, rule) { + return str.toLowerCase().includes(rule.toLowerCase()); // Simple substring match + } + + function updateFilteredItems() { + if (!root.blacklist || root.blacklist.length === 0) { + if (SystemTray.items && SystemTray.items.values) { + filteredItems = SystemTray.items.values + } else { + filteredItems = [] + } + return + } + + let newItems = [] + if (SystemTray.items && SystemTray.items.values) { + const trayItems = SystemTray.items.values + for (var i = 0; i < trayItems.length; i++) { + const item = trayItems[i] + if (!item) { + continue + } + + const title = item.tooltipTitle || item.name || item.id || "" + + let isBlacklisted = false + for (var j = 0; j < root.blacklist.length; j++) { + const rule = root.blacklist[j] + if (wildCardMatch(title, rule)) { + isBlacklisted = true + break + } + } + + if (!isBlacklisted) { + newItems.push(item) + } + } + } + filteredItems = newItems + } function onLoaded() { // When the widget is fully initialized with its props set the screen for the trayMenu @@ -28,7 +72,25 @@ Rectangle { } } - visible: SystemTray.items.values.length > 0 + Connections { + target: SystemTray.items + function onValuesChanged() { + root.updateFilteredItems() + } + } + + Connections { + target: Settings + function onSettingsSaved() { + root.updateFilteredItems() + } + } + + Component.onCompleted: { + root.updateFilteredItems() // Initial update + } + + visible: filteredItems.length > 0 implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : (trayFlow.implicitWidth + Style.marginS * scaling * 2) implicitHeight: isVertical ? (trayFlow.implicitHeight + Style.marginS * scaling * 2) : Math.round(Style.capsuleHeight * scaling) radius: Math.round(Style.radiusM * scaling) @@ -44,7 +106,7 @@ Rectangle { Repeater { id: repeater - model: SystemTray.items + model: filteredItems delegate: Item { width: itemSize From 8172b901cd3c7fe7c686bd8f0f314ebf8a065c15 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 01:01:27 +0800 Subject: [PATCH 020/106] feat(settings): Integrate tray blacklist with global settings --- Assets/settings-default.json | 1 + Commons/Settings.qml | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index a80e2bb4..d7791e57 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -9,6 +9,7 @@ "floating": false, "marginVertical": 0.25, "marginHorizontal": 0.25, + "trayBlacklist": [], "widgets": { "left": [ { diff --git a/Commons/Settings.qml b/Commons/Settings.qml index c0939caf..04e9f507 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -33,6 +33,7 @@ Singleton { // Signal emitted when settings are loaded after startupcale changes signal settingsLoaded + signal settingsSaved // ----------------------------------------------------- // ----------------------------------------------------- @@ -76,6 +77,7 @@ Singleton { if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) { settingsFallbackFileView.writeAdapter() } + root.settingsSaved() // Emit signal after saving } } @@ -142,6 +144,7 @@ Singleton { property bool floating: false property real marginVertical: 0.25 property real marginHorizontal: 0.25 + property list trayBlacklist: [] // Widget configuration for modular bar system property JsonObject widgets @@ -351,6 +354,17 @@ Singleton { } } + // ----------------------------------------------------- + // Public function to trigger immediate settings saving + function saveImmediate() { + settingsFileView.writeAdapter() + // Write to fallback location if set + if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) { + settingsFallbackFileView.writeAdapter() + } + root.settingsSaved() // Emit signal after saving + } + // ----------------------------------------------------- // Generate default settings at the root of the repo function generateDefaultSettings() { From 85043d537047b5fc01feccf20071dc8c034004db Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 01:01:44 +0800 Subject: [PATCH 021/106] feat(ui): Implement Tray widget settings UI --- .../Settings/Bar/BarWidgetSettingsDialog.qml | 10 +- .../Bar/WidgetSettings/TraySettings.qml | 111 ++++++++++++++++++ Services/BarWidgetRegistry.qml | 3 + 3 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 Modules/Settings/Bar/WidgetSettings/TraySettings.qml diff --git a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml index 1c2a1aaf..06729c66 100644 --- a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml +++ b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml @@ -110,7 +110,12 @@ Popup { onClicked: { if (settingsLoader.item && settingsLoader.item.saveSettings) { var newSettings = settingsLoader.item.saveSettings() - root.updateWidgetSettings(sectionId, widgetSettings.widgetIndex, newSettings) + if (widgetSettings.widgetId === "Tray") { + Settings.data.bar.trayBlacklist = newSettings.blacklist || [] + Settings.saveImmediate() + } else { + root.updateWidgetSettings(sectionId, widgetSettings.widgetIndex, newSettings) + } widgetSettings.close() } } @@ -134,7 +139,8 @@ Popup { "SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml", "Volume": "WidgetSettings/VolumeSettings.qml", "Workspace": "WidgetSettings/WorkspaceSettings.qml", - "Taskbar": "WidgetSettings/TaskbarSettings.qml" + "Taskbar": "WidgetSettings/TaskbarSettings.qml", + "Tray": "WidgetSettings/TraySettings.qml" } const source = widgetSettingsMap[widgetId] diff --git a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml new file mode 100644 index 00000000..f0efd28b --- /dev/null +++ b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml @@ -0,0 +1,111 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +ColumnLayout { + // Properties to receive data from parent + property var widgetData: ({}) // Expected by BarWidgetSettingsDialog + property var widgetMetadata: ({}) // Expected by BarWidgetSettingsDialog + + // Local state for the blacklist + property var localBlacklist: widgetData.blacklist || Settings.data.bar.trayBlacklist || [] + + ListModel { + id: blacklistModel + } + + Component.onCompleted: { + // Populate the ListModel from localBlacklist + for (var i = 0; i < localBlacklist.length; i++) { + blacklistModel.append({"rule": localBlacklist[i]}) + } + } + + spacing: Style.marginM * scaling + + // Input for new blacklist items + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS * scaling + + NTextInput { + id: newRuleInput + Layout.fillWidth: true + placeholderText: I18n.tr("settings.bar.widget-settings.tray.blacklist.placeholder") + } + + NIconButton { + icon: "add" + enabled: newRuleInput.text.length > 0 + onClicked: { + if (newRuleInput.text.length > 0) { + var newRule = newRuleInput.text.trim() + var exists = false + for (var i = 0; i < blacklistModel.count; i++) { + if (blacklistModel.get(i).rule === newRule) { + exists = true + break + } + } + if (!exists) { + blacklistModel.append({"rule": newRule}) + newRuleInput.text = "" + } + } + } + } + } + + // List of current blacklist items + ListView { + Layout.fillWidth: true + Layout.preferredHeight: 150 * scaling + clip: true + model: blacklistModel + delegate: Rectangle { + width: ListView.width + height: 40 * scaling + color: Color.transparent // Make background transparent + visible: model.rule !== undefined && model.rule !== "" // Only visible if rule exists + + RowLayout { + anchors.fill: parent + anchors.leftMargin: Style.marginM * scaling + anchors.rightMargin: Style.marginS * scaling + spacing: Style.marginS * scaling + + NText { + Layout.fillWidth: true + text: model.rule + elide: Text.ElideRight + } + + NIconButton { + Layout.alignment: Qt.AlignRight + icon: "close" + baseSize: 24 * scaling + colorBg: Color.transparent + colorFg: Color.mError + onClicked: { + blacklistModel.remove(index) + } + } + } + } + } + + // This function will be called by the dialog to get the new settings + function saveSettings() { + var newBlacklist = [] + for (var i = 0; i < blacklistModel.count; i++) { + newBlacklist.push(blacklistModel.get(i).rule) + } + + // Return the updated settings for this widget instance + var settings = Object.assign({}, widgetData || {}) + settings.blacklist = newBlacklist + return settings + } +} \ No newline at end of file diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index cddb544a..799a5ce0 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -116,6 +116,9 @@ Singleton { "onlySameOutput": true, "onlyActiveWorkspaces": true }, + "Tray": { + "allowUserSettings": true + }, "Workspace": { "allowUserSettings": true, "labelMode": "index", From c986b3426864e45a29ea87be95548442f272cd0d Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 01:26:17 +0800 Subject: [PATCH 022/106] feat(i18n): Add English translations for tray blacklist --- Assets/Translations/en.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index ff91166a..7cc99f0b 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -269,6 +269,13 @@ "label": "Monitors display", "description": "Show bar on specific monitors. Defaults to all if none are chosen." } + }, + "tray": { + "blacklist": { + "label": "Blacklist", + "description": "Add tray exclusion rules, supports wildcards (*).", + "placeholder": "e.g., nm-applet, Fcitx*" + } } }, "dock": { From 27cacdff17e99b19b3f98a1c38edc37d7f4b182d Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 01:41:24 +0800 Subject: [PATCH 023/106] feat(i18n): update tray blacklist translation in German --- Assets/Translations/de.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 021a823f..49612882 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -271,6 +271,13 @@ "label": "Nur Apps vom gleichen Bildschirm", "description": "Zeige nur Apps vom dem Bildschirm an, wo sich das Dock befindet." } + }, + "tray": { + "blacklist": { + "label": "Ausschlussliste", + "description": "Füge Ausschlussregeln für die Tray-Symbolleiste hinzu, unterstützt Platzhalter (*).", + "placeholder": "z.B., nm-applet, Fcitx*" + } } }, "dock": { From b406f1ecf218f5fa8b8904ad8bd6f5fb72f04fc8 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 01:42:27 +0800 Subject: [PATCH 024/106] feat(i18n): update tray blacklist translation in Spanish --- Assets/Translations/es.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index f33d2e0c..cfd6174c 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -267,6 +267,13 @@ "label": "Visualización en monitores", "description": "Muestra la barra en monitores específicos. Por defecto, se muestra en todos si no se elige ninguno." } + }, + "tray": { + "blacklist": { + "label": "Lista negra", + "description": "Agregar reglas de exclusión de la bandeja, admite comodines (*).", + "placeholder": "ej., nm-applet, Fcitx*" + } } }, "dock": { From cc20a7f7337e582243c2e5caf1964304b4ee41a1 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 01:43:05 +0800 Subject: [PATCH 025/106] feat(i18n): update tray blacklist translation in French --- Assets/Translations/fr.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index a087896a..83689471 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -264,9 +264,16 @@ }, "monitors": { "section": { - "label": "Affichage sur les moniteur", + "label": "Affichage sur les moniteurs", "description": "Afficher la barre sur des moniteurs spécifiques. Par défaut, sur tous si aucun n'est choisi." } + }, + "tray": { + "blacklist": { + "label": "Liste noire", + "description": "Ajouter des règles d'exclusion pour la boîte à miniatures, prend en charge les caractères génériques (*).", + "placeholder": "ex: nm-applet, Fcitx*" + } } }, "dock": { From d1d70ca428333525d6a4187dade8967a9f48841e Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 01:43:45 +0800 Subject: [PATCH 026/106] feat(i18n): update tray blacklist translation in Portuguese --- Assets/Translations/pt.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index d9b1d538..c27bb52e 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -267,6 +267,13 @@ "label": "Exibição nos monitores", "description": "Mostra a barra em monitores específicos. O padrão é todos, se nenhum for escolhido." } + }, + "tray": { + "blacklist": { + "label": "Lista Negra", + "description": "Adicione regras de exclusão para a bandeja do sistema, suporta curingas (*).", + "placeholder": "ex: nm-applet, Fcitx*" + } } }, "dock": { From 8cb9a5082e7b04af5c4cf223fec07bbb76de7e9b Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 01:44:31 +0800 Subject: [PATCH 027/106] feat(i18n): update tray blacklist translation in Chinese Simplified --- Assets/Translations/zh-CN.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 8bdfbce4..e35bb440 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -267,6 +267,13 @@ "label": "显示器显示", "description": "在特定显示器上显示状态栏。如果未选择,则默认为全部。" } + }, + "tray": { + "blacklist": { + "label": "黑名单", + "description": "添加托盘排除规则,支持通配符 (*)。", + "placeholder": "例如:nm-applet, Fcitx*" + } } }, "dock": { From 5de6560d421d02a05b6f688f825cd37c2ebdc9d9 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 01:57:17 +0800 Subject: [PATCH 028/106] fix(ui): Correct TraySettings label and description --- .../Settings/Bar/WidgetSettings/TraySettings.qml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml index f0efd28b..40019e80 100644 --- a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml @@ -30,10 +30,12 @@ ColumnLayout { Layout.fillWidth: true spacing: Style.marginS * scaling - NTextInput { - id: newRuleInput - Layout.fillWidth: true - placeholderText: I18n.tr("settings.bar.widget-settings.tray.blacklist.placeholder") + NTextInput { + id: newRuleInput + Layout.fillWidth: true + label: I18n.tr("settings.bar.tray.blacklist.label") + description: I18n.tr("settings.bar.tray.blacklist.description") + placeholderText: I18n.tr("settings.bar.tray.blacklist.placeholder") } NIconButton { @@ -69,7 +71,7 @@ ColumnLayout { height: 40 * scaling color: Color.transparent // Make background transparent visible: model.rule !== undefined && model.rule !== "" // Only visible if rule exists - + RowLayout { anchors.fill: parent anchors.leftMargin: Style.marginM * scaling @@ -108,4 +110,4 @@ ColumnLayout { settings.blacklist = newBlacklist return settings } -} \ No newline at end of file +} From 1d86fdc0989e4bf851f4d939626c9da7f0973429 Mon Sep 17 00:00:00 2001 From: Sridou Date: Thu, 9 Oct 2025 23:27:23 +0530 Subject: [PATCH 029/106] updated foot theme for pre-defined color schemes --- Assets/ColorScheme/Ayu/terminal/foot/Ayu-dark | 6 ++---- Assets/ColorScheme/Ayu/terminal/foot/Ayu-light | 4 +--- Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-dark | 4 +--- .../ColorScheme/Catppuccin/terminal/foot/Catppuccin-light | 4 +--- Assets/ColorScheme/Dracula/terminal/foot/Dracula-dark | 6 ++---- Assets/ColorScheme/Dracula/terminal/foot/Dracula-light | 4 +--- Assets/ColorScheme/Everforest/terminal/foot/Everforest-dark | 6 ++---- .../ColorScheme/Everforest/terminal/foot/Everforest-light | 6 ++---- Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-dark | 6 ++---- Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-light | 6 ++---- Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-dark | 6 ++---- Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-light | 4 +--- Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-dark | 4 +--- .../ColorScheme/Monochrome/terminal/foot/Monochrome-light | 4 +--- .../Noctalia-default/terminal/foot/Noctalia-default-dark | 4 +--- .../Noctalia-default/terminal/foot/Noctalia-default-light | 4 +--- .../Noctalia-legacy/terminal/foot/Noctalia-legacy-dark | 4 +--- .../Noctalia-legacy/terminal/foot/Noctalia-legacy-light | 4 +--- Assets/ColorScheme/Nord/terminal/foot/Nord-dark | 4 +--- Assets/ColorScheme/Nord/terminal/foot/Nord-light | 4 +--- Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-dark | 4 +--- Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-light | 4 +--- Assets/ColorScheme/Solarized/terminal/foot/Solarized-dark | 6 ++---- Assets/ColorScheme/Solarized/terminal/foot/Solarized-light | 6 ++---- .../ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-dark | 6 ++---- .../ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-light | 6 ++---- 26 files changed, 37 insertions(+), 89 deletions(-) diff --git a/Assets/ColorScheme/Ayu/terminal/foot/Ayu-dark b/Assets/ColorScheme/Ayu/terminal/foot/Ayu-dark index 9b2c9351..db50321a 100644 --- a/Assets/ColorScheme/Ayu/terminal/foot/Ayu-dark +++ b/Assets/ColorScheme/Ayu/terminal/foot/Ayu-dark @@ -1,6 +1,3 @@ -[cursor] -color=1f2430 ffcc66 - [colors] foreground=cccac2 background=1f2430 @@ -21,4 +18,5 @@ bright5=dfbfff bright6=95e6cb bright7=ffffff selection-foreground=1f2430 -selection-background=409fff \ No newline at end of file +selection-background=409fff +cursor=1f2430 ffcc66 diff --git a/Assets/ColorScheme/Ayu/terminal/foot/Ayu-light b/Assets/ColorScheme/Ayu/terminal/foot/Ayu-light index 4b3a615c..0686b00b 100644 --- a/Assets/ColorScheme/Ayu/terminal/foot/Ayu-light +++ b/Assets/ColorScheme/Ayu/terminal/foot/Ayu-light @@ -1,6 +1,3 @@ -[cursor] -color=f8f9fa ffaa33 - [colors] foreground=5c6166 background=f8f9fa @@ -22,3 +19,4 @@ bright6=4cbf99 bright7=d1d1d1 selection-foreground=f8f9fa selection-background=035bd6 +cursor=f8f9fa ffaa33 diff --git a/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-dark b/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-dark index 593188f9..6e30e18c 100644 --- a/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-dark +++ b/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-dark @@ -1,6 +1,3 @@ -[cursor] -color=1e1e2e f5e0dc - [colors] foreground=cdd6f4 background=1e1e2e @@ -22,3 +19,4 @@ bright6=6bd7ca bright7=bac2de selection-foreground=cdd6f4 selection-background=585b70 +cursor=1e1e2e f5e0dc diff --git a/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-light b/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-light index afe3025b..6877ab82 100644 --- a/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-light +++ b/Assets/ColorScheme/Catppuccin/terminal/foot/Catppuccin-light @@ -1,6 +1,3 @@ -[cursor] -color=303446 f2d5cf - [colors] foreground=c6d0f5 background=303446 @@ -22,3 +19,4 @@ bright6=5abfb5 bright7=b5bfe2 selection-foreground=c6d0f5 selection-background=626880 +cursor=303446 f2d5cf diff --git a/Assets/ColorScheme/Dracula/terminal/foot/Dracula-dark b/Assets/ColorScheme/Dracula/terminal/foot/Dracula-dark index 56d26b00..64ead8a4 100644 --- a/Assets/ColorScheme/Dracula/terminal/foot/Dracula-dark +++ b/Assets/ColorScheme/Dracula/terminal/foot/Dracula-dark @@ -1,6 +1,3 @@ -[cursor] -color=282a36 f8f8f2 - [colors] foreground=f8f8f2 background=282a36 @@ -21,4 +18,5 @@ bright5=ff92df bright6=a4ffff bright7=ffffff selection-foreground=ffffff -selection-background=44475a \ No newline at end of file +selection-background=44475a +cursor=282a36 f8f8f2 diff --git a/Assets/ColorScheme/Dracula/terminal/foot/Dracula-light b/Assets/ColorScheme/Dracula/terminal/foot/Dracula-light index 92d94f63..a8214cec 100644 --- a/Assets/ColorScheme/Dracula/terminal/foot/Dracula-light +++ b/Assets/ColorScheme/Dracula/terminal/foot/Dracula-light @@ -1,6 +1,3 @@ -[cursor] -color=ffffff 282a36 - [colors] foreground=282a36 background=ffffff @@ -22,3 +19,4 @@ bright6=a4ffff bright7=000000 selection-foreground=ffffff selection-background=6272a4 +cursor=ffffff 282a36 diff --git a/Assets/ColorScheme/Everforest/terminal/foot/Everforest-dark b/Assets/ColorScheme/Everforest/terminal/foot/Everforest-dark index 5d34d399..f049a598 100644 --- a/Assets/ColorScheme/Everforest/terminal/foot/Everforest-dark +++ b/Assets/ColorScheme/Everforest/terminal/foot/Everforest-dark @@ -1,6 +1,3 @@ -[cursor] -color=4c3743 e69875 - [colors] foreground=d3c6aa background=1e2326 @@ -21,4 +18,5 @@ bright5=df69ba bright6=35a77c bright7=fffbef selection-foreground=d3c6aa -selection-background=4c3743 \ No newline at end of file +selection-background=4c3743 +cursor=4c3743 e69875 diff --git a/Assets/ColorScheme/Everforest/terminal/foot/Everforest-light b/Assets/ColorScheme/Everforest/terminal/foot/Everforest-light index 56721a1e..c1af0ad7 100644 --- a/Assets/ColorScheme/Everforest/terminal/foot/Everforest-light +++ b/Assets/ColorScheme/Everforest/terminal/foot/Everforest-light @@ -1,6 +1,3 @@ -[cursor] -color=eaedc8 f57d26 - [colors] foreground=5c6a72 background=efebd4 @@ -21,4 +18,5 @@ bright5=df69ba bright6=35a77c bright7=fffbef selection-foreground=5c6a72 -selection-background=eaedc8 \ No newline at end of file +selection-background=eaedc8 +cursor=eaedc8 f57d26 diff --git a/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-dark b/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-dark index ca3205bc..aee8ef7f 100644 --- a/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-dark +++ b/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-dark @@ -1,7 +1,4 @@ -[cursor] -color=282828 ebdbb2 - [colors] foreground=ebdbb2 background=282828 @@ -22,4 +19,5 @@ bright5=d3869b bright6=8ec07c bright7=ebdbb2 selection-foreground=ebdbb2 -selection-background=665c54 \ No newline at end of file +selection-background=665c54 +cursor=282828 ebdbb2 diff --git a/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-light b/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-light index 8943a299..94bda7a7 100644 --- a/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-light +++ b/Assets/ColorScheme/Gruvbox/terminal/foot/Gruvbox-light @@ -1,7 +1,4 @@ -[cursor] -color=625e5c 3c3836 - [colors] foreground=3c3836 background=fbf1c7 @@ -22,4 +19,5 @@ bright5=8f3f71 bright6=427b58 bright7=3c3836 selection-foreground=fbf1c7 -selection-background=3c3836 \ No newline at end of file +selection-background=3c3836 +cursor=625e5c 3c3836 diff --git a/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-dark b/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-dark index e8b46621..fbfe1f69 100644 --- a/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-dark +++ b/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-dark @@ -1,6 +1,3 @@ -[cursor] -color=1f1f28 e6e0c2 - [colors] foreground=ddd8bb background=1f1f28 @@ -21,4 +18,5 @@ bright5=a98fd2 bright6=7bc2df bright7=a8a48d selection-foreground=ddd8bb -selection-background=49473e \ No newline at end of file +selection-background=49473e +cursor=1f1f28 e6e0c2 diff --git a/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-light b/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-light index e852a960..b3712eee 100644 --- a/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-light +++ b/Assets/ColorScheme/Kanagawa/terminal/foot/Kanagawa-light @@ -1,6 +1,3 @@ -[cursor] -color=f2ecbc 43436c - [colors] foreground=545464 background=f2ecbc @@ -22,3 +19,4 @@ bright6=5e857a bright7=43436c selection-foreground=f2ecbc selection-background=c9cbd1 +cursor=f2ecbc 43436c diff --git a/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-dark b/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-dark index 7f5b2ccf..08182714 100644 --- a/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-dark +++ b/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-dark @@ -1,6 +1,3 @@ -[cursor] -color=111111 aaaaaa - [colors] foreground=828282 background=111111 @@ -22,3 +19,4 @@ bright6=cccccc bright7=ffffff selection-foreground=111111 selection-background=828282 +cursor=111111 aaaaaa diff --git a/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-light b/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-light index 479082cb..bf57181b 100644 --- a/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-light +++ b/Assets/ColorScheme/Monochrome/terminal/foot/Monochrome-light @@ -1,6 +1,3 @@ -[cursor] -color=d4d4d4 555555 - [colors] foreground=696969 background=d4d4d4 @@ -22,3 +19,4 @@ bright6=333333 bright7=000000 selection-foreground=d4d4d4 selection-background=696969 +cursor=d4d4d4 555555 diff --git a/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-dark b/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-dark index 7f528b8f..86da6444 100644 --- a/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-dark +++ b/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-dark @@ -1,6 +1,3 @@ -[cursor] -color=070722 fff59b - [colors] foreground=f3edf7 background=070722 @@ -22,3 +19,4 @@ bright6=9BFECE bright7=ffffff selection-foreground=070722 selection-background=f3edf7 +cursor=070722 fff59b diff --git a/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-light b/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-light index a8d78846..ee2e271d 100644 --- a/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-light +++ b/Assets/ColorScheme/Noctalia-default/terminal/foot/Noctalia-default-light @@ -1,6 +1,3 @@ -[cursor] -color=e6e8fa 5d65f5 - [colors] foreground=4b55c8 background=e6e8fa @@ -22,3 +19,4 @@ bright6=0e0e43 bright7=0e0e43 selection-foreground=e6e8fa selection-background=4b55c8 +cursor=e6e8fa 5d65f5 diff --git a/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-dark b/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-dark index 43c1498b..d23ad88e 100644 --- a/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-dark +++ b/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-dark @@ -1,6 +1,3 @@ -[cursor] -color=1c1822 c7a1d8 - [colors] foreground=e9e4f0 background=1c1822 @@ -22,3 +19,4 @@ bright6=e0b7c9 bright7=ffffff selection-foreground=1c1822 selection-background=e9e4f0 +cursor=1c1822 c7a1d8 diff --git a/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-light b/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-light index 1790ae98..1b539c4d 100644 --- a/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-light +++ b/Assets/ColorScheme/Noctalia-legacy/terminal/foot/Noctalia-legacy-light @@ -1,6 +1,3 @@ -[cursor] -color=f5f1fa 9b59ba - [colors] foreground=1c1822 background=f5f1fa @@ -22,3 +19,4 @@ bright6=c17093 bright7=1c1822 selection-foreground=f5f1fa selection-background=1c1822 +cursor=f5f1fa 9b59ba diff --git a/Assets/ColorScheme/Nord/terminal/foot/Nord-dark b/Assets/ColorScheme/Nord/terminal/foot/Nord-dark index 8cf7c183..05862554 100644 --- a/Assets/ColorScheme/Nord/terminal/foot/Nord-dark +++ b/Assets/ColorScheme/Nord/terminal/foot/Nord-dark @@ -1,7 +1,4 @@ -[cursor] -color=282828 eceff4 - [colors] foreground=d8dee9 background=2e3440 @@ -23,3 +20,4 @@ bright6=8fbcbb bright7=eceff4 selection-foreground=4c566a selection-background=eceff4 +cursor=282828 eceff4 diff --git a/Assets/ColorScheme/Nord/terminal/foot/Nord-light b/Assets/ColorScheme/Nord/terminal/foot/Nord-light index 2eb5ad4f..b012c226 100644 --- a/Assets/ColorScheme/Nord/terminal/foot/Nord-light +++ b/Assets/ColorScheme/Nord/terminal/foot/Nord-light @@ -1,7 +1,4 @@ -[cursor] -color=3b4252 7bb3c3 - [colors] foreground=414858 background=e5e9f0 @@ -23,3 +20,4 @@ bright6=82afae bright7=eceff4 selection-foreground=4c556a selection-background=d8dee9 +cursor=3b4252 7bb3c3 diff --git a/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-dark b/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-dark index 06847ce9..7462c5a1 100644 --- a/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-dark +++ b/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-dark @@ -1,7 +1,4 @@ -[cursor] -color=191724 e0def4 - [colors] foreground=e0def4 background=191724 @@ -23,3 +20,4 @@ bright6=ebbcba bright7=e0def4 selection-foreground=e0def4 selection-background=403d52 +cursor=191724 e0def4 diff --git a/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-light b/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-light index bf86fc77..4d74cf42 100644 --- a/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-light +++ b/Assets/ColorScheme/Rosepine/terminal/foot/Rosepine-light @@ -1,7 +1,4 @@ -[cursor] -color=faf4ed 575279 - [colors] foreground=575279 background=faf4ed @@ -23,3 +20,4 @@ bright6=d7827e bright7=575279 selection-foreground=575279 selection-background=dfdad9 +cursor=faf4ed 575279 diff --git a/Assets/ColorScheme/Solarized/terminal/foot/Solarized-dark b/Assets/ColorScheme/Solarized/terminal/foot/Solarized-dark index 63ad9f8e..46618196 100644 --- a/Assets/ColorScheme/Solarized/terminal/foot/Solarized-dark +++ b/Assets/ColorScheme/Solarized/terminal/foot/Solarized-dark @@ -1,6 +1,3 @@ -[cursor] -color=073642 839496 - [colors] foreground=839496 background=002b36 @@ -21,4 +18,5 @@ bright5=6c71c4 bright6=93a1a1 bright7=fdf6e3 selection-foreground=93a1a1 -selection-background=073642 \ No newline at end of file +selection-background=073642 +cursor=073642 839496 diff --git a/Assets/ColorScheme/Solarized/terminal/foot/Solarized-light b/Assets/ColorScheme/Solarized/terminal/foot/Solarized-light index cef69200..b318ebe9 100644 --- a/Assets/ColorScheme/Solarized/terminal/foot/Solarized-light +++ b/Assets/ColorScheme/Solarized/terminal/foot/Solarized-light @@ -1,7 +1,4 @@ -[cursor] -color=eee8d5 657b83 - [colors] foreground=657b83 background=fdf6e3 @@ -22,4 +19,5 @@ bright5=6c71c4 bright6=93a1a1 bright7=fdf6e3 selection-foreground=586e75 -selection-background=eee8d5 \ No newline at end of file +selection-background=eee8d5 +cursor=eee8d5 657b83 diff --git a/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-dark b/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-dark index f4d9dc41..794ff163 100644 --- a/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-dark +++ b/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-dark @@ -1,6 +1,3 @@ -[cursor] -color=1a1b26 c0caf5 - [colors] foreground=c0caf5 background=1a1b26 @@ -21,4 +18,5 @@ bright5=bb9af7 bright6=7dcfff bright7=c0caf5 selection-foreground=c0caf5 -selection-background=283457 \ No newline at end of file +selection-background=283457 +cursor=1a1b26 c0caf5 diff --git a/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-light b/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-light index 5ac8b4db..611a2721 100644 --- a/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-light +++ b/Assets/ColorScheme/Tokyo-Night/terminal/foot/Tokyo-Night-light @@ -1,6 +1,3 @@ -[cursor] -color=e1e2e7 3760bf - [colors] foreground=3760bf background=e1e2e7 @@ -21,4 +18,5 @@ bright5=9854f1 bright6=007197 bright7=3760bf selection-foreground=3760bf -selection-background=99a7df \ No newline at end of file +selection-background=99a7df +cursor=e1e2e7 3760bf From 96b63480b4d031c16389bd8800407b6b0dd5cb2b Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 14:52:22 -0400 Subject: [PATCH 030/106] ActiveWindow + MediaMini: proper cleanup of strings to avoid line breaks. --- Services/CompositorService.qml | 6 +++++- Services/MediaService.qml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Services/CompositorService.qml b/Services/CompositorService.qml index fbab5177..fdbde343 100644 --- a/Services/CompositorService.qml +++ b/Services/CompositorService.qml @@ -162,7 +162,11 @@ Singleton { // Get focused window title function getFocusedWindowTitle() { if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) { - return windows.get(focusedWindowIndex).title || "" + var title = windows.get(focusedWindowIndex).title + if (title !== undefined) { + title = title.replace(/(\r\n|\n|\r)/g, "") + } + return title || "" } return "" } diff --git a/Services/MediaService.qml b/Services/MediaService.qml index 5127b162..d67b1bbe 100644 --- a/Services/MediaService.qml +++ b/Services/MediaService.qml @@ -14,7 +14,7 @@ Singleton { property bool isSeeking: false property int selectedPlayerIndex: 0 property bool isPlaying: currentPlayer ? (currentPlayer.playbackState === MprisPlaybackState.Playing || currentPlayer.isPlaying) : false - property string trackTitle: currentPlayer ? (currentPlayer.trackTitle || "") : "" + property string trackTitle: currentPlayer ? (currentPlayer.trackTitle !== undefined ? currentPlayer.trackTitle.replace(/(\r\n|\n|\r)/g, "") : "") : "" property string trackArtist: currentPlayer ? (currentPlayer.trackArtist || "") : "" property string trackAlbum: currentPlayer ? (currentPlayer.trackAlbum || "") : "" property string trackArtUrl: currentPlayer ? (currentPlayer.trackArtUrl || "") : "" From b30879b38d28978ee042a317ff4d2c1319255842 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 03:22:06 +0800 Subject: [PATCH 031/106] Fix: Adjust tray module and icon size --- Modules/Bar/Widgets/Tray.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 85e42afc..acc11dc7 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -19,7 +19,7 @@ Rectangle { readonly property string barPosition: Settings.data.bar.position readonly property bool isVertical: barPosition === "left" || barPosition === "right" readonly property bool compact: (Settings.data.bar.density === "compact") - property real itemSize: isVertical ? Math.round(width * 0.7) : Math.round(height * 0.7) + property real itemSize: Math.round(Style.capsuleHeight * 0.65 * scaling) property list blacklist: Settings.data.bar.trayBlacklist || [] // Read from settings property var filteredItems: [] @@ -91,8 +91,8 @@ Rectangle { } visible: filteredItems.length > 0 - implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : (trayFlow.implicitWidth + Style.marginS * scaling * 2) - implicitHeight: isVertical ? (trayFlow.implicitHeight + Style.marginS * scaling * 2) : Math.round(Style.capsuleHeight * scaling) + implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : (trayFlow.implicitWidth + Style.marginM * 2 * scaling) + implicitHeight: isVertical ? (trayFlow.implicitHeight + Style.marginM * 2 * scaling) : Math.round(Style.capsuleHeight * scaling) radius: Math.round(Style.radiusM * scaling) color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent From 2f2bcdebc88add2d1db61d332b7ae3b51204be73 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 03:34:09 +0800 Subject: [PATCH 032/106] feat: Add custom settings and blacklist for Tray module --- Modules/Bar/Widgets/Tray.qml | 19 ++++++++++++++++++- .../Settings/Bar/BarWidgetSettingsDialog.qml | 7 +------ Services/BarWidgetRegistry.qml | 3 ++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index acc11dc7..03750fe1 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -16,11 +16,28 @@ Rectangle { property ShellScreen screen property real scaling: 1.0 + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string section: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + readonly property string barPosition: Settings.data.bar.position readonly property bool isVertical: barPosition === "left" || barPosition === "right" readonly property bool compact: (Settings.data.bar.density === "compact") property real itemSize: Math.round(Style.capsuleHeight * 0.65 * scaling) - property list blacklist: Settings.data.bar.trayBlacklist || [] // Read from settings + property list blacklist: widgetSettings.blacklist || Settings.data.bar.trayBlacklist || [] // Read from settings property var filteredItems: [] function wildCardMatch(str, rule) { diff --git a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml index 06729c66..4d31f7d0 100644 --- a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml +++ b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml @@ -110,12 +110,7 @@ Popup { onClicked: { if (settingsLoader.item && settingsLoader.item.saveSettings) { var newSettings = settingsLoader.item.saveSettings() - if (widgetSettings.widgetId === "Tray") { - Settings.data.bar.trayBlacklist = newSettings.blacklist || [] - Settings.saveImmediate() - } else { - root.updateWidgetSettings(sectionId, widgetSettings.widgetIndex, newSettings) - } + root.updateWidgetSettings(sectionId, widgetSettings.widgetIndex, newSettings) widgetSettings.close() } } diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index 799a5ce0..efb85537 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -117,7 +117,8 @@ Singleton { "onlyActiveWorkspaces": true }, "Tray": { - "allowUserSettings": true + "allowUserSettings": true, + "blacklist": [] }, "Workspace": { "allowUserSettings": true, From 8915de4673970dd179d4c1495aa724b2bbe7cb5b Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 03:36:28 +0800 Subject: [PATCH 033/106] refactor: Use saveImmediate() in Settings.qml saveTimer --- Commons/Settings.qml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 04e9f507..4bd97d90 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -72,12 +72,7 @@ Singleton { running: false interval: 1000 onTriggered: { - settingsFileView.writeAdapter() - // Write to fallback location if set - if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) { - settingsFallbackFileView.writeAdapter() - } - root.settingsSaved() // Emit signal after saving + root.saveImmediate() } } From f47216033eae1296455063e1380eb77ce9bc6dae Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 05:00:13 +0800 Subject: [PATCH 034/106] feat(tray): Remove global trayBlacklist --- Assets/settings-default.json | 2 +- Commons/Settings.qml | 2 +- Modules/Bar/Widgets/Tray.qml | 2 +- Modules/Settings/Bar/WidgetSettings/TraySettings.qml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index d7791e57..7d607878 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -9,7 +9,7 @@ "floating": false, "marginVertical": 0.25, "marginHorizontal": 0.25, - "trayBlacklist": [], + "widgets": { "left": [ { diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 4bd97d90..6867a2c0 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -139,7 +139,7 @@ Singleton { property bool floating: false property real marginVertical: 0.25 property real marginHorizontal: 0.25 - property list trayBlacklist: [] + // Widget configuration for modular bar system property JsonObject widgets diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 03750fe1..8361a1d6 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -37,7 +37,7 @@ Rectangle { readonly property bool isVertical: barPosition === "left" || barPosition === "right" readonly property bool compact: (Settings.data.bar.density === "compact") property real itemSize: Math.round(Style.capsuleHeight * 0.65 * scaling) - property list blacklist: widgetSettings.blacklist || Settings.data.bar.trayBlacklist || [] // Read from settings + property list blacklist: widgetSettings.blacklist || widgetMetadata.blacklist || [] // Read from settings property var filteredItems: [] function wildCardMatch(str, rule) { diff --git a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml index 40019e80..dca3ea10 100644 --- a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml @@ -10,7 +10,7 @@ ColumnLayout { property var widgetMetadata: ({}) // Expected by BarWidgetSettingsDialog // Local state for the blacklist - property var localBlacklist: widgetData.blacklist || Settings.data.bar.trayBlacklist || [] + property var localBlacklist: widgetData.blacklist || [] ListModel { id: blacklistModel From 1455c84b0ccf8ea874280576821cb721260f89b2 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 05:16:43 +0800 Subject: [PATCH 035/106] fix(tray): Improving regex escaping logic. --- Modules/Bar/Widgets/Tray.qml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 8361a1d6..88bd9f6b 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -41,7 +41,28 @@ Rectangle { property var filteredItems: [] function wildCardMatch(str, rule) { - return str.toLowerCase().includes(rule.toLowerCase()); // Simple substring match + if (!str || !rule) { + return false; + } + Logger.log("Tray", "wildCardMatch - Input str:", str, "rule:", rule); + + // Escape all special regex characters in the rule + let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + // Convert '*' to '.*' for wildcard matching + let pattern = escapedRule.replace(/\\\*/g, '.*'); + // Add ^ and $ to match the entire string + pattern = '^' + pattern + '$'; + + Logger.log("Tray", "wildCardMatch - Generated pattern:", pattern); + + try { + const regex = new RegExp(pattern, 'i'); // 'i' for case-insensitive + Logger.log("Tray", "wildCardMatch - Regex test result:", regex.test(str)); + return regex.test(str); + } catch (e) { + Logger.warn("Tray", "Invalid regex pattern for wildcard match:", rule, e.message); + return false; // If regex is invalid, it won't match + } } function updateFilteredItems() { From 0989601dbcdf79e3ead3256af9b9d727045f103b Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 17:31:42 -0400 Subject: [PATCH 036/106] Fixed a bunch of scaling issue in bar NIconButtons --- Modules/Bar/Widgets/Bluetooth.qml | 3 ++- Modules/Bar/Widgets/ControlCenter.qml | 3 ++- Modules/Bar/Widgets/CustomButton.qml | 2 +- Modules/Bar/Widgets/DarkMode.qml | 3 ++- Modules/Bar/Widgets/KeepAwake.qml | 3 ++- Modules/Bar/Widgets/NightLight.qml | 3 ++- Modules/Bar/Widgets/NotificationHistory.qml | 3 ++- Modules/Bar/Widgets/PowerProfile.qml | 3 ++- Modules/Bar/Widgets/ScreenRecorder.qml | 3 ++- Modules/Bar/Widgets/SessionMenu.qml | 1 + Modules/Bar/Widgets/Spacer.qml | 2 +- Modules/Bar/Widgets/WallpaperSelector.qml | 3 ++- Modules/Bar/Widgets/WiFi.qml | 3 ++- Widgets/NIconButton.qml | 1 + 14 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml index 21fe96c7..520455e4 100644 --- a/Modules/Bar/Widgets/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -10,7 +10,8 @@ import qs.Widgets NIconButton { id: root - property real scaling: 1.0 + // NIconButton must only define screen, not scaling + property ShellScreen screen baseSize: Style.capsuleHeight compact: (Settings.data.bar.density === "compact") diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml index 5fbe7282..993e2f0b 100644 --- a/Modules/Bar/Widgets/ControlCenter.qml +++ b/Modules/Bar/Widgets/ControlCenter.qml @@ -9,7 +9,8 @@ import qs.Services NIconButton { id: root - property real scaling: 1.0 + // NIconButton must only define screen, not scaling + property ShellScreen screen // Widget properties passed from Bar.qml for per-instance settings property string widgetId: "" diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index 3973b4cd..11a1b7d6 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -12,7 +12,7 @@ Item { id: root // Widget properties passed from Bar.qml - property var screen + property ShellScreen screen property real scaling: 1.0 // Widget properties passed from Bar.qml for per-instance settings diff --git a/Modules/Bar/Widgets/DarkMode.qml b/Modules/Bar/Widgets/DarkMode.qml index 1333da05..13998414 100644 --- a/Modules/Bar/Widgets/DarkMode.qml +++ b/Modules/Bar/Widgets/DarkMode.qml @@ -6,7 +6,8 @@ import qs.Services NIconButton { id: root - property real scaling: 1.0 + // NIconButton must only define screen, not scaling + property ShellScreen screen icon: "dark-mode" tooltipText: Settings.data.colorSchemes.darkMode ? I18n.tr("tooltips.switch-to-light-mode") : I18n.tr("tooltips.switch-to-dark-mode") diff --git a/Modules/Bar/Widgets/KeepAwake.qml b/Modules/Bar/Widgets/KeepAwake.qml index 692466a4..058a512d 100644 --- a/Modules/Bar/Widgets/KeepAwake.qml +++ b/Modules/Bar/Widgets/KeepAwake.qml @@ -8,7 +8,8 @@ import qs.Widgets NIconButton { id: root - property real scaling: 1.0 + // NIconButton must only define screen, not scaling + property ShellScreen screen baseSize: Style.capsuleHeight compact: (Settings.data.bar.density === "compact") diff --git a/Modules/Bar/Widgets/NightLight.qml b/Modules/Bar/Widgets/NightLight.qml index f5ac5770..065f6799 100644 --- a/Modules/Bar/Widgets/NightLight.qml +++ b/Modules/Bar/Widgets/NightLight.qml @@ -11,7 +11,8 @@ import qs.Widgets NIconButton { id: root - property real scaling: 1.0 + // NIconButton must only define screen, not scaling + property ShellScreen screen compact: (Settings.data.bar.density === "compact") baseSize: Style.capsuleHeight diff --git a/Modules/Bar/Widgets/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml index ad9bb86b..a5448ae1 100644 --- a/Modules/Bar/Widgets/NotificationHistory.qml +++ b/Modules/Bar/Widgets/NotificationHistory.qml @@ -10,7 +10,8 @@ import qs.Widgets NIconButton { id: root - property real scaling: 1.0 + // NIconButton must only define screen, not scaling + property ShellScreen screen // Widget properties passed from Bar.qml for per-instance settings property string widgetId: "" diff --git a/Modules/Bar/Widgets/PowerProfile.qml b/Modules/Bar/Widgets/PowerProfile.qml index a89c4e7a..ad1dc21d 100644 --- a/Modules/Bar/Widgets/PowerProfile.qml +++ b/Modules/Bar/Widgets/PowerProfile.qml @@ -9,7 +9,8 @@ import qs.Widgets NIconButton { id: root - property real scaling: 1.0 + // NIconButton must only define screen, not scaling + property ShellScreen screen baseSize: Style.capsuleHeight visible: PowerProfileService.available diff --git a/Modules/Bar/Widgets/ScreenRecorder.qml b/Modules/Bar/Widgets/ScreenRecorder.qml index 0f2dd5fa..06869689 100644 --- a/Modules/Bar/Widgets/ScreenRecorder.qml +++ b/Modules/Bar/Widgets/ScreenRecorder.qml @@ -7,7 +7,8 @@ import qs.Widgets NIconButton { id: root - property real scaling: 1.0 + // NIconButton must only define screen, not scaling + property ShellScreen screen icon: "camera-video" tooltipText: ScreenRecorderService.isRecording ? I18n.tr("tooltips.click-to-stop-recording") : I18n.tr("tooltips.click-to-start-recording") diff --git a/Modules/Bar/Widgets/SessionMenu.qml b/Modules/Bar/Widgets/SessionMenu.qml index cb506ad3..f8f09929 100644 --- a/Modules/Bar/Widgets/SessionMenu.qml +++ b/Modules/Bar/Widgets/SessionMenu.qml @@ -8,6 +8,7 @@ import qs.Widgets NIconButton { id: root + property ShellScreen screen property real scaling: 1.0 compact: (Settings.data.bar.density === "compact") diff --git a/Modules/Bar/Widgets/Spacer.qml b/Modules/Bar/Widgets/Spacer.qml index 8f1378e3..811bcb53 100644 --- a/Modules/Bar/Widgets/Spacer.qml +++ b/Modules/Bar/Widgets/Spacer.qml @@ -9,7 +9,7 @@ Item { id: root // Widget properties passed from Bar.qml - property var screen + property ShellScreen screen property real scaling: 1.0 // Widget properties passed from Bar.qml for per-instance settings diff --git a/Modules/Bar/Widgets/WallpaperSelector.qml b/Modules/Bar/Widgets/WallpaperSelector.qml index 51188450..cbed457f 100644 --- a/Modules/Bar/Widgets/WallpaperSelector.qml +++ b/Modules/Bar/Widgets/WallpaperSelector.qml @@ -8,7 +8,8 @@ import qs.Widgets NIconButton { id: root - property real scaling: 1.0 + // NIconButton must only define screen, not scaling + property ShellScreen screen baseSize: Style.capsuleHeight compact: (Settings.data.bar.density === "compact") diff --git a/Modules/Bar/Widgets/WiFi.qml b/Modules/Bar/Widgets/WiFi.qml index 58a0e885..55b2ebb8 100644 --- a/Modules/Bar/Widgets/WiFi.qml +++ b/Modules/Bar/Widgets/WiFi.qml @@ -10,7 +10,8 @@ import qs.Widgets NIconButton { id: root - property real scaling: 1.0 + // NIconButton must only define screen, not scaling + property ShellScreen screen compact: (Settings.data.bar.density === "compact") baseSize: Style.capsuleHeight diff --git a/Widgets/NIconButton.qml b/Widgets/NIconButton.qml index e2d0d0fa..57df95a2 100644 --- a/Widgets/NIconButton.qml +++ b/Widgets/NIconButton.qml @@ -8,6 +8,7 @@ Rectangle { id: root property real baseSize: Style.baseWidgetSize + property real scaling: 1.0 property string icon property string tooltipText From 20c54e292ffbd531e7547559fefab57c6e1d861b Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 07:17:11 +0800 Subject: [PATCH 037/106] feat(tray): Implement debouncing for tray item updates --- Modules/Bar/Widgets/Tray.qml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 88bd9f6b..b940d267 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -65,7 +65,18 @@ Rectangle { } } - function updateFilteredItems() { + // Debounce timer for updateFilteredItems to prevent excessive calls + // when multiple events (e.g., SystemTray changes, settings saves) + // trigger it in rapid succession, reducing redundant processing. + Timer { + id: updateDebounceTimer + interval: 100 // milliseconds + running: false + repeat: false + onTriggered: _performFilteredItemsUpdate() + } + + function _performFilteredItemsUpdate() { if (!root.blacklist || root.blacklist.length === 0) { if (SystemTray.items && SystemTray.items.values) { filteredItems = SystemTray.items.values @@ -103,6 +114,10 @@ Rectangle { filteredItems = newItems } + function updateFilteredItems() { + updateDebounceTimer.restart() + } + function onLoaded() { // When the widget is fully initialized with its props set the screen for the trayMenu if (trayMenu.item) { From 4d0041abeeb7f71724560134d20aff5b9788c0df Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 19:57:12 -0400 Subject: [PATCH 038/106] Quicksettings: polishing, fixed all scaling issues. --- Assets/settings-default.json | 19 +- Commons/Settings.qml | 2 +- Modules/Bar/Widgets/ActiveWindow.qml | 1 + Modules/Bar/Widgets/Bluetooth.qml | 2 +- Modules/Bar/Widgets/ControlCenter.qml | 2 +- Modules/Bar/Widgets/CustomButton.qml | 1 - Modules/Bar/Widgets/DarkMode.qml | 2 +- Modules/Bar/Widgets/KeepAwake.qml | 2 +- Modules/Bar/Widgets/NightLight.qml | 2 +- Modules/Bar/Widgets/NotificationHistory.qml | 2 +- Modules/Bar/Widgets/PowerProfile.qml | 2 +- Modules/Bar/Widgets/ScreenRecorder.qml | 2 +- Modules/Bar/Widgets/Spacer.qml | 1 - Modules/Bar/Widgets/WallpaperSelector.qml | 2 +- Modules/Bar/Widgets/WiFi.qml | 2 +- Modules/ControlCenter/Cards/TopCard.qml | 4 +- Modules/ControlCenter/ControlCenterPanel.qml | 16 +- Modules/ControlCenter/Widgets/Bluetooth.qml | 2 - Modules/ControlCenter/Widgets/KeepAwake.qml | 2 +- Modules/ControlCenter/Widgets/NightLight.qml | 2 +- .../ControlCenter/Widgets/Notifications.qml | 2 +- .../ControlCenter/Widgets/PowerProfile.qml | 2 +- .../ControlCenter/Widgets/ScreenRecorder.qml | 2 +- .../Widgets/WallpaperSelector.qml | 1 - Modules/ControlCenter/Widgets/WiFi.qml | 18 -- .../Bar/WidgetSettings/TaskbarSettings.qml | 1 - Modules/Settings/Tabs/ControlCenterTab.qml | 8 +- Services/PowerProfileService.qml | 6 + Widgets/NCircleStat.qml | 2 +- Widgets/NIconButton.qml | 1 - Widgets/NQuickSetting.qml | 166 +++++------------- 31 files changed, 87 insertions(+), 192 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index fd3707c4..05ccde05 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -36,12 +36,6 @@ { "id": "NotificationHistory" }, - { - "id": "WiFi" - }, - { - "id": "Bluetooth" - }, { "id": "Battery" }, @@ -113,7 +107,7 @@ }, "controlCenter": { "position": "close_to_bar_button", - "quickSettingsStyle": "modern", + "quickSettingsStyle": "compact", "widgets": { "quickSettings": [ { @@ -125,20 +119,11 @@ { "id": "Notifications" }, - { - "id": "NightLight" - }, - { - "id": "KeepAwake" - }, - { - "id": "PowerProfile" - }, { "id": "ScreenRecorder" }, { - "id": "WallpaperSelector" + "id": "PowerProfile" } ] } diff --git a/Commons/Settings.qml b/Commons/Settings.qml index ffe9039a..960edd67 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -242,7 +242,7 @@ Singleton { property JsonObject controlCenter: JsonObject { // Position: close_to_bar_button, center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center property string position: "close_to_bar_button" - property string quickSettingsStyle: "compact" // "modern", "classic", or "compact" + property string quickSettingsStyle: "compact" // "compact", "classic", or "modern" property JsonObject widgets widgets: JsonObject { property list quickSettings: [{ diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 91c05f1d..de10760d 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -10,6 +10,7 @@ import qs.Widgets Item { id: root + property ShellScreen screen property real scaling: 1.0 diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml index 520455e4..e76fa234 100644 --- a/Modules/Bar/Widgets/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -10,8 +10,8 @@ import qs.Widgets NIconButton { id: root - // NIconButton must only define screen, not scaling property ShellScreen screen + property real scaling: 1.0 baseSize: Style.capsuleHeight compact: (Settings.data.bar.density === "compact") diff --git a/Modules/Bar/Widgets/ControlCenter.qml b/Modules/Bar/Widgets/ControlCenter.qml index 993e2f0b..65eb3b8c 100644 --- a/Modules/Bar/Widgets/ControlCenter.qml +++ b/Modules/Bar/Widgets/ControlCenter.qml @@ -9,8 +9,8 @@ import qs.Services NIconButton { id: root - // NIconButton must only define screen, not scaling property ShellScreen screen + property real scaling: 1.0 // Widget properties passed from Bar.qml for per-instance settings property string widgetId: "" diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index 11a1b7d6..50310b24 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -11,7 +11,6 @@ import qs.Modules.Bar.Extras Item { id: root - // Widget properties passed from Bar.qml property ShellScreen screen property real scaling: 1.0 diff --git a/Modules/Bar/Widgets/DarkMode.qml b/Modules/Bar/Widgets/DarkMode.qml index 13998414..b35dd236 100644 --- a/Modules/Bar/Widgets/DarkMode.qml +++ b/Modules/Bar/Widgets/DarkMode.qml @@ -6,8 +6,8 @@ import qs.Services NIconButton { id: root - // NIconButton must only define screen, not scaling property ShellScreen screen + property real scaling: 1.0 icon: "dark-mode" tooltipText: Settings.data.colorSchemes.darkMode ? I18n.tr("tooltips.switch-to-light-mode") : I18n.tr("tooltips.switch-to-dark-mode") diff --git a/Modules/Bar/Widgets/KeepAwake.qml b/Modules/Bar/Widgets/KeepAwake.qml index 058a512d..ba09d92a 100644 --- a/Modules/Bar/Widgets/KeepAwake.qml +++ b/Modules/Bar/Widgets/KeepAwake.qml @@ -8,8 +8,8 @@ import qs.Widgets NIconButton { id: root - // NIconButton must only define screen, not scaling property ShellScreen screen + property real scaling: 1.0 baseSize: Style.capsuleHeight compact: (Settings.data.bar.density === "compact") diff --git a/Modules/Bar/Widgets/NightLight.qml b/Modules/Bar/Widgets/NightLight.qml index 065f6799..b972d37d 100644 --- a/Modules/Bar/Widgets/NightLight.qml +++ b/Modules/Bar/Widgets/NightLight.qml @@ -11,8 +11,8 @@ import qs.Widgets NIconButton { id: root - // NIconButton must only define screen, not scaling property ShellScreen screen + property real scaling: 1.0 compact: (Settings.data.bar.density === "compact") baseSize: Style.capsuleHeight diff --git a/Modules/Bar/Widgets/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml index a5448ae1..aee73598 100644 --- a/Modules/Bar/Widgets/NotificationHistory.qml +++ b/Modules/Bar/Widgets/NotificationHistory.qml @@ -10,8 +10,8 @@ import qs.Widgets NIconButton { id: root - // NIconButton must only define screen, not scaling property ShellScreen screen + property real scaling: 1.0 // Widget properties passed from Bar.qml for per-instance settings property string widgetId: "" diff --git a/Modules/Bar/Widgets/PowerProfile.qml b/Modules/Bar/Widgets/PowerProfile.qml index ad1dc21d..fbd2a719 100644 --- a/Modules/Bar/Widgets/PowerProfile.qml +++ b/Modules/Bar/Widgets/PowerProfile.qml @@ -9,8 +9,8 @@ import qs.Widgets NIconButton { id: root - // NIconButton must only define screen, not scaling property ShellScreen screen + property real scaling: 1.0 baseSize: Style.capsuleHeight visible: PowerProfileService.available diff --git a/Modules/Bar/Widgets/ScreenRecorder.qml b/Modules/Bar/Widgets/ScreenRecorder.qml index 06869689..16b57804 100644 --- a/Modules/Bar/Widgets/ScreenRecorder.qml +++ b/Modules/Bar/Widgets/ScreenRecorder.qml @@ -7,8 +7,8 @@ import qs.Widgets NIconButton { id: root - // NIconButton must only define screen, not scaling property ShellScreen screen + property real scaling: 1.0 icon: "camera-video" tooltipText: ScreenRecorderService.isRecording ? I18n.tr("tooltips.click-to-stop-recording") : I18n.tr("tooltips.click-to-start-recording") diff --git a/Modules/Bar/Widgets/Spacer.qml b/Modules/Bar/Widgets/Spacer.qml index 811bcb53..d8e4abcb 100644 --- a/Modules/Bar/Widgets/Spacer.qml +++ b/Modules/Bar/Widgets/Spacer.qml @@ -8,7 +8,6 @@ import qs.Widgets Item { id: root - // Widget properties passed from Bar.qml property ShellScreen screen property real scaling: 1.0 diff --git a/Modules/Bar/Widgets/WallpaperSelector.qml b/Modules/Bar/Widgets/WallpaperSelector.qml index cbed457f..4be3e9a1 100644 --- a/Modules/Bar/Widgets/WallpaperSelector.qml +++ b/Modules/Bar/Widgets/WallpaperSelector.qml @@ -8,8 +8,8 @@ import qs.Widgets NIconButton { id: root - // NIconButton must only define screen, not scaling property ShellScreen screen + property real scaling: 1.0 baseSize: Style.capsuleHeight compact: (Settings.data.bar.density === "compact") diff --git a/Modules/Bar/Widgets/WiFi.qml b/Modules/Bar/Widgets/WiFi.qml index 55b2ebb8..00fefab6 100644 --- a/Modules/Bar/Widgets/WiFi.qml +++ b/Modules/Bar/Widgets/WiFi.qml @@ -10,8 +10,8 @@ import qs.Widgets NIconButton { id: root - // NIconButton must only define screen, not scaling property ShellScreen screen + property real scaling: 1.0 compact: (Settings.data.bar.density === "compact") baseSize: Style.capsuleHeight diff --git a/Modules/ControlCenter/Cards/TopCard.qml b/Modules/ControlCenter/Cards/TopCard.qml index 643200e2..16336beb 100644 --- a/Modules/ControlCenter/Cards/TopCard.qml +++ b/Modules/ControlCenter/Cards/TopCard.qml @@ -31,7 +31,7 @@ NBox { NImageCircled { width: Style.baseWidgetSize * 1.25 * scaling - height: Style.baseWidgetSize * 1.25 * scaling + height: width imagePath: Settings.data.general.avatarImage fallbackIcon: "person" borderColor: Color.mPrimary @@ -105,7 +105,7 @@ NBox { GridLayout { id: grid Layout.fillWidth: true - columns: (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 4 : 3 + columns: (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 5 : 3 columnSpacing: Style.marginM * scaling rowSpacing: Style.marginS * scaling diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 4f76e029..25458401 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -11,12 +11,12 @@ NPanel { id: root preferredWidth: 440 - preferredHeight: topHeight + bottomHeight + Math.round(Style.marginL * scaling * 3) + preferredHeight: topHeight + bottomHeight + Math.round(Style.marginL * 3) panelKeyboardFocus: true - readonly property int bottomHeight: Math.round(Math.max(196 * scaling)) + readonly property int bottomHeight: 196 readonly property int topHeight: { - const columns = (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 4 : 3 + const columns = (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 5 : 3 const rowsCount = Math.ceil(Settings.data.controlCenter.widgets.quickSettings.length / columns) var buttonHeight @@ -28,7 +28,7 @@ NPanel { buttonHeight = 56 } - return (rowsCount * buttonHeight) + (120 * scaling) + return (rowsCount * buttonHeight) + 120 } // Positioning @@ -56,26 +56,26 @@ NPanel { TopCard { id: topCard Layout.fillWidth: true - Layout.preferredHeight: topHeight + Layout.preferredHeight: topHeight * scaling } // Media + stats column RowLayout { id: bottomCard Layout.fillWidth: true - Layout.preferredHeight: bottomHeight + Layout.preferredHeight: bottomHeight * scaling spacing: content.cardSpacing // Media card MediaCard { Layout.preferredWidth: Math.max(250 * scaling) - Layout.preferredHeight: bottomHeight + Layout.preferredHeight: bottomHeight * scaling } // System monitors combined in one card SystemMonitorCard { Layout.preferredWidth: Math.max(140 * scaling) - Layout.preferredHeight: bottomHeight + Layout.preferredHeight: bottomHeight * scaling } } } diff --git a/Modules/ControlCenter/Widgets/Bluetooth.qml b/Modules/ControlCenter/Widgets/Bluetooth.qml index e44d9844..9150004d 100644 --- a/Modules/ControlCenter/Widgets/Bluetooth.qml +++ b/Modules/ControlCenter/Widgets/Bluetooth.qml @@ -12,9 +12,7 @@ NQuickSetting { fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off" - active: BluetoothService.enabled tooltipText: I18n.tr("quickSettings.bluetooth.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" - onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this) } diff --git a/Modules/ControlCenter/Widgets/KeepAwake.qml b/Modules/ControlCenter/Widgets/KeepAwake.qml index e384272a..fff72489 100644 --- a/Modules/ControlCenter/Widgets/KeepAwake.qml +++ b/Modules/ControlCenter/Widgets/KeepAwake.qml @@ -12,7 +12,7 @@ NQuickSetting { fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off" - active: IdleInhibitorService.isInhibited + hot: IdleInhibitorService.isInhibited tooltipText: I18n.tr("quickSettings.keepAwake.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" diff --git a/Modules/ControlCenter/Widgets/NightLight.qml b/Modules/ControlCenter/Widgets/NightLight.qml index a2d6bfc1..dac6dd98 100644 --- a/Modules/ControlCenter/Widgets/NightLight.qml +++ b/Modules/ControlCenter/Widgets/NightLight.qml @@ -13,7 +13,7 @@ NQuickSetting { fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off" - active: Settings.data.nightLight.enabled + hot: !Settings.data.nightLight.enabled || Settings.data.nightLight.forced style: Settings.data.controlCenter.quickSettingsStyle || "modern" tooltipText: I18n.tr("quickSettings.nightLight.tooltip.action") diff --git a/Modules/ControlCenter/Widgets/Notifications.qml b/Modules/ControlCenter/Widgets/Notifications.qml index 131db7f9..7d8085e4 100644 --- a/Modules/ControlCenter/Widgets/Notifications.qml +++ b/Modules/ControlCenter/Widgets/Notifications.qml @@ -12,7 +12,7 @@ NQuickSetting { fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell" - active: Settings.data.notifications.doNotDisturb + hot: Settings.data.notifications.doNotDisturb tooltipText: I18n.tr("quickSettings.notifications.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" diff --git a/Modules/ControlCenter/Widgets/PowerProfile.qml b/Modules/ControlCenter/Widgets/PowerProfile.qml index 1c57f9fa..442cc9c5 100644 --- a/Modules/ControlCenter/Widgets/PowerProfile.qml +++ b/Modules/ControlCenter/Widgets/PowerProfile.qml @@ -16,7 +16,7 @@ NQuickSetting { fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium icon: PowerProfileService.getIcon() - active: hasPP + hot: !PowerProfileService.isDefault() tooltipText: I18n.tr("quickSettings.powerProfile.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" diff --git a/Modules/ControlCenter/Widgets/ScreenRecorder.qml b/Modules/ControlCenter/Widgets/ScreenRecorder.qml index a9560bdf..a53e3950 100644 --- a/Modules/ControlCenter/Widgets/ScreenRecorder.qml +++ b/Modules/ControlCenter/Widgets/ScreenRecorder.qml @@ -13,7 +13,7 @@ NQuickSetting { text: ScreenRecorderService.isRecording ? I18n.tr("quickSettings.screenRecorder.label.recording") : I18n.tr("quickSettings.screenRecorder.label.stopped") fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium - active: ScreenRecorderService.isRecording + hot: ScreenRecorderService.isRecording tooltipText: I18n.tr("quickSettings.screenRecorder.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" diff --git a/Modules/ControlCenter/Widgets/WallpaperSelector.qml b/Modules/ControlCenter/Widgets/WallpaperSelector.qml index a4639383..e4531ecd 100644 --- a/Modules/ControlCenter/Widgets/WallpaperSelector.qml +++ b/Modules/ControlCenter/Widgets/WallpaperSelector.qml @@ -13,7 +13,6 @@ NQuickSetting { text: I18n.tr("quickSettings.wallpaperSelector.label") fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium - active: Settings.data.wallpaper.enabled tooltipText: I18n.tr("quickSettings.wallpaperSelector.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" diff --git a/Modules/ControlCenter/Widgets/WiFi.qml b/Modules/ControlCenter/Widgets/WiFi.qml index 9d896a5c..29c89a8a 100644 --- a/Modules/ControlCenter/Widgets/WiFi.qml +++ b/Modules/ControlCenter/Widgets/WiFi.qml @@ -46,24 +46,6 @@ NQuickSetting { fontSize: Style.fontSizeS * scaling fontWeight: Style.fontWeightMedium style: Settings.data.controlCenter.quickSettingsStyle || "modern" - - active: { - if (NetworkService.ethernetConnected) { - return true - } - try { - for (const net in NetworkService.networks) { - if (NetworkService.networks[net].connected) { - return true - } - } - return false - } catch (error) { - return false - } - } - tooltipText: I18n.tr("quickSettings.wifi.tooltip.action") - onClicked: PanelService.getPanel("wifiPanel")?.toggle(this) } diff --git a/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml b/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml index 98df4c3f..c80dd09f 100644 --- a/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml @@ -21,7 +21,6 @@ ColumnLayout { var settings = Object.assign({}, widgetData || {}) settings.onlySameOutput = valueOnlySameOutput settings.onlyActiveWorkspaces = valueOnlyActiveWorkspaces - console.log(JSON.stringify(settings)) return settings } diff --git a/Modules/Settings/Tabs/ControlCenterTab.qml b/Modules/Settings/Tabs/ControlCenterTab.qml index 8b7fe265..a880334c 100644 --- a/Modules/Settings/Tabs/ControlCenterTab.qml +++ b/Modules/Settings/Tabs/ControlCenterTab.qml @@ -43,14 +43,14 @@ ColumnLayout { description: I18n.tr("settings.control-center.quickSettingsStyle.style.description") Layout.fillWidth: true model: [{ - "key": "modern", - "name": I18n.tr("options.control-center.quickSettingsStyle.modern") + "key": "compact", + "name": I18n.tr("options.control-center.quickSettingsStyle.compact") }, { "key": "classic", "name": I18n.tr("options.control-center.quickSettingsStyle.classic") }, { - "key": "compact", - "name": I18n.tr("options.control-center.quickSettingsStyle.compact") + "key": "modern", + "name": I18n.tr("options.control-center.quickSettingsStyle.modern") }] currentKey: Settings.data.controlCenter.quickSettingsStyle || "compact" onSelected: function (key) { diff --git a/Services/PowerProfileService.qml b/Services/PowerProfileService.qml index 98acfb71..9a43217e 100644 --- a/Services/PowerProfileService.qml +++ b/Services/PowerProfileService.qml @@ -71,6 +71,12 @@ Singleton { setProfile(PowerProfile.Balanced) } + function isDefault() { + if (!available) + return true + return (profile === PowerProfile.Balanced) + } + Connections { target: powerProfiles function onProfileChanged() { diff --git a/Widgets/NCircleStat.qml b/Widgets/NCircleStat.qml index 53177d07..9c924883 100644 --- a/Widgets/NCircleStat.qml +++ b/Widgets/NCircleStat.qml @@ -84,7 +84,7 @@ Rectangle { anchors.centerIn: parent anchors.verticalCenterOffset: -4 * scaling * contentScale text: `${root.value}${root.suffix}` - pointSize: Style.fontSizeM * scaling * contentScale + pointSize: Style.fontSizeM * scaling * contentScale * 0.9 font.weight: Style.fontWeightBold color: Color.mOnSurface horizontalAlignment: Text.AlignHCenter diff --git a/Widgets/NIconButton.qml b/Widgets/NIconButton.qml index 57df95a2..e2d0d0fa 100644 --- a/Widgets/NIconButton.qml +++ b/Widgets/NIconButton.qml @@ -8,7 +8,6 @@ Rectangle { id: root property real baseSize: Style.baseWidgetSize - property real scaling: 1.0 property string icon property string tooltipText diff --git a/Widgets/NQuickSetting.qml b/Widgets/NQuickSetting.qml index 75517718..c5706cf9 100644 --- a/Widgets/NQuickSetting.qml +++ b/Widgets/NQuickSetting.qml @@ -13,8 +13,7 @@ Rectangle { property string icon: "" property string tooltipText: "" property bool enabled: true - property bool active: false - property bool compact: false + property bool hot: false property string style: "modern" // "modern", "classic", or "compact" // Styling properties @@ -23,56 +22,54 @@ Rectangle { property real iconSize: Style.fontSizeL * scaling property real cornerRadius: Style.radiusM * scaling + // Internal properties + property bool hovered: false + property bool pressed: false + // Colors - Style-dependent colors property color backgroundColor: { + if (pressed) { + return Color.mTertiary + } + if (hot) { + return Color.mPrimary + } if (style === "classic") return Color.mSurfaceVariant if (style === "compact") return Color.mSurface return Color.mSurface } - property color textColor: Color.mOnSurface + property color textColor: { + if (pressed) { + return Color.mOnTertiary + } + if (hot) { + return Color.mOnPrimary + } + return Color.mOnSurface + } property color iconColor: { - if (style === "classic") + if (pressed) { + return Color.mOnTertiary + } + if (hot) { + return Color.mOnPrimary + } + if (style !== "compact") return Color.mPrimary - if (style === "compact") - return active ? Color.mPrimary : Color.mOnSurface - return active ? Color.mPrimary : Color.mOnSurface + return Color.mOnSurface } property color borderColor: Color.mOutline - property color hoverColor: { - if (style === "classic") - return Color.mTertiary - if (style === "compact") - return Color.mPrimary - return Color.mPrimary - } - property color pressedColor: { - if (style === "classic") - return Color.mTertiary - if (style === "compact") - return Qt.darker(Color.mPrimary, 1.1) - return Qt.darker(Color.mPrimary, 1.1) - } - property color hoverTextColor: Color.mOnPrimary - property color hoverIconColor: { - if (style === "classic") - return Color.mOnTertiary - if (style === "compact") - return Color.mOnPrimary - return Color.mOnPrimary - } + property color hoverColor: Color.mTertiary + property color hoverTextColor: Color.mOnTertiary + property color hoverIconColor: Color.mOnTertiary // Signals signal clicked signal rightClicked signal middleClicked - // Internal properties - property bool hovered: false - property bool pressed: false - property real scaling: 1.0 - // Dimensions - Style-dependent sizing implicitWidth: { if (style === "classic") { @@ -81,7 +78,7 @@ Rectangle { if (style === "compact") { return Style.baseWidgetSize * 0.8 * scaling } - return compact ? Math.max(100 * scaling, contentRow.implicitWidth + (Style.marginL * scaling)) : Math.max(120 * scaling, contentRow.implicitWidth + (Style.marginL * scaling)) + return Math.max(120 * scaling, contentRow.implicitWidth + (Style.marginL * scaling)) } implicitHeight: { if (style === "classic") { @@ -90,7 +87,7 @@ Rectangle { if (style === "compact") { return Style.baseWidgetSize * 0.8 * scaling } - return compact ? Math.max(48 * scaling, contentRow.implicitHeight + (Style.marginM * scaling)) : Math.max(56 * scaling, contentRow.implicitHeight + (Style.marginL * scaling)) + return Math.max(48 * scaling, contentRow.implicitHeight + (Style.marginL * scaling)) } // Appearance - Style-dependent styling @@ -104,8 +101,6 @@ Rectangle { color: { if (!enabled) return Qt.lighter(Color.mSurface, 1.1) - if (pressed) - return pressedColor if (hovered) return hoverColor return backgroundColor @@ -128,14 +123,14 @@ Rectangle { Behavior on color { ColorAnimation { - duration: style === "classic" ? Style.animationNormal : Style.animationFast + duration: Style.animationFast easing.type: style === "classic" ? Easing.InOutQuad : Easing.OutCubic } } Behavior on border.color { ColorAnimation { - duration: style === "classic" ? Style.animationNormal : Style.animationFast + duration: Style.animationFast easing.type: style === "classic" ? Easing.InOutQuad : Easing.OutCubic } } @@ -147,25 +142,6 @@ Rectangle { } } - // Hover scale effect - scale: hovered ? 1.02 : 1.0 - - // Subtle shadow/elevation effect - Rectangle { - anchors.fill: parent - radius: parent.radius - color: Qt.rgba(0, 0, 0, 0.1) - visible: active - z: -1 - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - easing.type: Easing.OutCubic - } - } - } - // Modern style - icon above text ColumnLayout { id: contentRow @@ -198,7 +174,7 @@ Rectangle { // Text content NText { Layout.alignment: Qt.AlignHCenter - visible: root.text !== "" && !compact + visible: root.text !== "" text: root.text pointSize: root.fontSize font.weight: root.fontWeight @@ -315,22 +291,25 @@ Rectangle { onPressed: mouse => { root.pressed = true - root.scale = 0.95 + root.scale = 0.92 if (tooltipText) { TooltipService.hide() } } onReleased: mouse => { - root.pressed = false root.scale = 1.0 + root.pressed = false - if (mouse.button === Qt.LeftButton) { - root.clicked() - } else if (mouse.button === Qt.RightButton) { - root.rightClicked() - } else if (mouse.button === Qt.MiddleButton) { - root.middleClicked() + // Only trigger actions if released while hovering + if (root.hovered) { + if (mouse.button === Qt.LeftButton) { + root.clicked() + } else if (mouse.button === Qt.RightButton) { + root.rightClicked() + } else if (mouse.button === Qt.MiddleButton) { + root.middleClicked() + } } } @@ -343,55 +322,4 @@ Rectangle { } } } - - Rectangle { - id: ripple - anchors.fill: parent - radius: parent.radius - color: Qt.rgba(1, 1, 1, 0.2) - scale: 0 - opacity: 0 - visible: false - - SequentialAnimation { - id: rippleAnimation - running: false - - ParallelAnimation { - NumberAnimation { - target: ripple - property: "scale" - from: 0 - to: 1.2 - duration: Style.animationNormal - easing.type: Easing.OutCubic - } - NumberAnimation { - target: ripple - property: "opacity" - from: 0.6 - to: 0 - duration: Style.animationNormal - easing.type: Easing.OutCubic - } - } - } - } - - Connections { - target: root - function onClicked() { - ripple.visible = true - rippleAnimation.start() - } - } - - Connections { - target: rippleAnimation - function onFinished() { - ripple.visible = false - ripple.scale = 0 - ripple.opacity = 0 - } - } } From 3fe63f463e7ab75cac9cf39b36ba8a88bfdf6421 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 20:20:26 -0400 Subject: [PATCH 039/106] ControlCenter: Looks better when it's taller and less wide. --- .../ControlCenter/Cards/SystemMonitorCard.qml | 7 ++-- Modules/ControlCenter/Cards/TopCard.qml | 2 +- Modules/ControlCenter/ControlCenterPanel.qml | 35 ++++++++----------- Widgets/NQuickSetting.qml | 4 +-- 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/Modules/ControlCenter/Cards/SystemMonitorCard.qml b/Modules/ControlCenter/Cards/SystemMonitorCard.qml index 351d4c68..b0572f8a 100644 --- a/Modules/ControlCenter/Cards/SystemMonitorCard.qml +++ b/Modules/ControlCenter/Cards/SystemMonitorCard.qml @@ -9,14 +9,11 @@ import qs.Widgets NBox { id: root - GridLayout { + RowLayout { id: content anchors.fill: parent anchors.margins: Style.marginXS * scaling - columns: 2 - rows: 2 - columnSpacing: Style.marginS * scaling - rowSpacing: Style.marginS * scaling + spacing: Style.marginS * scaling NCircleStat { value: SystemStatService.cpuUsage diff --git a/Modules/ControlCenter/Cards/TopCard.qml b/Modules/ControlCenter/Cards/TopCard.qml index 16336beb..2c9616c2 100644 --- a/Modules/ControlCenter/Cards/TopCard.qml +++ b/Modules/ControlCenter/Cards/TopCard.qml @@ -105,7 +105,7 @@ NBox { GridLayout { id: grid Layout.fillWidth: true - columns: (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 5 : 3 + columns: (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 4 : 3 columnSpacing: Style.marginM * scaling rowSpacing: Style.marginS * scaling diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 25458401..94e36dbd 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -10,13 +10,13 @@ import qs.Widgets NPanel { id: root - preferredWidth: 440 - preferredHeight: topHeight + bottomHeight + Math.round(Style.marginL * 3) + preferredWidth: 400 + preferredHeight: topHeight + midHeight + bottomHeight + Math.round(Style.marginL * 4) panelKeyboardFocus: true - readonly property int bottomHeight: 196 + readonly property int topHeight: { - const columns = (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 5 : 3 + const columns = (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 4 : 3 const rowsCount = Math.ceil(Settings.data.controlCenter.widgets.quickSettings.length / columns) var buttonHeight @@ -30,6 +30,9 @@ NPanel { return (rowsCount * buttonHeight) + 120 } + readonly property int midHeight: 220 + readonly property int bottomHeight: 80 + // Positioning readonly property string controlCenterPosition: Settings.data.controlCenter.position @@ -59,24 +62,16 @@ NPanel { Layout.preferredHeight: topHeight * scaling } - // Media + stats column - RowLayout { - id: bottomCard + // Media card + MediaCard { + Layout.fillWidth: true + Layout.preferredHeight: midHeight * scaling + } + + // System monitors combined in one card + SystemMonitorCard { Layout.fillWidth: true Layout.preferredHeight: bottomHeight * scaling - spacing: content.cardSpacing - - // Media card - MediaCard { - Layout.preferredWidth: Math.max(250 * scaling) - Layout.preferredHeight: bottomHeight * scaling - } - - // System monitors combined in one card - SystemMonitorCard { - Layout.preferredWidth: Math.max(140 * scaling) - Layout.preferredHeight: bottomHeight * scaling - } } } } diff --git a/Widgets/NQuickSetting.qml b/Widgets/NQuickSetting.qml index c5706cf9..2e708e7c 100644 --- a/Widgets/NQuickSetting.qml +++ b/Widgets/NQuickSetting.qml @@ -300,7 +300,7 @@ Rectangle { onReleased: mouse => { root.scale = 1.0 root.pressed = false - + // Only trigger actions if released while hovering if (root.hovered) { if (mouse.button === Qt.LeftButton) { @@ -322,4 +322,4 @@ Rectangle { } } } -} +} \ No newline at end of file From 6b444cea07b1565302bc04fb1ae52bb78eb478d3 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 20:26:07 -0400 Subject: [PATCH 040/106] Translations --- Assets/Translations/de.json | 85 ++++++++++---------- Assets/Translations/en.json | 2 +- Assets/Translations/es.json | 85 ++++++++++---------- Assets/Translations/fr.json | 53 ++---------- Assets/Translations/pt.json | 85 ++++++++++---------- Assets/Translations/zh-CN.json | 83 ++++++++++--------- Modules/ControlCenter/Cards/TopCard.qml | 2 +- Modules/ControlCenter/ControlCenterPanel.qml | 4 +- Widgets/NQuickSetting.qml | 4 +- 9 files changed, 186 insertions(+), 217 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 6f530c47..b9c10d0d 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -743,53 +743,56 @@ "label": "Widgets", "description": "Konfigurieren und verwalten Sie Kontrollzentrum-Widgets und deren Anzeigeoptionen." } - } - }, - "quickSettings": { - "notifications": { - "label": "Benachrichtigungen", - "tooltip": { - "enable": "Nicht stören aktivieren", - "disable": "Nicht stören deaktivieren" + }, + "quickSettings": { + "sectionName": "Schnelleinstellungen" } }, - "wifi": { - "label": { - "wifi": "Wi-Fi", - "ethernet": "Netzwerk" + "quickSettings": { + "notifications": { + "label": "Benachrichtigungen", + "tooltip": { + "enable": "Nicht stören aktivieren", + "disable": "Nicht stören deaktivieren" + } }, - "tooltip": { - "wifi": { - "connected": "Wi-Fi verbunden", - "disconnected": "Wi-Fi getrennt" + "wifi": { + "label": { + "wifi": "Wi-Fi", + "ethernet": "Netzwerk" }, - "ethernet": { - "connected": "Ethernet verbunden" + "tooltip": { + "wifi": { + "connected": "Wi-Fi verbunden", + "disconnected": "Wi-Fi getrennt" + }, + "ethernet": { + "connected": "Ethernet verbunden" + } + } + }, + "bluetooth": { + "label": "Bluetooth", + "tooltip": { + "enabled": "Bluetooth aktiviert", + "disabled": "Bluetooth deaktiviert" + } + }, + "screenRecorder": { + "label": "Bildschirm", + "tooltip": { + "start": "Bildschirmaufnahme starten", + "stop": "Aufnahme beenden" + } + }, + "powerProfile": { + "tooltip": { + "current": "Aktuell: {profile}", + "unavailable": "Energieprofile nicht verfügbar" } } }, - "bluetooth": { - "label": "Bluetooth", - "tooltip": { - "enabled": "Bluetooth aktiviert", - "disabled": "Bluetooth deaktiviert" - } - }, - "screenRecorder": { - "label": "Bildschirm", - "tooltip": { - "start": "Bildschirmaufnahme starten", - "stop": "Aufnahme beenden" - } - }, - "powerProfile": { - "tooltip": { - "current": "Aktuell: {profile}", - "unavailable": "Energieprofile nicht verfügbar" - } - } - }, - "hooks": { + "hooks": { "title": "Hooks", "system-hooks": { "section": { @@ -1440,7 +1443,7 @@ "calculator-error": "Fehler" }, "system": { - "uptime": "System-Laufzeit: {uptime}", + "uptime": "Laufzeit: {uptime}", "welcome-back": "Willkommen zurück,", "monitor-description": "{model} ({width}x{height})", "scaling-percentage": "{percentage}%", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index b9dd76b7..4401d058 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1381,7 +1381,7 @@ "calculator-error": "Error" }, "system": { - "uptime": "System uptime: {uptime}", + "uptime": "Uptime: {uptime}", "welcome-back": "Welcome back,", "monitor-description": "{model} ({width}x{height})", "scaling-percentage": "{percentage}%", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index c7e094ca..455e93fa 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -739,53 +739,56 @@ "label": "Widgets", "description": "Configura y gestiona los widgets del centro de control y sus opciones de visualización." } - } - }, - "quickSettings": { - "notifications": { - "label": "Notificaciones", - "tooltip": { - "enable": "Activar No molestar", - "disable": "Desactivar No molestar" + }, + "quickSettings": { + "sectionName": "Ajustes rápidos" } }, - "wifi": { - "label": { - "wifi": "Wi-Fi", - "ethernet": "Red" + "quickSettings": { + "notifications": { + "label": "Notificaciones", + "tooltip": { + "enable": "Activar No molestar", + "disable": "Desactivar No molestar" + } }, - "tooltip": { - "wifi": { - "connected": "Wi-Fi conectado", - "disconnected": "Wi-Fi desconectado" + "wifi": { + "label": { + "wifi": "Wi-Fi", + "ethernet": "Red" }, - "ethernet": { - "connected": "Ethernet conectado" + "tooltip": { + "wifi": { + "connected": "Wi-Fi conectado", + "disconnected": "Wi-Fi desconectado" + }, + "ethernet": { + "connected": "Ethernet conectado" + } + } + }, + "bluetooth": { + "label": "Bluetooth", + "tooltip": { + "enabled": "Bluetooth habilitado", + "disabled": "Bluetooth deshabilitado" + } + }, + "screenRecorder": { + "label": "Pantalla", + "tooltip": { + "start": "Iniciar grabación de pantalla", + "stop": "Detener grabación" + } + }, + "powerProfile": { + "tooltip": { + "current": "Actual: {profile}", + "unavailable": "Perfiles de energía no disponibles" } } }, - "bluetooth": { - "label": "Bluetooth", - "tooltip": { - "enabled": "Bluetooth habilitado", - "disabled": "Bluetooth deshabilitado" - } - }, - "screenRecorder": { - "label": "Pantalla", - "tooltip": { - "start": "Iniciar grabación de pantalla", - "stop": "Detener grabación" - } - }, - "powerProfile": { - "tooltip": { - "current": "Actual: {profile}", - "unavailable": "Perfiles de energía no disponibles" - } - } - }, - "hooks": { + "hooks": { "title": "Hooks", "system-hooks": { "section": { @@ -1419,7 +1422,7 @@ "calculator-error": "Error" }, "system": { - "uptime": "Tiempo de actividad: {uptime}", + "uptime": "Actividad: {uptime}", "welcome-back": "¡Bienvenido de nuevo,", "monitor-description": "{model} ({width}x{height})", "scaling-percentage": "{percentage}%", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 1a659cb7..c09015c6 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -739,6 +739,9 @@ "label": "Widgets", "description": "Configurez et gérez les widgets du centre de contrôle et leurs options d'affichage." } + }, + "quickSettings": { + "sectionName": "Paramètres rapides" } }, "quickSettings": { @@ -784,52 +787,8 @@ "unavailable": "Profils d'alimentation non disponibles" } } - }, - "quickSettings": { - "notifications": { - "label": "Notifications", - "tooltip": { - "enable": "Activer Ne pas déranger", - "disable": "Désactiver Ne pas déranger" - } }, - "wifi": { - "label": { - "wifi": "Wi-Fi", - "ethernet": "Réseau" - }, - "tooltip": { - "wifi": { - "connected": "Wi-Fi connecté", - "disconnected": "Wi-Fi déconnecté" - }, - "ethernet": { - "connected": "Ethernet connecté" - } - } - }, - "bluetooth": { - "label": "Bluetooth", - "tooltip": { - "enabled": "Bluetooth activé", - "disabled": "Bluetooth désactivé" - } - }, - "screenRecorder": { - "label": "Écran", - "tooltip": { - "start": "Démarrer l'enregistrement d'écran", - "stop": "Arrêter l'enregistrement" - } - }, - "powerProfile": { - "tooltip": { - "current": "Actuel : {profile}", - "unavailable": "Profils d'alimentation non disponibles" - } - } - }, - "hooks": { + "hooks": { "title": "Hooks", "system-hooks": { "section": { @@ -1463,7 +1422,6 @@ "calculator-error": "Erreur" }, "system": { - "uptime": "Temps d'activité : {uptime}", "welcome-back": "Bon retour,", "monitor-description": "{model} ({width}x{height})", "scaling-percentage": "{percentage}%", @@ -1477,7 +1435,8 @@ "user-requested": "Demandé par l'utilisateur", "unknown": "Inconnu", "unknown-version": "Inconnue", - "unknown-layout": "Inconnue" + "unknown-layout": "Inconnue", + "uptime": "Activité : {uptime}" }, "lock-screen": { "password": "Entrez votre mot de passe...", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 4dcb10c7..0c098e50 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -705,53 +705,56 @@ "label": "Widgets", "description": "Configure e gerencie os widgets do centro de controle e suas opções de exibição." } - } - }, - "quickSettings": { - "notifications": { - "label": "Notificações", - "tooltip": { - "enable": "Ativar Não perturbe", - "disable": "Desativar Não perturbe" + }, + "quickSettings": { + "sectionName": "Configurações rápidas" } }, - "wifi": { - "label": { - "wifi": "Wi-Fi", - "ethernet": "Rede" + "quickSettings": { + "notifications": { + "label": "Notificações", + "tooltip": { + "enable": "Ativar Não perturbe", + "disable": "Desativar Não perturbe" + } }, - "tooltip": { - "wifi": { - "connected": "Wi-Fi conectado", - "disconnected": "Wi-Fi desconectado" + "wifi": { + "label": { + "wifi": "Wi-Fi", + "ethernet": "Rede" }, - "ethernet": { - "connected": "Ethernet conectado" + "tooltip": { + "wifi": { + "connected": "Wi-Fi conectado", + "disconnected": "Wi-Fi desconectado" + }, + "ethernet": { + "connected": "Ethernet conectado" + } + } + }, + "bluetooth": { + "label": "Bluetooth", + "tooltip": { + "enabled": "Bluetooth habilitado", + "disabled": "Bluetooth desabilitado" + } + }, + "screenRecorder": { + "label": "Tela", + "tooltip": { + "start": "Iniciar gravação de tela", + "stop": "Parar gravação" + } + }, + "powerProfile": { + "tooltip": { + "current": "Atual: {profile}", + "unavailable": "Perfis de energia não disponíveis" } } }, - "bluetooth": { - "label": "Bluetooth", - "tooltip": { - "enabled": "Bluetooth habilitado", - "disabled": "Bluetooth desabilitado" - } - }, - "screenRecorder": { - "label": "Tela", - "tooltip": { - "start": "Iniciar gravação de tela", - "stop": "Parar gravação" - } - }, - "powerProfile": { - "tooltip": { - "current": "Atual: {profile}", - "unavailable": "Perfis de energia não disponíveis" - } - } - }, - "hooks": { + "hooks": { "title": "Hooks", "system-hooks": { "section": { @@ -1419,7 +1422,7 @@ "calculator-error": "Erro" }, "system": { - "uptime": "Sistema ativo há: {uptime}", + "uptime": "Atividade: {uptime}", "welcome-back": "Bem-vindo(a) de volta, {user}!", "monitor-description": "{model} ({width}x{height})", "scaling-percentage": "{percentage}%", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 6b21676c..e70ccd30 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -739,53 +739,56 @@ "label": "小部件", "description": "配置和管理控制中心小部件及其显示选项。" } - } - }, - "quickSettings": { - "notifications": { - "label": "通知", - "tooltip": { - "enable": "开启勿扰模式", - "disable": "关闭勿扰模式" + }, + "quickSettings": { + "sectionName": "快速设置" } }, - "wifi": { - "label": { - "wifi": "Wi-Fi", - "ethernet": "网络" + "quickSettings": { + "notifications": { + "label": "通知", + "tooltip": { + "enable": "开启勿扰模式", + "disable": "关闭勿扰模式" + } }, - "tooltip": { - "wifi": { - "connected": "Wi-Fi 已连接", - "disconnected": "Wi-Fi 已断开" + "wifi": { + "label": { + "wifi": "Wi-Fi", + "ethernet": "网络" }, - "ethernet": { - "connected": "以太网已连接" + "tooltip": { + "wifi": { + "connected": "Wi-Fi 已连接", + "disconnected": "Wi-Fi 已断开" + }, + "ethernet": { + "connected": "以太网已连接" + } + } + }, + "bluetooth": { + "label": "蓝牙", + "tooltip": { + "enabled": "蓝牙已启用", + "disabled": "蓝牙已禁用" + } + }, + "screenRecorder": { + "label": "屏幕录制", + "tooltip": { + "start": "开始屏幕录制", + "stop": "停止录制" + } + }, + "powerProfile": { + "tooltip": { + "current": "当前:{profile}", + "unavailable": "电源配置文件不可用" } } }, - "bluetooth": { - "label": "蓝牙", - "tooltip": { - "enabled": "蓝牙已启用", - "disabled": "蓝牙已禁用" - } - }, - "screenRecorder": { - "label": "屏幕录制", - "tooltip": { - "start": "开始屏幕录制", - "stop": "停止录制" - } - }, - "powerProfile": { - "tooltip": { - "current": "当前:{profile}", - "unavailable": "电源配置文件不可用" - } - } - }, - "hooks": { + "hooks": { "title": "钩子", "system-hooks": { "section": { diff --git a/Modules/ControlCenter/Cards/TopCard.qml b/Modules/ControlCenter/Cards/TopCard.qml index 2c9616c2..69f1bf5a 100644 --- a/Modules/ControlCenter/Cards/TopCard.qml +++ b/Modules/ControlCenter/Cards/TopCard.qml @@ -52,7 +52,7 @@ NBox { text: I18n.tr("system.uptime", { "uptime": uptimeText }) - pointSize: Style.fontSizeS * scaling + pointSize: Style.fontSizeXS * scaling color: Color.mOnSurfaceVariant } } diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 94e36dbd..756dc1d6 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -10,11 +10,10 @@ import qs.Widgets NPanel { id: root - preferredWidth: 400 + preferredWidth: 360 preferredHeight: topHeight + midHeight + bottomHeight + Math.round(Style.marginL * 4) panelKeyboardFocus: true - readonly property int topHeight: { const columns = (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 4 : 3 const rowsCount = Math.ceil(Settings.data.controlCenter.widgets.quickSettings.length / columns) @@ -33,7 +32,6 @@ NPanel { readonly property int midHeight: 220 readonly property int bottomHeight: 80 - // Positioning readonly property string controlCenterPosition: Settings.data.controlCenter.position panelAnchorHorizontalCenter: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_center") diff --git a/Widgets/NQuickSetting.qml b/Widgets/NQuickSetting.qml index 2e708e7c..c5706cf9 100644 --- a/Widgets/NQuickSetting.qml +++ b/Widgets/NQuickSetting.qml @@ -300,7 +300,7 @@ Rectangle { onReleased: mouse => { root.scale = 1.0 root.pressed = false - + // Only trigger actions if released while hovering if (root.hovered) { if (mouse.button === Qt.LeftButton) { @@ -322,4 +322,4 @@ Rectangle { } } } -} \ No newline at end of file +} From c96eecedb3f6e1c7f0d2aa9208f32669102cf001 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 20:39:28 -0400 Subject: [PATCH 041/106] i18n-json-check: removed empty nested structure. --- Assets/Translations/de.json | 48 -------- Assets/Translations/es.json | 44 ------- Assets/Translations/fr.json | 44 ------- Assets/Translations/pt.json | 44 ------- Assets/Translations/zh-CN.json | 44 ------- Bin/i18n-json-check.sh | 205 ++++++++++++++++++++++++++++++++- 6 files changed, 200 insertions(+), 229 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index b9c10d0d..21a946a4 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -283,10 +283,6 @@ "section": { "label": "Monitor-Anzeige", "description": "Statusleiste auf bestimmten Monitoren anzeigen. Standard ist alle, wenn keine ausgewählt sind." - }, - "only-same-output": { - "label": "Nur Apps vom gleichen Bildschirm", - "description": "Zeige nur Apps vom dem Bildschirm an, wo sich das Dock befindet." } } }, @@ -748,50 +744,6 @@ "sectionName": "Schnelleinstellungen" } }, - "quickSettings": { - "notifications": { - "label": "Benachrichtigungen", - "tooltip": { - "enable": "Nicht stören aktivieren", - "disable": "Nicht stören deaktivieren" - } - }, - "wifi": { - "label": { - "wifi": "Wi-Fi", - "ethernet": "Netzwerk" - }, - "tooltip": { - "wifi": { - "connected": "Wi-Fi verbunden", - "disconnected": "Wi-Fi getrennt" - }, - "ethernet": { - "connected": "Ethernet verbunden" - } - } - }, - "bluetooth": { - "label": "Bluetooth", - "tooltip": { - "enabled": "Bluetooth aktiviert", - "disabled": "Bluetooth deaktiviert" - } - }, - "screenRecorder": { - "label": "Bildschirm", - "tooltip": { - "start": "Bildschirmaufnahme starten", - "stop": "Aufnahme beenden" - } - }, - "powerProfile": { - "tooltip": { - "current": "Aktuell: {profile}", - "unavailable": "Energieprofile nicht verfügbar" - } - } - }, "hooks": { "title": "Hooks", "system-hooks": { diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 455e93fa..68474d06 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -744,50 +744,6 @@ "sectionName": "Ajustes rápidos" } }, - "quickSettings": { - "notifications": { - "label": "Notificaciones", - "tooltip": { - "enable": "Activar No molestar", - "disable": "Desactivar No molestar" - } - }, - "wifi": { - "label": { - "wifi": "Wi-Fi", - "ethernet": "Red" - }, - "tooltip": { - "wifi": { - "connected": "Wi-Fi conectado", - "disconnected": "Wi-Fi desconectado" - }, - "ethernet": { - "connected": "Ethernet conectado" - } - } - }, - "bluetooth": { - "label": "Bluetooth", - "tooltip": { - "enabled": "Bluetooth habilitado", - "disabled": "Bluetooth deshabilitado" - } - }, - "screenRecorder": { - "label": "Pantalla", - "tooltip": { - "start": "Iniciar grabación de pantalla", - "stop": "Detener grabación" - } - }, - "powerProfile": { - "tooltip": { - "current": "Actual: {profile}", - "unavailable": "Perfiles de energía no disponibles" - } - } - }, "hooks": { "title": "Hooks", "system-hooks": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index c09015c6..478abc24 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -744,50 +744,6 @@ "sectionName": "Paramètres rapides" } }, - "quickSettings": { - "notifications": { - "label": "Notifications", - "tooltip": { - "enable": "Activer Ne pas déranger", - "disable": "Désactiver Ne pas déranger" - } - }, - "wifi": { - "label": { - "wifi": "Wi-Fi", - "ethernet": "Réseau" - }, - "tooltip": { - "wifi": { - "connected": "Wi-Fi connecté", - "disconnected": "Wi-Fi déconnecté" - }, - "ethernet": { - "connected": "Ethernet connecté" - } - } - }, - "bluetooth": { - "label": "Bluetooth", - "tooltip": { - "enabled": "Bluetooth activé", - "disabled": "Bluetooth désactivé" - } - }, - "screenRecorder": { - "label": "Écran", - "tooltip": { - "start": "Démarrer l'enregistrement d'écran", - "stop": "Arrêter l'enregistrement" - } - }, - "powerProfile": { - "tooltip": { - "current": "Actuel : {profile}", - "unavailable": "Profils d'alimentation non disponibles" - } - } - }, "hooks": { "title": "Hooks", "system-hooks": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 0c098e50..e614733e 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -710,50 +710,6 @@ "sectionName": "Configurações rápidas" } }, - "quickSettings": { - "notifications": { - "label": "Notificações", - "tooltip": { - "enable": "Ativar Não perturbe", - "disable": "Desativar Não perturbe" - } - }, - "wifi": { - "label": { - "wifi": "Wi-Fi", - "ethernet": "Rede" - }, - "tooltip": { - "wifi": { - "connected": "Wi-Fi conectado", - "disconnected": "Wi-Fi desconectado" - }, - "ethernet": { - "connected": "Ethernet conectado" - } - } - }, - "bluetooth": { - "label": "Bluetooth", - "tooltip": { - "enabled": "Bluetooth habilitado", - "disabled": "Bluetooth desabilitado" - } - }, - "screenRecorder": { - "label": "Tela", - "tooltip": { - "start": "Iniciar gravação de tela", - "stop": "Parar gravação" - } - }, - "powerProfile": { - "tooltip": { - "current": "Atual: {profile}", - "unavailable": "Perfis de energia não disponíveis" - } - } - }, "hooks": { "title": "Hooks", "system-hooks": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index e70ccd30..d0c913a0 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -744,50 +744,6 @@ "sectionName": "快速设置" } }, - "quickSettings": { - "notifications": { - "label": "通知", - "tooltip": { - "enable": "开启勿扰模式", - "disable": "关闭勿扰模式" - } - }, - "wifi": { - "label": { - "wifi": "Wi-Fi", - "ethernet": "网络" - }, - "tooltip": { - "wifi": { - "connected": "Wi-Fi 已连接", - "disconnected": "Wi-Fi 已断开" - }, - "ethernet": { - "connected": "以太网已连接" - } - } - }, - "bluetooth": { - "label": "蓝牙", - "tooltip": { - "enabled": "蓝牙已启用", - "disabled": "蓝牙已禁用" - } - }, - "screenRecorder": { - "label": "屏幕录制", - "tooltip": { - "start": "开始屏幕录制", - "stop": "停止录制" - } - }, - "powerProfile": { - "tooltip": { - "current": "当前:{profile}", - "unavailable": "电源配置文件不可用" - } - } - }, "hooks": { "title": "钩子", "system-hooks": { diff --git a/Bin/i18n-json-check.sh b/Bin/i18n-json-check.sh index 408677b2..a847d66c 100755 --- a/Bin/i18n-json-check.sh +++ b/Bin/i18n-json-check.sh @@ -181,6 +181,40 @@ inject_translation() { fi } +# Function to remove a key from JSON file using jq +remove_json_key() { + local json_file=$1 + local key_path=$2 + + # Split key path into array + local -a path_parts + IFS='.' read -ra path_parts <<< "$key_path" + + # Build jq path array + local jq_path="[" + for i in "${!path_parts[@]}"; do + if [[ $i -gt 0 ]]; then + jq_path+="," + fi + jq_path+="\"${path_parts[$i]}\"" + done + jq_path+="]" + + # Create a temporary file + local temp_file=$(mktemp) + + # Use jq to delete the path + jq --argjson path "$jq_path" 'delpaths([$path])' "$json_file" > "$temp_file" + + if [[ $? -eq 0 ]]; then + mv "$temp_file" "$json_file" + return 0 + else + rm -f "$temp_file" + return 1 + fi +} + # Function to extract all keys from a JSON file recursively extract_keys() { local json_file=$1 @@ -207,6 +241,78 @@ extract_keys() { ' "$json_file" 2>/dev/null | sort } +# Function to extract empty keys from a JSON file recursively +extract_empty_keys() { + local json_file=$1 + + if [[ ! -f "$json_file" ]]; then + echo "Error: File $json_file not found" >&2 + return 1 + fi + + # Extract all keys with empty string or null values recursively using jq + jq -r ' + def empty_keys_recursive: + if type == "object" then + keys[] as $k | + if (.[$k] | type) == "object" then + ($k + "." + (.[$k] | empty_keys_recursive)) + elif (.[$k] == "" or .[$k] == null) then + $k + else + empty + end + else + empty + end; + empty_keys_recursive + ' "$json_file" 2>/dev/null | sort +} + +# Function to remove empty objects recursively from JSON file +remove_empty_objects() { + local json_file=$1 + + # Create a temporary file + local temp_file=$(mktemp) + + # Use jq to recursively remove empty objects + # This function walks the entire JSON tree and removes any object that contains no leaf values + jq ' + def remove_empty: + if type == "object" then + to_entries | + map( + .value |= remove_empty + ) | + map( + select( + .value != {} and + .value != [] and + .value != null and + .value != "" + ) + ) | + from_entries | + if length == 0 then empty else . end + elif type == "array" then + map(remove_empty) | + map(select(. != null and . != {} and . != [] and . != "")) + else + . + end; + remove_empty + ' "$json_file" > "$temp_file" 2>/dev/null + + if [[ $? -eq 0 ]]; then + mv "$temp_file" "$json_file" + return 0 + else + rm -f "$temp_file" + return 1 + fi +} + # Function to get language files get_language_files() { find "$FOLDER_PATH" -maxdepth 1 -name "*.json" -type f | sort @@ -223,15 +329,20 @@ generate_header() { echo "Reference file: $REFERENCE_FILE" echo "Folder: $(realpath "$FOLDER_PATH")" if $TRANSLATE_MODE; then - echo "Mode: TRANSLATION ENABLED" + echo "Mode: TRANSLATION ENABLED (translates missing keys, removes extra/empty keys and empty objects)" fi echo "" echo "Notes:" echo "- Keys are compared recursively through all nested JSON objects" echo "- Missing keys indicate incomplete translations" echo "- Extra keys might indicate deprecated keys or translation-specific additions" + echo "- Empty keys are keys with empty string (\"\") or null values" + echo "- Empty objects are nested objects containing no actual values (only other empty objects)" echo "- Translation completion percentage is calculated based on English reference" echo "- Results are sorted by descending line numbers for easier editing" + if $TRANSLATE_MODE; then + echo "- In translation mode, extra keys, empty keys, and empty objects are automatically removed" + fi echo "" echo "This report compares all language JSON files against the English reference file" echo "and identifies missing keys and extra keys in each language." @@ -427,11 +538,92 @@ compare_language() { done rm -f "$temp_extra" echo "" + + # Remove extra keys if in translate mode + if $TRANSLATE_MODE; then + print_color $BLUE "Removing extra keys from $lang_name..." >&2 + local removed_count=0 + local failed_removal_count=0 + + while IFS= read -r key; do + if [[ -n "$key" ]]; then + print_color $YELLOW " Removing: $key" >&2 + + if remove_json_key "$lang_file" "$key"; then + print_color $GREEN " ✓ Removed: $key" >&2 + removed_count=$((removed_count + 1)) + else + print_color $RED " ✗ Failed to remove: $key" >&2 + failed_removal_count=$((failed_removal_count + 1)) + fi + fi + done <<< "$extra_keys" + + echo "" + print_color $GREEN "Removal complete: $removed_count removed, $failed_removal_count failed" >&2 + echo "" + fi else echo "✅ No extra keys in $lang_name" echo "" fi + # Handle empty keys in translate mode + if $TRANSLATE_MODE; then + local empty_keys=$(extract_empty_keys "$lang_file") + local empty_count=$(count_non_empty_lines "$empty_keys") + + if [[ $empty_count -gt 0 && -n "$empty_keys" ]]; then + echo "EMPTY KEYS IN $lang_name:" + + # Display empty keys + local counter=1 + while IFS= read -r key; do + if [[ -n "$key" ]]; then + local lang_line=$(find_key_line_number "$lang_file" "$key") + printf " %3d. %s (%s:%s)\n" "$counter" "$key" "$(basename "$lang_file")" "$lang_line" + counter=$((counter + 1)) + fi + done <<< "$empty_keys" + echo "" + + print_color $BLUE "Removing empty keys from $lang_name..." >&2 + local removed_empty_count=0 + local failed_empty_removal_count=0 + + while IFS= read -r key; do + if [[ -n "$key" ]]; then + print_color $YELLOW " Removing empty key: $key" >&2 + + if remove_json_key "$lang_file" "$key"; then + print_color $GREEN " ✓ Removed: $key" >&2 + removed_empty_count=$((removed_empty_count + 1)) + else + print_color $RED " ✗ Failed to remove: $key" >&2 + failed_empty_removal_count=$((failed_empty_removal_count + 1)) + fi + fi + done <<< "$empty_keys" + + echo "" + print_color $GREEN "Empty key removal complete: $removed_empty_count removed, $failed_empty_removal_count failed" >&2 + echo "" + else + echo "✅ No empty keys in $lang_name" + echo "" + fi + + # Remove empty objects (nested objects with no actual values) + print_color $BLUE "Cleaning up empty objects in $lang_name..." >&2 + if remove_empty_objects "$lang_file"; then + print_color $GREEN "✓ Successfully removed all empty objects" >&2 + echo "" + else + print_color $RED "✗ Failed to clean up empty objects" >&2 + echo "" + fi + fi + # Clean up rm -f "$lang_keys_file" } @@ -545,7 +737,7 @@ main() { echo "Target language: $target_language" fi if $TRANSLATE_MODE; then - echo "Translation mode: ENABLED" + echo "Translation mode: ENABLED (translated missing keys, removed extra keys, removed empty keys and objects)" fi echo "Report generated: $(date '+%Y-%m-%d %H:%M:%S')" echo "" @@ -568,7 +760,9 @@ show_usage() { echo "This script compares JSON language files in '$FOLDER_PATH' against the English reference." >&2 echo "" >&2 echo "Arguments:" >&2 - echo " --translate Enable automatic translation of missing keys using Gemini API" >&2 + echo " --translate Enable automatic translation of missing keys, removal of extra keys," >&2 + echo " removal of empty keys (empty strings or null values), and removal of" >&2 + echo " empty objects (nested objects containing no actual values)" >&2 echo " --list-models List all available Gemini models and exit" >&2 echo " language_code Optional. Compare only the specified language (e.g., 'fr', 'es', 'de')" >&2 echo " If not provided, all language files will be compared" >&2 @@ -581,8 +775,8 @@ show_usage() { echo " $0 # Compare all languages" >&2 echo " $0 fr # Compare only French (fr.json)" >&2 echo " $0 --list-models # List available Gemini models" >&2 - echo " $0 --translate # Compare all and translate missing keys" >&2 - echo " $0 --translate fr # Translate missing keys for French only" >&2 + echo " $0 --translate # Compare all, translate missing, remove extra/empty keys and objects" >&2 + echo " $0 --translate fr # Translate and clean French only" >&2 echo "" >&2 echo "Requirements:" >&2 echo " - jq must be installed" >&2 @@ -595,6 +789,7 @@ show_usage() { echo " - Comparison report is printed to stdout" >&2 echo " - Progress messages are printed to stderr" >&2 echo " - Results are sorted by descending line numbers for easier editing" >&2 + echo " - In translate mode, extra keys, empty keys, and empty objects are removed" >&2 } # Handle command line arguments From b2c5c71116902e18e2cf1dec4b11da86c147da82 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 20:48:30 -0400 Subject: [PATCH 042/106] NFilePicker: added missing translations --- Assets/Translations/de.json | 6 +++++- Assets/Translations/en.json | 6 +++++- Assets/Translations/es.json | 6 +++++- Assets/Translations/fr.json | 6 +++++- Assets/Translations/pt.json | 6 +++++- Assets/Translations/zh-CN.json | 6 +++++- .../Settings/Bar/BarWidgetSettingsDialog.qml | 2 +- Widgets/NFilePicker.qml | 18 +++++++++--------- 8 files changed, 40 insertions(+), 16 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 21a946a4..79211cbb 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -801,7 +801,11 @@ }, "file-picker": { "select-folder": "Ordner auswählen", - "select-file": "Datei auswählen" + "select-file": "Datei auswählen", + "cancel": "Abbrechen", + "search-placeholder": "Dateien und Ordner suchen...", + "select-current": "Aktuelle auswählen", + "title": "Dateiauswahl" }, "datetime-tokens": { "common": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 4401d058..b2c0df34 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -785,8 +785,12 @@ "placeholder": "Placeholder" }, "file-picker": { + "title": "File Picker", "select-folder": "Select Folder", - "select-file": "Select File" + "select-file": "Select File", + "search-placeholder": "Search files and folders...", + "select-current": "Select Current", + "cancel": "Cancel" }, "datetime-tokens": { "common": { diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 68474d06..53a69f7a 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -784,7 +784,11 @@ }, "file-picker": { "select-folder": "Seleccionar carpeta", - "select-file": "Seleccionar archivo" + "select-file": "Seleccionar archivo", + "cancel": "Cancelar", + "search-placeholder": "Buscar archivos y carpetas...", + "select-current": "Seleccionar actual", + "title": "Selector de archivos" }, "datetime-tokens": { "common": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 478abc24..2a5b07f9 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -784,7 +784,11 @@ }, "file-picker": { "select-folder": "Sélectionner un dossier", - "select-file": "Sélectionner un fichier" + "select-file": "Sélectionner un fichier", + "cancel": "Annuler", + "search-placeholder": "Rechercher des fichiers et des dossiers...", + "select-current": "Sélectionner Actuel", + "title": "Sélecteur de fichiers" }, "datetime-tokens": { "common": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index e614733e..528ff799 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -784,7 +784,11 @@ }, "file-picker": { "select-folder": "Selecionar Pasta", - "select-file": "Selecionar Arquivo" + "select-file": "Selecionar Arquivo", + "cancel": "Cancelar", + "search-placeholder": "Pesquisar arquivos e pastas...", + "select-current": "Selecionar Atual", + "title": "Seletor de Arquivos" }, "datetime-tokens": { "common": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index d0c913a0..23af1975 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -784,7 +784,11 @@ }, "file-picker": { "select-folder": "选择文件夹", - "select-file": "选择文件" + "select-file": "选择文件", + "cancel": "取消", + "search-placeholder": "搜索文件和文件夹...", + "select-current": "选择当前", + "title": "文件选择器" }, "datetime-tokens": { "common": { diff --git a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml index 1c2a1aaf..4eac93d3 100644 --- a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml +++ b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml @@ -69,7 +69,7 @@ Popup { NIconButton { icon: "close" - tooltipText: "Close" + tooltipText: I18n.tr("tooltips.close") onClicked: widgetSettings.close() } } diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index 2eed6c9a..4013c59c 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -13,7 +13,7 @@ Popup { id: root // Properties - property string title: "File Picker" + property string title: I18n.tr("widget.file-picker.title") property string initialPath: Quickshell.env("HOME") || "/home" property string selectionMode: "files" // "files" or "folders" property var nameFilters: ["*"] @@ -203,7 +203,7 @@ Popup { // "Select Current" button only visible in folder selection mode NButton { - text: "Select Current" + text: I18n.tr("widgets.file-picker.select-current") icon: "filepicker-folder-current" visible: root.selectionMode === "folders" onClicked: { @@ -214,7 +214,7 @@ Popup { NIconButton { icon: "filepicker-refresh" - tooltipText: "Refresh" + tooltipText: I18n.tr("tooltips.refresh") onClicked: { // Force a proper refresh by resetting the folder const currentFolder = folderModel.folder @@ -225,7 +225,7 @@ Popup { } NIconButton { icon: "filepicker-close" - tooltipText: "Close" + tooltipText: I18n.tr("tooltips.close") onClicked: { root.cancelled() root.close() @@ -256,7 +256,7 @@ Popup { NIconButton { icon: "filepicker-arrow-up" - tooltipText: "Up" + tooltipText: I18n.tr("tooltips.up") baseSize: Style.baseWidgetSize * 0.8 enabled: folderModel.folder.toString() !== "file:///" onClicked: { @@ -268,7 +268,7 @@ Popup { NIconButton { icon: "filepicker-home" - tooltipText: "Home" + tooltipText: I18n.tr("tooltips.home") baseSize: Style.baseWidgetSize * 0.8 onClicked: { const homePath = Quickshell.env("HOME") || "/home" @@ -361,7 +361,7 @@ Popup { } NTextInput { id: searchInput - placeholderText: "Search files and folders..." + placeholderText: I18n.tr("widget.file-picker.search-placeholder") Layout.fillWidth: true text: filePickerPanel.searchText onTextChanged: { @@ -378,7 +378,7 @@ Popup { } NIconButton { icon: "filepicker-x" - tooltipText: "Clear" + tooltipText: I18n.tr("tooltips.clear") baseSize: Style.baseWidgetSize * 0.6 visible: filePickerPanel.searchText.length > 0 onClicked: { @@ -814,7 +814,7 @@ Popup { } NButton { - text: "Cancel" + text: I18n.tr("widgets.file-picker.cancel") outlined: true onClicked: { root.cancelled() From 2bcdcb1e9e5a3451619a73c4fd6f3d6f1f0ee910 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Fri, 10 Oct 2025 09:25:15 +0800 Subject: [PATCH 043/106] refactor(tray): refactor blacklist UI layout and fix display issues --- .../Bar/WidgetSettings/TraySettings.qml | 89 ++++++++++++------- 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml index dca3ea10..77f00d5d 100644 --- a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml @@ -25,37 +25,46 @@ ColumnLayout { spacing: Style.marginM * scaling - // Input for new blacklist items - RowLayout { + ColumnLayout { Layout.fillWidth: true spacing: Style.marginS * scaling - NTextInput { - id: newRuleInput - Layout.fillWidth: true - label: I18n.tr("settings.bar.tray.blacklist.label") - description: I18n.tr("settings.bar.tray.blacklist.description") - placeholderText: I18n.tr("settings.bar.tray.blacklist.placeholder") + NLabel { + label: I18n.tr("settings.bar.tray.blacklist.label") + description: I18n.tr("settings.bar.tray.blacklist.description") } - NIconButton { - icon: "add" - enabled: newRuleInput.text.length > 0 - onClicked: { - if (newRuleInput.text.length > 0) { - var newRule = newRuleInput.text.trim() - var exists = false - for (var i = 0; i < blacklistModel.count; i++) { - if (blacklistModel.get(i).rule === newRule) { - exists = true - break + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS * scaling + + NTextInput { + id: newRuleInput + Layout.fillWidth: true + placeholderText: I18n.tr("settings.bar.tray.blacklist.placeholder") + } + + NIconButton { + Layout.alignment: Qt.AlignVCenter + icon: "add" + baseSize: Style.baseWidgetSize * 0.8 * scaling + onClicked: { + if (newRuleInput.text.length > 0) { + var newRule = newRuleInput.text.trim() + var exists = false + for (var i = 0; i < blacklistModel.count; i++) { + if (blacklistModel.get(i).rule === newRule) { + exists = true + break + } + } + if (!exists) { + blacklistModel.append({"rule": newRule}) + newRuleInput.text = "" } } - if (!exists) { - blacklistModel.append({"rule": newRule}) - newRuleInput.text = "" - } } + enabled: newRuleInput.text.length > 0 } } } @@ -64,32 +73,46 @@ ColumnLayout { ListView { Layout.fillWidth: true Layout.preferredHeight: 150 * scaling + Layout.topMargin: Style.marginL * scaling // Increased top margin clip: true model: blacklistModel - delegate: Rectangle { + delegate: Item { width: ListView.width height: 40 * scaling - color: Color.transparent // Make background transparent - visible: model.rule !== undefined && model.rule !== "" // Only visible if rule exists - RowLayout { + Rectangle { + id: itemBackground anchors.fill: parent - anchors.leftMargin: Style.marginM * scaling + anchors.margins: Style.marginXS * scaling + color: Color.transparent // Make background transparent + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + radius: Style.radiusS * scaling + visible: model.rule !== undefined && model.rule !== "" // Only visible if rule exists + } + + Row { + anchors.fill: parent + anchors.leftMargin: Style.marginS * scaling anchors.rightMargin: Style.marginS * scaling spacing: Style.marginS * scaling NText { - Layout.fillWidth: true text: model.rule elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + Layout.fillWidth: true } NIconButton { - Layout.alignment: Qt.AlignRight + width: 16 * scaling + height: 16 * scaling icon: "close" - baseSize: 24 * scaling - colorBg: Color.transparent - colorFg: Color.mError + baseSize: 8 * scaling + colorBg: Color.mSurfaceVariant + colorFg: Color.mOnSurface + colorBgHover: Color.mError + colorFgHover: Color.mOnError onClicked: { blacklistModel.remove(index) } From 789354464de479fa953f2d5a236a1f6d969ca15b Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 21:29:07 -0400 Subject: [PATCH 044/106] QuickSettings: minor layout tweaks --- Modules/ControlCenter/Cards/MediaCard.qml | 6 +++--- Modules/ControlCenter/Cards/TopCard.qml | 2 +- Modules/ControlCenter/ControlCenterPanel.qml | 2 +- Modules/ControlCenter/Widgets/Bluetooth.qml | 2 -- Modules/ControlCenter/Widgets/KeepAwake.qml | 2 -- Modules/ControlCenter/Widgets/NightLight.qml | 2 -- Modules/ControlCenter/Widgets/Notifications.qml | 2 -- Modules/ControlCenter/Widgets/PowerProfile.qml | 2 -- Modules/ControlCenter/Widgets/ScreenRecorder.qml | 2 -- Modules/ControlCenter/Widgets/WallpaperSelector.qml | 2 -- Modules/ControlCenter/Widgets/WiFi.qml | 2 -- Widgets/NQuickSetting.qml | 5 +++-- 12 files changed, 8 insertions(+), 23 deletions(-) diff --git a/Modules/ControlCenter/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml index 0781b984..4484e27f 100644 --- a/Modules/ControlCenter/Cards/MediaCard.qml +++ b/Modules/ControlCenter/Cards/MediaCard.qml @@ -84,7 +84,7 @@ NBox { anchors.fill: parent values: CavaService.values fillColor: Color.mPrimary - opacity: MediaService.trackArtUrl !== "" ? 0.4 : 0.8 + opacity: MediaService.trackArtUrl !== "" ? 0.5 : 0.8 } } @@ -94,7 +94,7 @@ NBox { anchors.fill: parent values: CavaService.values fillColor: Color.mPrimary - opacity: MediaService.trackArtUrl !== "" ? 0.4 : 0.8 + opacity: MediaService.trackArtUrl !== "" ? 0.5 : 0.8 } } @@ -104,7 +104,7 @@ NBox { anchors.fill: parent values: CavaService.values fillColor: Color.mPrimary - opacity: MediaService.trackArtUrl !== "" ? 0.4 : 0.8 + opacity: MediaService.trackArtUrl !== "" ? 0.5 : 0.8 } } } diff --git a/Modules/ControlCenter/Cards/TopCard.qml b/Modules/ControlCenter/Cards/TopCard.qml index 69f1bf5a..21b68eca 100644 --- a/Modules/ControlCenter/Cards/TopCard.qml +++ b/Modules/ControlCenter/Cards/TopCard.qml @@ -106,7 +106,7 @@ NBox { id: grid Layout.fillWidth: true columns: (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 4 : 3 - columnSpacing: Style.marginM * scaling + columnSpacing: Style.marginS * scaling rowSpacing: Style.marginS * scaling Repeater { diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 756dc1d6..e5b2145d 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -10,7 +10,7 @@ import qs.Widgets NPanel { id: root - preferredWidth: 360 + preferredWidth: 400 preferredHeight: topHeight + midHeight + bottomHeight + Math.round(Style.marginL * 4) panelKeyboardFocus: true diff --git a/Modules/ControlCenter/Widgets/Bluetooth.qml b/Modules/ControlCenter/Widgets/Bluetooth.qml index 9150004d..e9886a4f 100644 --- a/Modules/ControlCenter/Widgets/Bluetooth.qml +++ b/Modules/ControlCenter/Widgets/Bluetooth.qml @@ -9,8 +9,6 @@ NQuickSetting { property real scaling: 1.0 text: I18n.tr("quickSettings.bluetooth.label.enabled") - fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightMedium icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off" tooltipText: I18n.tr("quickSettings.bluetooth.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" diff --git a/Modules/ControlCenter/Widgets/KeepAwake.qml b/Modules/ControlCenter/Widgets/KeepAwake.qml index fff72489..044734e4 100644 --- a/Modules/ControlCenter/Widgets/KeepAwake.qml +++ b/Modules/ControlCenter/Widgets/KeepAwake.qml @@ -9,8 +9,6 @@ NQuickSetting { property real scaling: 1.0 text: I18n.tr("quickSettings.keepAwake.label.enabled") - fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightMedium icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off" hot: IdleInhibitorService.isInhibited tooltipText: I18n.tr("quickSettings.keepAwake.tooltip.action") diff --git a/Modules/ControlCenter/Widgets/NightLight.qml b/Modules/ControlCenter/Widgets/NightLight.qml index dac6dd98..19cdd669 100644 --- a/Modules/ControlCenter/Widgets/NightLight.qml +++ b/Modules/ControlCenter/Widgets/NightLight.qml @@ -10,8 +10,6 @@ NQuickSetting { enabled: ProgramCheckerService.wlsunsetAvailable text: I18n.tr("quickSettings.nightLight.label.enabled") - fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightMedium icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off" hot: !Settings.data.nightLight.enabled || Settings.data.nightLight.forced style: Settings.data.controlCenter.quickSettingsStyle || "modern" diff --git a/Modules/ControlCenter/Widgets/Notifications.qml b/Modules/ControlCenter/Widgets/Notifications.qml index 7d8085e4..19c22a82 100644 --- a/Modules/ControlCenter/Widgets/Notifications.qml +++ b/Modules/ControlCenter/Widgets/Notifications.qml @@ -9,8 +9,6 @@ NQuickSetting { property real scaling: 1.0 text: Settings.data.notifications.doNotDisturb ? I18n.tr("quickSettings.notifications.label.disabled") : I18n.tr("quickSettings.notifications.label.enabled") - fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightMedium icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell" hot: Settings.data.notifications.doNotDisturb tooltipText: I18n.tr("quickSettings.notifications.tooltip.action") diff --git a/Modules/ControlCenter/Widgets/PowerProfile.qml b/Modules/ControlCenter/Widgets/PowerProfile.qml index 442cc9c5..1a504963 100644 --- a/Modules/ControlCenter/Widgets/PowerProfile.qml +++ b/Modules/ControlCenter/Widgets/PowerProfile.qml @@ -13,8 +13,6 @@ NQuickSetting { enabled: hasPP text: hasPP ? PowerProfileService.getName() : I18n.tr("quickSettings.powerProfile.label.unavailable") - fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightMedium icon: PowerProfileService.getIcon() hot: !PowerProfileService.isDefault() tooltipText: I18n.tr("quickSettings.powerProfile.tooltip.action") diff --git a/Modules/ControlCenter/Widgets/ScreenRecorder.qml b/Modules/ControlCenter/Widgets/ScreenRecorder.qml index a53e3950..f9ad8786 100644 --- a/Modules/ControlCenter/Widgets/ScreenRecorder.qml +++ b/Modules/ControlCenter/Widgets/ScreenRecorder.qml @@ -11,8 +11,6 @@ NQuickSetting { enabled: ProgramCheckerService.gpuScreenRecorderAvailable icon: "camera-video" text: ScreenRecorderService.isRecording ? I18n.tr("quickSettings.screenRecorder.label.recording") : I18n.tr("quickSettings.screenRecorder.label.stopped") - fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightMedium hot: ScreenRecorderService.isRecording tooltipText: I18n.tr("quickSettings.screenRecorder.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" diff --git a/Modules/ControlCenter/Widgets/WallpaperSelector.qml b/Modules/ControlCenter/Widgets/WallpaperSelector.qml index e4531ecd..95a50af3 100644 --- a/Modules/ControlCenter/Widgets/WallpaperSelector.qml +++ b/Modules/ControlCenter/Widgets/WallpaperSelector.qml @@ -11,8 +11,6 @@ NQuickSetting { enabled: Settings.data.wallpaper.enabled icon: "wallpaper-selector" text: I18n.tr("quickSettings.wallpaperSelector.label") - fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightMedium tooltipText: I18n.tr("quickSettings.wallpaperSelector.tooltip.action") style: Settings.data.controlCenter.quickSettingsStyle || "modern" diff --git a/Modules/ControlCenter/Widgets/WiFi.qml b/Modules/ControlCenter/Widgets/WiFi.qml index 29c89a8a..7f739866 100644 --- a/Modules/ControlCenter/Widgets/WiFi.qml +++ b/Modules/ControlCenter/Widgets/WiFi.qml @@ -43,8 +43,6 @@ NQuickSetting { return connected ? I18n.tr("quickSettings.wifi.label.wifi") : I18n.tr("quickSettings.wifi.label.disconnected") } - fontSize: Style.fontSizeS * scaling - fontWeight: Style.fontWeightMedium style: Settings.data.controlCenter.quickSettingsStyle || "modern" tooltipText: I18n.tr("quickSettings.wifi.tooltip.action") onClicked: PanelService.getPanel("wifiPanel")?.toggle(this) diff --git a/Widgets/NQuickSetting.qml b/Widgets/NQuickSetting.qml index c5706cf9..7b559019 100644 --- a/Widgets/NQuickSetting.qml +++ b/Widgets/NQuickSetting.qml @@ -17,7 +17,7 @@ Rectangle { property string style: "modern" // "modern", "classic", or "compact" // Styling properties - property real fontSize: Style.fontSizeS * scaling + property real fontSize: (style === "classic") ? Style.fontSizeXS * scaling : Style.fontSizeS * scaling property int fontWeight: Style.fontWeightMedium property real iconSize: Style.fontSizeL * scaling property real cornerRadius: Style.radiusM * scaling @@ -171,7 +171,7 @@ Rectangle { } } - // Text content + // Modern - Text content NText { Layout.alignment: Qt.AlignHCenter visible: root.text !== "" @@ -245,6 +245,7 @@ Rectangle { } } + // Classic - Text content NText { visible: root.text !== "" text: root.text From 17bca7ce355721e3bb6935c8d724688ca4426dbb Mon Sep 17 00:00:00 2001 From: Aiser <2912778691@qq.com> Date: Fri, 10 Oct 2025 10:59:53 +0800 Subject: [PATCH 045/106] Matugen: Add KColorScheme for KDE's APP --- Assets/MatugenTemplates/kcolorscheme.colors | 156 ++++++++++++++++++++ Assets/Translations/de.json | 3 + Assets/Translations/en.json | 7 +- Assets/Translations/es.json | 3 + Assets/Translations/fr.json | 3 + Assets/Translations/pt.json | 3 + Assets/Translations/zh-CN.json | 3 + Commons/Settings.qml | 1 + Modules/Settings/Tabs/ColorSchemeTab.qml | 12 ++ Services/AppThemeService.qml | 6 + 10 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 Assets/MatugenTemplates/kcolorscheme.colors diff --git a/Assets/MatugenTemplates/kcolorscheme.colors b/Assets/MatugenTemplates/kcolorscheme.colors new file mode 100644 index 00000000..5b97b234 --- /dev/null +++ b/Assets/MatugenTemplates/kcolorscheme.colors @@ -0,0 +1,156 @@ +[KDE] +contrast=4 + +[General] +ColorScheme=Matugen +Name=noctalia + +[ColorEffects:Disabled] +Color={{colors.surface_dim.default.hex}} +ColorAmount=0 +ColorEffect=0 +ContrastAmount=0.65 +ContrastEffect=1 +IntensityAmount=0.1 +IntensityEffect=2 + +[ColorEffects:Inactive] +ChangeSelectionColor=true +Color={{colors.surface_variant.default.hex}} +ColorAmount=0.025 +ColorEffect=2 +ContrastAmount=0.1 +ContrastEffect=2 +Enable=false +IntensityAmount=0 +IntensityEffect=0 + +[Colors:Button] +BackgroundAlternate={{colors.surface_container_low.default.hex}} +BackgroundNormal={{colors.surface_container_high.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_surface.default.hex}} +ForegroundPositive={{colors.tertiary_fixed.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:Complementary] +BackgroundAlternate={{colors.surface_container_low.default.hex}} +BackgroundNormal={{colors.surface.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_primary_container.default.hex}} +ForegroundPositive={{colors.tertiary_fixed.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:Header] +BackgroundAlternate={{colors.surface.default.hex}} +BackgroundNormal={{colors.surface_container.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_surface.default.hex}} +ForegroundPositive={{colors.tertiary_fixed.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:Header][Inactive] +BackgroundAlternate={{colors.surface_container.default.hex}} +BackgroundNormal={{colors.surface.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_surface.default.hex}} +ForegroundPositive={{colors.tertiary_fixed.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:Selection] +BackgroundAlternate={{colors.surface_container_low.default.hex}} +BackgroundNormal={{colors.primary.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.on_primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary_fixed.default.hex}} +ForegroundNegative={{colors.error_container.default.hex}} +ForegroundNeutral={{colors.tertiary_fixed_dim.default.hex}} +ForegroundNormal={{colors.on_primary.default.hex}} +ForegroundPositive={{colors.tertiary_container.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:Tooltip] +BackgroundAlternate={{colors.surface.default.hex}} +BackgroundNormal={{colors.surface_container.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_background.default.hex}} +ForegroundPositive={{colors.tertiary_fixed.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:View] +BackgroundAlternate={{colors.surface_container.default.hex}} +BackgroundNormal={{colors.background.default.hex}} +DecorationFocus={{colors.on_primary_container.default.hex}} +DecorationHover={{colors.on_primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_background.default.hex}} +ForegroundPositive={{colors.tertiary_fixed.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[Colors:Window] +BackgroundAlternate={{colors.primary_container.default.hex}} +BackgroundNormal={{colors.surface_container.default.hex}} +DecorationFocus={{colors.primary.default.hex}} +DecorationHover={{colors.primary.default.hex}} +ForegroundActive={{colors.primary.default.hex}} +ForegroundInactive={{colors.on_surface_variant.default.hex}} +ForegroundLink={{colors.secondary.default.hex}} +ForegroundNegative={{colors.error.default.hex}} +ForegroundNeutral={{colors.tertiary.default.hex}} +ForegroundNormal={{colors.on_background.default.hex}} +ForegroundPositive={{colors.tertiary_fixed.default.hex}} +ForegroundVisited={{colors.on_secondary_container.default.hex}} + +[General] +ColorScheme=Matugen +Name=Matugen + +[Appearance] +color_scheme=Matugen + +[KDE] +contrast=4 + +[WM] +activeBackground={{colors.primary_container.default.hex}} +activeBlend={{colors.on_primary_container.default.hex}} +activeForeground={{colors.on_primary_container.default.hex}} +inactiveBackground={{colors.surface.default.hex}} +inactiveBlend={{colors.on_surface_variant.default.hex}} +inactiveForeground={{colors.on_surface_variant.default.hex}} diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 021a823f..4bd84159 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -533,6 +533,9 @@ }, "qt": { "description": "Schreibt {filepath}" + }, + "kcolorscheme": { + "description": "Schreibt {filepath}" } }, "terminal": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index ff91166a..033f7c49 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -2,7 +2,6 @@ "settings": { "general": { "title": "General", - "profile": { "section": { "label": "Profile", @@ -14,7 +13,6 @@ }, "select-avatar": "Select avatar image" }, - "ui": { "section": { "label": "User interface", @@ -535,6 +533,9 @@ }, "qt": { "description": "Write {filepath}" + }, + "kcolorscheme": { + "description": "Write {filepath}" } }, "terminal": { @@ -1197,7 +1198,6 @@ "enter-width-pixels": "Enter width in pixels", "enter-command": "Enter command to execute (app or custom script)", "command-example": "echo \"Hello World\"", - "search-wallpapers": "Type to filter wallpapers...", "search-launcher": "Search entries... or use > for commands", "search": "Search...", @@ -1444,7 +1444,6 @@ "thunderstorm": "Thunderstorm", "unknown": "Unknown" }, - "authentication": { "failed": "Authentication failed", "error": "Authentication error" diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index f33d2e0c..949f4ef3 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -529,6 +529,9 @@ }, "qt": { "description": "Escribir {filepath}" + }, + "kcolorscheme": { + "description": "Escribir {filepath}" } }, "terminal": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index a087896a..dcc4cd41 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -529,6 +529,9 @@ }, "qt": { "description": "Écrire {filepath}" + }, + "kcolorscheme": { + "description": "Écrire {filepath}" } }, "terminal": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index d9b1d538..4843d932 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -495,6 +495,9 @@ }, "qt": { "description": "Escrever {filepath}" + }, + "kcolorscheme": { + "description": "Escrever {filepath}" } }, "terminal": { diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 8bdfbce4..f1317f15 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -529,6 +529,9 @@ }, "qt": { "description": "写入 {filepath}" + }, + "kcolorscheme": { + "description": "写入 {filepath}" } }, "terminal": { diff --git a/Commons/Settings.qml b/Commons/Settings.qml index c0939caf..68414fc7 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -323,6 +323,7 @@ Singleton { property JsonObject templates: JsonObject { property bool gtk: false property bool qt: false + property bool kcolorscheme: false property bool kitty: false property bool ghostty: false property bool foot: false diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 213ff4df..d58ba683 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -405,6 +405,18 @@ ColumnLayout { AppThemeService.generate() } } + + NCheckbox { + label: "KColorScheme" + description: I18n.tr("settings.color-scheme.templates.ui.kcolorscheme.description", { + "filepath": "~/.local/share/color-schemes/noctalia.colors" + }) + checked: Settings.data.templates.kcolorscheme + onToggled: checked => { + Settings.data.templates.kcolorscheme = checked + AppThemeService.generate() + } + } } // Terminal Emulators diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index 21426aa6..c3e60a22 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -41,6 +41,12 @@ Singleton { "path": "~/.config/qt6ct/colors/noctalia.conf" }] }, + "kcolorscheme": { + "input": "kcolorscheme.colors", + "outputs": [{ + "path": "~/.local/share/color-schemes/noctalia.colors" + }], + }, "fuzzel": { "input": "fuzzel.conf", "outputs": [{ From 254a3cfad686aca461205fad615761b7cdbe24db Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 23:52:54 -0400 Subject: [PATCH 046/106] Tray: partial revert of IconImage smoothing --- Modules/Bar/Widgets/Tray.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 30a3f7b9..43415942 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -58,8 +58,6 @@ Rectangle { anchors.fill: parent asynchronous: true - smooth: false - mipmap: true backer.fillMode: Image.PreserveAspectFit source: { let icon = modelData?.icon || "" From 8c5968c721709c233095c2f9d84f803b2db58be1 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Thu, 9 Oct 2025 23:55:58 -0400 Subject: [PATCH 047/106] Media: Stop the "No active player found" spam. --- Services/MediaService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/MediaService.qml b/Services/MediaService.qml index d67b1bbe..da4b1701 100644 --- a/Services/MediaService.qml +++ b/Services/MediaService.qml @@ -126,7 +126,7 @@ Singleton { function findActivePlayer() { let availablePlayers = getAvailablePlayers() if (availablePlayers.length === 0) { - Logger.log("Media", "No active player found") + //Logger.log("Media", "No active player found") return null } From bc80a1dfaf9f0a31b1b1effbd68eb91a7f73174d Mon Sep 17 00:00:00 2001 From: Aiser <2912778691@qq.com> Date: Fri, 10 Oct 2025 12:56:47 +0800 Subject: [PATCH 048/106] Matugen: Add KColorScheme for KDE's APP --- Assets/MatugenTemplates/kcolorscheme.colors | 10 ---------- Services/MatugenTemplates.qml | 9 ++++++++- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Assets/MatugenTemplates/kcolorscheme.colors b/Assets/MatugenTemplates/kcolorscheme.colors index 5b97b234..195bcc48 100644 --- a/Assets/MatugenTemplates/kcolorscheme.colors +++ b/Assets/MatugenTemplates/kcolorscheme.colors @@ -137,16 +137,6 @@ ForegroundNormal={{colors.on_background.default.hex}} ForegroundPositive={{colors.tertiary_fixed.default.hex}} ForegroundVisited={{colors.on_secondary_container.default.hex}} -[General] -ColorScheme=Matugen -Name=Matugen - -[Appearance] -color_scheme=Matugen - -[KDE] -contrast=4 - [WM] activeBackground={{colors.primary_container.default.hex}} activeBlend={{colors.on_primary_container.default.hex}} diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml index b50450bc..0eedbe32 100644 --- a/Services/MatugenTemplates.qml +++ b/Services/MatugenTemplates.qml @@ -92,7 +92,14 @@ Singleton { "output": "~/.config/qt6ct/colors/noctalia.conf" }], "input": "qtct.conf" - }, { + },{ + "name": "kcolorscheme", + "templates":[{ + "version": "kcolorscheme", + "output": "~/.local/share/color-schemes/noctalia.colors" + }], + "input":"kcolorscheme.colors" + },{ "name": "fuzzel", "templates": [{ "version": "fuzzel", From 8f614194df94fc3f7f928bdeb5c7888bf888b314 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 10 Oct 2025 07:42:28 -0400 Subject: [PATCH 049/106] QuickSettings: added wallpaper selector to the defaults --- Assets/settings-default.json | 3 +++ Commons/Settings.qml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 05ccde05..34e4eb59 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -124,6 +124,9 @@ }, { "id": "PowerProfile" + }, + { + "id": "WallpaperSelector" } ] } diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 960edd67..2f686558 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -255,6 +255,8 @@ Singleton { "id": "ScreenRecorder" }, { "id": "PowerProfile" + }, { + "id": "WallpaperSelector" }] } } From 63881bf8a95aefcc542fcf23880b5d034a56d2fd Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 10 Oct 2025 13:42:42 +0200 Subject: [PATCH 050/106] ColorSchemeTab: auto-detect themabale discord client --- Assets/Translations/de.json | 6 +- Assets/Translations/en.json | 6 +- Assets/Translations/es.json | 6 +- Assets/Translations/fr.json | 6 +- Assets/Translations/pt.json | 6 +- Assets/Translations/zh-CN.json | 6 +- Assets/settings-default.json | 8 +- Commons/Settings.qml | 8 +- Modules/Settings/Tabs/ColorSchemeTab.qml | 35 +++-- Services/ColorSchemeService.qml | 3 +- Services/MatugenTemplates.qml | 158 ++++++++++++++++------- Services/MediaService.qml | 43 +++--- Services/ProgramCheckerService.qml | 74 ++++++++++- 13 files changed, 260 insertions(+), 105 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 021a823f..d5cab5bb 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -558,9 +558,9 @@ "description": "Schreibt {filepath} und lädt neu", "description-missing": "Erfordert fuzzel Starter" }, - "vesktop": { - "description": "Schreibt {filepath}", - "description-missing": "Erfordert vesktop Discord-Client" + "discord": { + "description": "Schreibt {filepath} für {client}", + "description-missing": "Kein Discord-Client erkannt. Installieren Sie vesktop, webcord, armcord, equibop, lightcord oder dorion." }, "pywalfox": { "description": "Schreibt {filepath} und führt pywalfox update aus", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index ff91166a..0d724ca6 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -560,9 +560,9 @@ "description": "Write {filepath} and reload", "description-missing": "Requires {app} to be installed" }, - "vesktop": { - "description": "Write {filepath}", - "description-missing": "Requires {app} to be installed" + "discord": { + "description": "Write {filepath} for {client}", + "description-missing": "No Discord client detected. Install vesktop, webcord, armcord, equibop, lightcord, or dorion." }, "pywalfox": { "description": "Write {filepath} and run pywalfox update", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index f33d2e0c..70eb4b7e 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -554,9 +554,9 @@ "description": "Escribir {filepath} y recargar", "description-missing": "Requiere que {app} esté instalado" }, - "vesktop": { - "description": "Escribir {filepath}", - "description-missing": "Requiere que {app} esté instalado" + "discord": { + "description": "Escribir {filepath} para {client}", + "description-missing": "No se detectó cliente de Discord. Instala vesktop, webcord, armcord, equibop, lightcord o dorion." }, "pywalfox": { "description": "Escribir {filepath} y ejecutar pywalfox update", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index a087896a..70d3b98a 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -554,9 +554,9 @@ "description": "Écrire ~/.config/fuzzel/themes/noctalia et recharger", "description-missing": "Nécessite que le lanceur fuzzel soit installé" }, - "vesktop": { - "description": "Écrire ~/.config/vesktop/themes/noctalia.theme.css", - "description-missing": "Nécessite que le client Discord vesktop soit installé" + "discord": { + "description": "Écrire {filepath} pour {client}", + "description-missing": "Aucun client Discord détecté. Installez vesktop, webcord, armcord, equibop, lightcord ou dorion." }, "pywalfox": { "description": "Écrire ~/.cache/wal/colors.json et exécuter pywalfox update", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index d9b1d538..bce6e588 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -520,9 +520,9 @@ "description": "Escrever {filepath} e recarregar", "description-missing": "Requer que o {app} esteja instalado" }, - "vesktop": { - "description": "Escrever {filepath}", - "description-missing": "Requer que o {app} esteja instalado" + "discord": { + "description": "Escrever {filepath} para {client}", + "description-missing": "Nenhum cliente Discord detectado. Instale vesktop, webcord, armcord, equibop, lightcord ou dorion." }, "pywalfox": { "description": "Escrever {filepath} e executar pywalfox update", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 8bdfbce4..d8cdcf7b 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -554,9 +554,9 @@ "description": "写入 {filepath} 并重新加载", "description-missing": "需要安装 {app}" }, - "vesktop": { - "description": "写入 {filepath}", - "description-missing": "需要安装 {app}" + "discord": { + "description": "为 {client} 写入 {filepath}", + "description-missing": "未检测到 Discord 客户端。请安装 vesktop、webcord、armcord、equibop、lightcord 或 dorion。" }, "pywalfox": { "description": "写入 {filepath} 并运行 pywalfox update", diff --git a/Assets/settings-default.json b/Assets/settings-default.json index a80e2bb4..43f4daf6 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -176,7 +176,13 @@ "ghostty": false, "foot": false, "fuzzel": false, - "vesktop": false, + "discord": false, + "discord_vesktop": false, + "discord_webcord": false, + "discord_armcord": false, + "discord_equibop": false, + "discord_lightcord": false, + "discord_dorion": false, "pywalfox": false, "enableUserTemplates": false }, diff --git a/Commons/Settings.qml b/Commons/Settings.qml index c0939caf..9206c80e 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -327,7 +327,13 @@ Singleton { property bool ghostty: false property bool foot: false property bool fuzzel: false - property bool vesktop: false + property bool discord: false + property bool discord_vesktop: false + property bool discord_webcord: false + property bool discord_armcord: false + property bool discord_equibop: false + property bool discord_lightcord: false + property bool discord_dorion: false property bool pywalfox: false property bool enableUserTemplates: false } diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 213ff4df..1c5ab5e8 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -494,22 +494,29 @@ ColumnLayout { } } - NCheckbox { - label: "Vesktop" - description: ProgramCheckerService.vesktopAvailable ? I18n.tr("settings.color-scheme.templates.programs.vesktop.description", { - "filepath": "~/.config/vesktop/themes/noctalia.theme.css" - }) : I18n.tr("settings.color-scheme.templates.programs.vesktop.description-missing", { - "app": "vesktop" - }) - checked: Settings.data.templates.vesktop - enabled: ProgramCheckerService.vesktopAvailable - opacity: ProgramCheckerService.vesktopAvailable ? 1.0 : 0.6 - onToggled: checked => { - if (ProgramCheckerService.vesktopAvailable) { - Settings.data.templates.vesktop = checked + // Show individual checkboxes for each detected Discord client + Repeater { + model: ProgramCheckerService.availableDiscordClients + delegate: NCheckbox { + label: modelData.name.charAt(0).toUpperCase() + modelData.name.slice(1) + description: I18n.tr("settings.color-scheme.templates.programs.discord.description", { + "client": modelData.name.charAt(0).toUpperCase() + modelData.name.slice(1), + "filepath": modelData.themePath + }) + checked: Settings.data.templates["discord_" + modelData.name] || false + onToggled: checked => { + Settings.data.templates["discord_" + modelData.name] = checked AppThemeService.generate() } - } + } + } + + // Show message if no Discord clients detected + NText { + visible: ProgramCheckerService.availableDiscordClients.length === 0 + text: I18n.tr("settings.color-scheme.templates.programs.discord.description-missing") + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeS * scaling } NCheckbox { diff --git a/Services/ColorSchemeService.qml b/Services/ColorSchemeService.qml index a211b4af..19d5fc05 100644 --- a/Services/ColorSchemeService.qml +++ b/Services/ColorSchemeService.qml @@ -161,7 +161,8 @@ Singleton { // Check if any Matugen templates are enabled function hasEnabledMatugenTemplates() { - return Settings.data.templates.gtk || Settings.data.templates.qt || Settings.data.templates.kitty || Settings.data.templates.ghostty || Settings.data.templates.foot || Settings.data.templates.fuzzel || Settings.data.templates.vesktop || Settings.data.templates.pywalfox + return Settings.data.templates.gtk || Settings.data.templates.qt || Settings.data.templates.kitty || Settings.data.templates.ghostty || Settings.data.templates.foot || Settings.data.templates.fuzzel || Settings.data.templates.discord || Settings.data.templates.discord_vesktop || Settings.data.templates.discord_webcord + || Settings.data.templates.discord_armcord || Settings.data.templates.discord_equibop || Settings.data.templates.discord_lightcord || Settings.data.templates.discord_dorion || Settings.data.templates.pywalfox } // Writer to colors.json using a JsonAdapter for safety diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml index b50450bc..935871cf 100644 --- a/Services/MatugenTemplates.qml +++ b/Services/MatugenTemplates.qml @@ -69,65 +69,127 @@ Singleton { }) } + // Applications configuration + readonly property var applications: [{ + "name": "gtk", + "templates": [{ + "version": "gtk3", + "output": "~/.config/gtk-3.0/gtk.css" + }, { + "version": "gtk4", + "output": "~/.config/gtk-4.0/gtk.css" + }], + "input": "gtk.css", + "postHook": "gsettings set org.gnome.desktop.interface color-scheme prefer-{mode}" + }, { + "name": "qt", + "templates": [{ + "version": "qt5", + "output": "~/.config/qt5ct/colors/noctalia.conf" + }, { + "version": "qt6", + "output": "~/.config/qt6ct/colors/noctalia.conf" + }], + "input": "qtct.conf" + }, { + "name": "fuzzel", + "templates": [{ + "version": "fuzzel", + "output": "~/.config/fuzzel/themes/noctalia" + }], + "input": "fuzzel.conf", + "postHook": AppThemeService.colorsApplyScript + " fuzzel" + }, { + "name": "pywalfox", + "templates": [{ + "version": "pywalfox", + "output": "~/.cache/wal/colors.json" + }], + "input": "pywalfox.json", + "postHook": AppThemeService.colorsApplyScript + " pywalfox" + }, { + "name": "discord_vesktop", + "templates": [{ + "version": "discord_vesktop", + "output": "~/.config/vesktop/themes/noctalia.theme.css" + }], + "input": "vesktop.css" + }, { + "name": "discord_webcord", + "templates": [{ + "version": "discord_webcord", + "output": "~/.config/webcord/themes/noctalia.theme.css" + }], + "input": "vesktop.css" + }, { + "name": "discord_armcord", + "templates": [{ + "version": "discord_armcord", + "output": "~/.config/armcord/themes/noctalia.theme.css" + }], + "input": "vesktop.css" + }, { + "name": "discord_equibop", + "templates": [{ + "version": "discord_equibop", + "output": "~/.config/equibop/themes/noctalia.theme.css" + }], + "input": "vesktop.css" + }, { + "name": "discord_lightcord", + "templates": [{ + "version": "discord_lightcord", + "output": "~/.config/lightcord/themes/noctalia.theme.css" + }], + "input": "vesktop.css" + }, { + "name": "discord_dorion", + "templates": [{ + "version": "discord_dorion", + "output": "~/.config/dorion/themes/noctalia.theme.css" + }], + "input": "vesktop.css" + }] + // -------------------------------- function addApplicationTemplates(lines, mode) { - var applications = [{ - "name": "gtk", - "templates": [{ - "version": "gtk3", - "output": "~/.config/gtk-3.0/gtk.css" - }, { - "version": "gtk4", - "output": "~/.config/gtk-4.0/gtk.css" - }], - "input": "gtk.css", - "postHook": "gsettings set org.gnome.desktop.interface color-scheme prefer-" + mode - }, { - "name": "qt", - "templates": [{ - "version": "qt5", - "output": "~/.config/qt5ct/colors/noctalia.conf" - }, { - "version": "qt6", - "output": "~/.config/qt6ct/colors/noctalia.conf" - }], - "input": "qtct.conf" - }, { - "name": "fuzzel", - "templates": [{ - "version": "fuzzel", - "output": "~/.config/fuzzel/themes/noctalia" - }], - "input": "fuzzel.conf", - "postHook": AppThemeService.colorsApplyScript + " fuzzel" - }, { - "name": "pywalfox", - "templates": [{ - "version": "pywalfox", - "output": "~/.cache/wal/colors.json" - }], - "input": "pywalfox.json", - "postHook": AppThemeService.colorsApplyScript + " pywalfox" - }, { - "name": "vesktop", - "templates": [{ - "version": "vesktop", - "output": "~/.config/vesktop/themes/noctalia.theme.css" - }], - "input": "vesktop.css" - }] - applications.forEach(function (app) { - if (Settings.data.templates[app.name]) { + // Check if app has a condition and if it's met + var shouldInclude = true + if (app.condition !== undefined) { + shouldInclude = app.condition + } + + if (Settings.data.templates[app.name] && shouldInclude) { app.templates.forEach(function (template) { lines.push("\n[templates." + template.version + "]") lines.push('input_path = "' + Quickshell.shellDir + '/Assets/MatugenTemplates/' + app.input + '"') lines.push('output_path = "' + template.output + '"') if (app.postHook) { - lines.push('post_hook = "' + app.postHook + '"') + var postHook = app.postHook.replace("{mode}", mode) + lines.push('post_hook = "' + postHook + '"') } }) } }) } + + // Extract Discord clients from applications array + readonly property var discordClients: { + var clients = [] + for (var i = 0; i < applications.length; i++) { + var app = applications[i] + if (app.name && app.name.startsWith("discord_")) { + var clientName = app.name.replace("discord_", "") + var themePath = app.templates[0].output + var configPath = themePath.replace("/themes/noctalia.theme.css", "") + clients.push({ + "name": clientName, + "configPath": configPath, + "themePath": themePath + }) + } + } + return clients + } } diff --git a/Services/MediaService.qml b/Services/MediaService.qml index da4b1701..006285c9 100644 --- a/Services/MediaService.qml +++ b/Services/MediaService.qml @@ -61,7 +61,8 @@ Singleton { if (title1) { for (var j = 0; j < genericPlayers.length; j++) { - if (matchedGenericIndices[j]) continue + if (matchedGenericIndices[j]) + continue let genericPlayer = genericPlayers[j] let title2 = String(genericPlayer.trackTitle || "").trim() @@ -71,27 +72,29 @@ Singleton { let scoreSpecific = (specificPlayer.trackArtUrl ? 1 : 0) let scoreGeneric = (genericPlayer.trackArtUrl ? 1 : 0) - if(scoreSpecific > scoreGeneric){ dataPlayer = specificPlayer } + if (scoreSpecific > scoreGeneric) { + dataPlayer = specificPlayer + } let virtualPlayer = { - identity: identityPlayer.identity, - desktopEntry: identityPlayer.desktopEntry, - trackTitle: dataPlayer.trackTitle, - trackArtist: dataPlayer.trackArtist, - trackAlbum: dataPlayer.trackAlbum, - trackArtUrl: dataPlayer.trackArtUrl, - length: dataPlayer.length || 0, - position: dataPlayer.position || 0, - playbackState: dataPlayer.playbackState, - isPlaying: dataPlayer.isPlaying || false, - canPlay: dataPlayer.canPlay || false, - canPause: dataPlayer.canPause || false, - canGoNext: dataPlayer.canGoNext || false, - canGoPrevious: dataPlayer.canGoPrevious || false, - canSeek: dataPlayer.canSeek || false, - canControl: dataPlayer.canControl || false, - _stateSource: dataPlayer, - _controlTarget: identityPlayer + "identity": identityPlayer.identity, + "desktopEntry": identityPlayer.desktopEntry, + "trackTitle": dataPlayer.trackTitle, + "trackArtist": dataPlayer.trackArtist, + "trackAlbum": dataPlayer.trackAlbum, + "trackArtUrl": dataPlayer.trackArtUrl, + "length": dataPlayer.length || 0, + "position": dataPlayer.position || 0, + "playbackState": dataPlayer.playbackState, + "isPlaying": dataPlayer.isPlaying || false, + "canPlay": dataPlayer.canPlay || false, + "canPause": dataPlayer.canPause || false, + "canGoNext": dataPlayer.canGoNext || false, + "canGoPrevious": dataPlayer.canGoPrevious || false, + "canSeek": dataPlayer.canSeek || false, + "canControl": dataPlayer.canControl || false, + "_stateSource": dataPlayer, + "_controlTarget": identityPlayer } finalPlayers.push(virtualPlayer) matchedGenericIndices[j] = true diff --git a/Services/ProgramCheckerService.qml b/Services/ProgramCheckerService.qml index a7d5f436..268274e3 100644 --- a/Services/ProgramCheckerService.qml +++ b/Services/ProgramCheckerService.qml @@ -16,13 +16,67 @@ Singleton { property bool ghosttyAvailable: false property bool footAvailable: false property bool fuzzelAvailable: false - property bool vesktopAvailable: false property bool gpuScreenRecorderAvailable: false property bool wlsunsetAvailable: false + // Discord client auto-detection + property var availableDiscordClients: [] + // Signal emitted when all checks are complete signal checksCompleted + // Function to detect Discord client by checking config directories + function detectDiscordClient() { + // Build list of client names from MatugenTemplates + var clientNames = [] + for (var i = 0; i < MatugenTemplates.discordClients.length; i++) { + clientNames.push(MatugenTemplates.discordClients[i].name) + } + + // Use a Process to check directory existence for all clients + discordDetector.command = ["sh", "-c", "available_clients=\"\"; " + "for client in " + clientNames.join(" ") + "; do " + " if [ -d \"$HOME/.config/$client\" ]; then " + " available_clients=\"$available_clients $client\"; " + " fi; " + "done; " + "echo \"$available_clients\""] + discordDetector.running = true + } + + // Process to detect Discord client directories + Process { + id: discordDetector + running: false + + onExited: function (exitCode) { + availableDiscordClients = [] + + if (exitCode === 0) { + var detectedClients = stdout.text.trim().split(/\s+/).filter(function (client) { + return client.length > 0 + }) + + if (detectedClients.length > 0) { + // Build list of available clients + for (var i = 0; i < detectedClients.length; i++) { + var clientName = detectedClients[i] + for (var j = 0; j < MatugenTemplates.discordClients.length; j++) { + var client = MatugenTemplates.discordClients[j] + if (client.name === clientName) { + availableDiscordClients.push(client) + break + } + } + } + + Logger.log("ProgramChecker", "Detected Discord clients:", detectedClients.join(", ")) + } + } + + if (availableDiscordClients.length === 0) { + Logger.log("ProgramChecker", "No Discord clients detected") + } + } + + stdout: StdioCollector {} + stderr: StdioCollector {} + } + // Programs to check - maps property names to commands readonly property var programsToCheck: ({ "matugenAvailable": ["which", "matugen"], @@ -31,7 +85,6 @@ Singleton { "ghosttyAvailable": ["which", "ghostty"], "footAvailable": ["which", "foot"], "fuzzelAvailable": ["which", "fuzzel"], - "vesktopAvailable": ["which", "vesktop"], "gpuScreenRecorderAvailable": ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"], "wlsunsetAvailable": ["which", "wlsunset"] }) @@ -59,6 +112,8 @@ Singleton { // Check next program or emit completion signal if (root.completedChecks >= root.totalChecks) { + // Run Discord client detection after all checks are complete + root.detectDiscordClient() root.checksCompleted() } else { root.checkNextProgram() @@ -113,6 +168,21 @@ Singleton { checker.running = true } + // Manual function to test Discord detection (for debugging) + function testDiscordDetection() { + Logger.log("ProgramChecker", "Testing Discord detection...") + Logger.log("ProgramChecker", "HOME:", Quickshell.env("HOME")) + + // Test each client directory + for (var i = 0; i < MatugenTemplates.discordClients.length; i++) { + var client = MatugenTemplates.discordClients[i] + var configDir = client.configPath.replace("~", Quickshell.env("HOME")) + Logger.log("ProgramChecker", "Checking:", configDir) + } + + detectDiscordClient() + } + // Initialize checks when service is created Component.onCompleted: { checkAllPrograms() From 93803f13090751d3172007028308ded7cfa4b0b8 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 10 Oct 2025 08:02:10 -0400 Subject: [PATCH 051/106] autofmt --- Commons/Settings.qml | 1 - Modules/Bar/Widgets/Tray.qml | 23 +++--- .../Bar/WidgetSettings/TraySettings.qml | 82 ++++++++++--------- 3 files changed, 55 insertions(+), 51 deletions(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 149458c9..f2592348 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -140,7 +140,6 @@ Singleton { property real marginVertical: 0.25 property real marginHorizontal: 0.25 - // Widget configuration for modular bar system property JsonObject widgets widgets: JsonObject { diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 24c05980..e24da899 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -42,26 +42,27 @@ Rectangle { function wildCardMatch(str, rule) { if (!str || !rule) { - return false; + return false } - Logger.log("Tray", "wildCardMatch - Input str:", str, "rule:", rule); + Logger.log("Tray", "wildCardMatch - Input str:", str, "rule:", rule) // Escape all special regex characters in the rule - let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Convert '*' to '.*' for wildcard matching - let pattern = escapedRule.replace(/\\\*/g, '.*'); + let pattern = escapedRule.replace(/\\\*/g, '.*') // Add ^ and $ to match the entire string - pattern = '^' + pattern + '$'; + pattern = '^' + pattern + '$' - Logger.log("Tray", "wildCardMatch - Generated pattern:", pattern); + Logger.log("Tray", "wildCardMatch - Generated pattern:", pattern) try { - const regex = new RegExp(pattern, 'i'); // 'i' for case-insensitive - Logger.log("Tray", "wildCardMatch - Regex test result:", regex.test(str)); - return regex.test(str); + const regex = new RegExp(pattern, 'i') + // 'i' for case-insensitive + Logger.log("Tray", "wildCardMatch - Regex test result:", regex.test(str)) + return regex.test(str) } catch (e) { - Logger.warn("Tray", "Invalid regex pattern for wildcard match:", rule, e.message); - return false; // If regex is invalid, it won't match + Logger.warn("Tray", "Invalid regex pattern for wildcard match:", rule, e.message) + return false // If regex is invalid, it won't match } } diff --git a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml index 77f00d5d..25d08bce 100644 --- a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml @@ -19,7 +19,9 @@ ColumnLayout { Component.onCompleted: { // Populate the ListModel from localBlacklist for (var i = 0; i < localBlacklist.length; i++) { - blacklistModel.append({"rule": localBlacklist[i]}) + blacklistModel.append({ + "rule": localBlacklist[i] + }) } } @@ -59,7 +61,9 @@ ColumnLayout { } } if (!exists) { - blacklistModel.append({"rule": newRule}) + blacklistModel.append({ + "rule": newRule + }) newRuleInput.text = "" } } @@ -77,47 +81,47 @@ ColumnLayout { clip: true model: blacklistModel delegate: Item { - width: ListView.width - height: 40 * scaling + width: ListView.width + height: 40 * scaling - Rectangle { - id: itemBackground - anchors.fill: parent - anchors.margins: Style.marginXS * scaling - color: Color.transparent // Make background transparent - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - radius: Style.radiusS * scaling - visible: model.rule !== undefined && model.rule !== "" // Only visible if rule exists + Rectangle { + id: itemBackground + anchors.fill: parent + anchors.margins: Style.marginXS * scaling + color: Color.transparent // Make background transparent + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + radius: Style.radiusS * scaling + visible: model.rule !== undefined && model.rule !== "" // Only visible if rule exists + } + + Row { + anchors.fill: parent + anchors.leftMargin: Style.marginS * scaling + anchors.rightMargin: Style.marginS * scaling + spacing: Style.marginS * scaling + + NText { + text: model.rule + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + Layout.fillWidth: true } - Row { - anchors.fill: parent - anchors.leftMargin: Style.marginS * scaling - anchors.rightMargin: Style.marginS * scaling - spacing: Style.marginS * scaling - - NText { - text: model.rule - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - Layout.fillWidth: true - } - - NIconButton { - width: 16 * scaling - height: 16 * scaling - icon: "close" - baseSize: 8 * scaling - colorBg: Color.mSurfaceVariant - colorFg: Color.mOnSurface - colorBgHover: Color.mError - colorFgHover: Color.mOnError - onClicked: { - blacklistModel.remove(index) - } - } + NIconButton { + width: 16 * scaling + height: 16 * scaling + icon: "close" + baseSize: 8 * scaling + colorBg: Color.mSurfaceVariant + colorFg: Color.mOnSurface + colorBgHover: Color.mError + colorFgHover: Color.mOnError + onClicked: { + blacklistModel.remove(index) + } } + } } } From 82eefbc65cc8d8995d26292219d629cca353164d Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 10 Oct 2025 14:02:37 +0200 Subject: [PATCH 052/106] Bin: move dev scripts in Bin/dev/ --- Bin/{ => dev}/i18n-json-check.sh | 0 Bin/{ => dev}/i18n-qml-check.sh | 0 Bin/{ => dev}/notifications-test.sh | 0 Bin/{ => dev}/qmlfmt.sh | 0 Bin/{ => dev}/shaders-compile.sh | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename Bin/{ => dev}/i18n-json-check.sh (100%) rename Bin/{ => dev}/i18n-qml-check.sh (100%) rename Bin/{ => dev}/notifications-test.sh (100%) rename Bin/{ => dev}/qmlfmt.sh (100%) rename Bin/{ => dev}/shaders-compile.sh (100%) diff --git a/Bin/i18n-json-check.sh b/Bin/dev/i18n-json-check.sh similarity index 100% rename from Bin/i18n-json-check.sh rename to Bin/dev/i18n-json-check.sh diff --git a/Bin/i18n-qml-check.sh b/Bin/dev/i18n-qml-check.sh similarity index 100% rename from Bin/i18n-qml-check.sh rename to Bin/dev/i18n-qml-check.sh diff --git a/Bin/notifications-test.sh b/Bin/dev/notifications-test.sh similarity index 100% rename from Bin/notifications-test.sh rename to Bin/dev/notifications-test.sh diff --git a/Bin/qmlfmt.sh b/Bin/dev/qmlfmt.sh similarity index 100% rename from Bin/qmlfmt.sh rename to Bin/dev/qmlfmt.sh diff --git a/Bin/shaders-compile.sh b/Bin/dev/shaders-compile.sh similarity index 100% rename from Bin/shaders-compile.sh rename to Bin/dev/shaders-compile.sh From 7a403bbddeaaea22499ab60d94a7a85327db1bf8 Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 10 Oct 2025 14:51:42 +0200 Subject: [PATCH 053/106] ControlCenter: add volume controls --- Modules/ControlCenter/Cards/AudioCard.qml | 189 +++++++++++++++++++ Modules/ControlCenter/ControlCenterPanel.qml | 9 +- 2 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 Modules/ControlCenter/Cards/AudioCard.qml diff --git a/Modules/ControlCenter/Cards/AudioCard.qml b/Modules/ControlCenter/Cards/AudioCard.qml new file mode 100644 index 00000000..7fa760bc --- /dev/null +++ b/Modules/ControlCenter/Cards/AudioCard.qml @@ -0,0 +1,189 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +// Audio controls card: output and input volume controls +NBox { + id: root + + property real localOutputVolume: AudioService.volume + property real localInputVolume: AudioService.inputVolume + + // Timer to debounce volume changes (similar to AudioTab) + Timer { + interval: 100 + running: true + repeat: true + onTriggered: { + if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) { + AudioService.setVolume(localOutputVolume) + } + } + } + + // Connections to update local volumes when AudioService changes + Connections { + target: AudioService.sink?.audio ? AudioService.sink?.audio : null + function onVolumeChanged() { + localOutputVolume = AudioService.volume + } + } + + Connections { + target: AudioService.source?.audio ? AudioService.source?.audio : null + function onVolumeChanged() { + localInputVolume = AudioService.inputVolume + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginM * scaling + spacing: Style.marginM * scaling + + // Output Volume Section + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + opacity: AudioService.sink ? 1.0 : 0.5 + enabled: AudioService.sink + + // Output Volume Header + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS * scaling + + NIconButton { + icon: AudioService.muted ? "volume-off" : "volume-high" + baseSize: Style.baseWidgetSize * 0.5 + colorFg: AudioService.muted ? Color.mError : Color.mOnSurfaceVariant + colorBg: Color.transparent + colorBgHover: Color.mTertiary + colorFgHover: Color.mOnTertiary + onClicked: { + if (AudioService.sink && AudioService.sink.audio) { + AudioService.sink.audio.muted = !AudioService.muted + } + } + } + + RowLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NText { + text: I18n.tr("settings.audio.volumes.output-volume.label") + pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurface + font.weight: Style.fontWeightMedium + } + + NText { + text: AudioService.sink ? AudioService.sink.description : "No output device" + pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + font.weight: Style.fontWeightMedium + elide: Text.ElideRight + Layout.fillWidth: true + } + } + } + + // Output Volume Slider + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS * scaling + + NSlider { + Layout.fillWidth: true + from: 0 + to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 + value: localOutputVolume + stepSize: 0.01 + onMoved: value => localOutputVolume = value + } + + NText { + text: Math.round(AudioService.volume * 100) + "%" + pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + font.weight: Style.fontWeightMedium + verticalAlignment: Text.AlignVCenter + Layout.alignment: Qt.AlignVCenter + } + } + } + + // Input Volume Section + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + opacity: AudioService.source ? 1.0 : 0.5 + enabled: AudioService.source + + // Input Volume Header + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS * scaling + + NIconButton { + icon: AudioService.inputMuted ? "microphone-off" : "microphone" + baseSize: Style.baseWidgetSize * 0.5 + colorFg: AudioService.inputMuted ? Color.mError : Color.mOnSurfaceVariant + colorBg: Color.transparent + colorBgHover: Color.mTertiary + colorFgHover: Color.mOnTertiary + onClicked: AudioService.setInputMuted(!AudioService.inputMuted) + } + + RowLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NText { + text: I18n.tr("settings.audio.volumes.input-volume.label") + pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurface + font.weight: Style.fontWeightMedium + } + + NText { + text: AudioService.source ? AudioService.source.description : "No input device" + pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + font.weight: Style.fontWeightMedium + elide: Text.ElideRight + Layout.fillWidth: true + } + } + } + + // Input Volume Slider + RowLayout { + Layout.fillWidth: true + spacing: Style.marginXS * scaling + + NSlider { + Layout.fillWidth: true + from: 0 + to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 + value: localInputVolume + stepSize: 0.01 + onMoved: value => AudioService.setInputVolume(value) + } + + NText { + text: Math.round(AudioService.inputVolume * 100) + "%" + pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + font.weight: Style.fontWeightMedium + verticalAlignment: Text.AlignVCenter + Layout.alignment: Qt.AlignVCenter + } + } + } + } +} diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index e5b2145d..2d5ba9bf 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -11,7 +11,7 @@ NPanel { id: root preferredWidth: 400 - preferredHeight: topHeight + midHeight + bottomHeight + Math.round(Style.marginL * 4) + preferredHeight: topHeight + midHeight + bottomHeight + audioHeight + Math.round(Style.marginL * 5) panelKeyboardFocus: true readonly property int topHeight: { @@ -31,6 +31,7 @@ NPanel { } readonly property int midHeight: 220 readonly property int bottomHeight: 80 + readonly property int audioHeight: 120 // Positioning readonly property string controlCenterPosition: Settings.data.controlCenter.position @@ -60,6 +61,12 @@ NPanel { Layout.preferredHeight: topHeight * scaling } + // Audio controls card + AudioCard { + Layout.fillWidth: true + Layout.preferredHeight: audioHeight * scaling + } + // Media card MediaCard { Layout.fillWidth: true From 999970f8da786e947e1aa03a0033a351997593bb Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 10 Oct 2025 08:58:37 -0400 Subject: [PATCH 054/106] NCircleStat: polished the look --- Widgets/NCircleStat.qml | 44 ++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/Widgets/NCircleStat.qml b/Widgets/NCircleStat.qml index 9c924883..f6d4332d 100644 --- a/Widgets/NCircleStat.qml +++ b/Widgets/NCircleStat.qml @@ -61,17 +61,31 @@ Rectangle { ctx.reset() ctx.lineWidth = 6 * scaling * contentScale - // Track uses surfaceVariant for stronger contrast + // Track uses surface for stronger contrast ctx.strokeStyle = Color.mSurface ctx.beginPath() ctx.arc(cx, cy, r, start, endBg) ctx.stroke() - // Value arc + // Value arc with gradient starting at 25% const ratio = Math.max(0, Math.min(1, root.value / 100)) const end = start + (endBg - start) * ratio - ctx.strokeStyle = Color.mPrimary + // Calculate gradient start point (25% into the arc) + const gradientStartRatio = 0.25 + const gradientStart = start + (endBg - start) * gradientStartRatio + + // Create linear gradient + const startX = cx + r * Math.cos(gradientStart) + const startY = cy + r * Math.sin(gradientStart) + const endX = cx + r * Math.cos(endBg) + const endY = cy + r * Math.sin(endBg) + + const gradient = ctx.createLinearGradient(startX, startY, endX, endY) + gradient.addColorStop(0, Color.mPrimary) + gradient.addColorStop(1, Color.mOnSurface) + + ctx.strokeStyle = gradient ctx.beginPath() ctx.arc(cx, cy, r, start, end) ctx.stroke() @@ -90,26 +104,16 @@ Rectangle { horizontalAlignment: Text.AlignHCenter } - // Tiny circular badge for the icon, positioned inside below the percentage - Rectangle { - id: iconBadge - width: iconText.implicitWidth + Style.marginXXS * scaling - height: width - radius: width / 2 - color: Color.mPrimary + NIcon { + id: iconText anchors.horizontalCenter: parent.horizontalCenter anchors.top: valueLabel.bottom anchors.topMargin: 8 * scaling * contentScale - - NIcon { - id: iconText - anchors.centerIn: parent - icon: root.icon - color: Color.mOnPrimary - pointSize: Style.fontSizeS * scaling - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + icon: root.icon + color: Color.mPrimary + pointSize: Style.fontSizeM * scaling + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter } } } From 2f527dcf61908ae142e5e158e7913b93e2ec0fa1 Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 10 Oct 2025 14:59:12 +0200 Subject: [PATCH 055/106] AudioCard: fix issue, change size of slider --- Modules/ControlCenter/Cards/AudioCard.qml | 58 +++++++---------------- Widgets/NValueSlider.qml | 7 ++- 2 files changed, 24 insertions(+), 41 deletions(-) diff --git a/Modules/ControlCenter/Cards/AudioCard.qml b/Modules/ControlCenter/Cards/AudioCard.qml index 7fa760bc..efa0117a 100644 --- a/Modules/ControlCenter/Cards/AudioCard.qml +++ b/Modules/ControlCenter/Cards/AudioCard.qml @@ -93,27 +93,16 @@ NBox { } // Output Volume Slider - RowLayout { + NValueSlider { Layout.fillWidth: true - spacing: Style.marginXS * scaling - - NSlider { - Layout.fillWidth: true - from: 0 - to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 - value: localOutputVolume - stepSize: 0.01 - onMoved: value => localOutputVolume = value - } - - NText { - text: Math.round(AudioService.volume * 100) + "%" - pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - font.weight: Style.fontWeightMedium - verticalAlignment: Text.AlignVCenter - Layout.alignment: Qt.AlignVCenter - } + from: 0 + to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 + value: localOutputVolume || 0 + stepSize: 0.01 + text: Math.round((AudioService.volume || 0) * 100) + "%" + textSize: Style.fontSizeXS * scaling + customHeightRatio: 0.6 + onMoved: value => localOutputVolume = value } } @@ -162,27 +151,16 @@ NBox { } // Input Volume Slider - RowLayout { + NValueSlider { Layout.fillWidth: true - spacing: Style.marginXS * scaling - - NSlider { - Layout.fillWidth: true - from: 0 - to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 - value: localInputVolume - stepSize: 0.01 - onMoved: value => AudioService.setInputVolume(value) - } - - NText { - text: Math.round(AudioService.inputVolume * 100) + "%" - pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - font.weight: Style.fontWeightMedium - verticalAlignment: Text.AlignVCenter - Layout.alignment: Qt.AlignVCenter - } + from: 0 + to: Settings.data.audio.volumeOverdrive ? 1.5 : 1.0 + value: AudioService.inputVolume || 0 + stepSize: 0.01 + text: Math.round((AudioService.inputVolume || 0) * 100) + "%" + textSize: Style.fontSizeXS * scaling + customHeightRatio: 0.6 + onMoved: value => AudioService.setInputVolume(value) } } } diff --git a/Widgets/NValueSlider.qml b/Widgets/NValueSlider.qml index 32d8ed76..3eb6538b 100644 --- a/Widgets/NValueSlider.qml +++ b/Widgets/NValueSlider.qml @@ -16,12 +16,16 @@ RowLayout { property bool snapAlways: true property real heightRatio: 0.7 property string text: "" + property real textSize: Style.fontSizeM * scaling + property real customHeight: -1 + property real customHeightRatio: -1 // Signals signal moved(real value) signal pressedChanged(bool pressed, real value) spacing: Style.marginL * scaling + implicitHeight: root.customHeight > 0 ? root.customHeight : slider.implicitHeight NSlider { id: slider @@ -32,7 +36,7 @@ RowLayout { stepSize: root.stepSize cutoutColor: root.cutoutColor snapAlways: root.snapAlways - heightRatio: root.heightRatio + heightRatio: root.customHeightRatio > 0 ? root.customHeightRatio : root.heightRatio onMoved: root.moved(value) onPressedChanged: root.pressedChanged(pressed, value) } @@ -40,6 +44,7 @@ RowLayout { NText { visible: root.text !== "" text: root.text + pointSize: root.textSize family: Settings.data.ui.fontFixed Layout.alignment: Qt.AlignVCenter Layout.preferredWidth: 45 * scaling From acd776a187e237d3982a21f8fdbbceeeb1da9a42 Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 10 Oct 2025 15:08:25 +0200 Subject: [PATCH 056/106] OSD: add audio input osd IPC: add audio input --- Modules/OSD/OSD.qml | 40 ++++++++++++++++++++++++++++++++++++++-- Services/IPCService.qml | 10 +++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 91c551a8..a4ec5701 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -25,7 +25,7 @@ Variants { active: false // Current OSD display state - property string currentOSDType: "" // "volume", "brightness", or "" + property string currentOSDType: "" // "volume", "inputVolume", "brightness", or "" // Volume properties readonly property real currentVolume: AudioService.volume @@ -33,6 +33,12 @@ Variants { property bool volumeInitialized: false property bool muteInitialized: false + // Input volume properties + readonly property real currentInputVolume: AudioService.inputVolume + readonly property bool isInputMuted: AudioService.inputMuted + property bool inputVolumeInitialized: false + property bool inputMuteInitialized: false + // Brightness properties property bool brightnessInitialized: false readonly property real currentBrightness: { @@ -49,6 +55,11 @@ Variants { return "volume-mute" } return (AudioService.volume <= Number.EPSILON) ? "volume-zero" : (AudioService.volume <= 0.5) ? "volume-low" : "volume-high" + } else if (currentOSDType === "inputVolume") { + if (AudioService.inputMuted) { + return "microphone-off" + } + return "microphone" } else if (currentOSDType === "brightness") { return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high" } @@ -59,6 +70,8 @@ Variants { function getCurrentValue() { if (currentOSDType === "volume") { return isMuted ? 0 : currentVolume + } else if (currentOSDType === "inputVolume") { + return isInputMuted ? 0 : currentInputVolume } else if (currentOSDType === "brightness") { return currentBrightness } @@ -72,6 +85,11 @@ Variants { return "0%" const pct = Math.round(Math.min(1.0, currentVolume) * 100) return pct + "%" + } else if (currentOSDType === "inputVolume") { + if (isInputMuted) + return "0%" + const pct = Math.round(Math.min(1.0, currentInputVolume) * 100) + return pct + "%" } else if (currentOSDType === "brightness") { const pct = Math.round(Math.min(1.0, currentBrightness) * 100) return pct + "%" @@ -85,13 +103,17 @@ Variants { if (isMuted) return Color.mError return Color.mPrimary + } else if (currentOSDType === "inputVolume") { + if (isInputMuted) + return Color.mError + return Color.mPrimary } return Color.mPrimary } // Get icon color function getIconColor() { - if (currentOSDType === "volume" && isMuted) { + if ((currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted)) { return Color.mError } return Color.mOnSurface @@ -467,6 +489,18 @@ Variants { showOSD("volume") } } + + function onInputVolumeChanged() { + if (inputVolumeInitialized) { + showOSD("inputVolume") + } + } + + function onInputMutedChanged() { + if (inputMuteInitialized) { + showOSD("inputVolume") + } + } } // Timer to initialize volume/mute flags after services are ready @@ -477,6 +511,8 @@ Variants { onTriggered: { volumeInitialized = true muteInitialized = true + inputVolumeInitialized = true + inputMuteInitialized = true } } diff --git a/Services/IPCService.qml b/Services/IPCService.qml index 711d44dd..70999aa1 100644 --- a/Services/IPCService.qml +++ b/Services/IPCService.qml @@ -112,10 +112,14 @@ Item { function muteOutput() { AudioService.setOutputMuted(!AudioService.muted) } + function increaseInput() { + AudioService.increaseInputVolume() + } + function decreaseInput() { + AudioService.decreaseInputVolume() + } function muteInput() { - if (AudioService.source?.ready && AudioService.source?.audio) { - AudioService.source.audio.muted = !AudioService.source.audio.muted - } + AudioService.setInputMuted(!AudioService.inputMuted) } } From 688d2d1d8fca30e37bab022844645bded99fdced Mon Sep 17 00:00:00 2001 From: lysec Date: Fri, 10 Oct 2025 15:44:07 +0200 Subject: [PATCH 057/106] MediaCard: set slider size to 0.6 --- Modules/ControlCenter/Cards/MediaCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ControlCenter/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml index 4484e27f..2b362fb0 100644 --- a/Modules/ControlCenter/Cards/MediaCard.qml +++ b/Modules/ControlCenter/Cards/MediaCard.qml @@ -376,7 +376,7 @@ NBox { stepSize: 0 snapAlways: false enabled: MediaService.trackLength > 0 && MediaService.canSeek - heightRatio: 0.65 + heightRatio: 0.6 onMoved: { progressWrapper.localSeekRatio = value From 9ff5eb98080e3f3534eb22480cfa4505ed9745b1 Mon Sep 17 00:00:00 2001 From: Victor Choueiri Date: Fri, 10 Oct 2025 16:00:22 +0200 Subject: [PATCH 058/106] Add DockMenu actions * Adds app-specific actions from the DesktopEntry to the context menu / DockMenu * Only displays actionable actions (ie: does not show Close or Focus if the app is not running, as those would do nothing) --- Modules/Dock/DockMenu.qml | 238 ++++++++++++++++++-------------------- 1 file changed, 112 insertions(+), 126 deletions(-) diff --git a/Modules/Dock/DockMenu.qml b/Modules/Dock/DockMenu.qml index 30de31e4..b3570bc3 100644 --- a/Modules/Dock/DockMenu.qml +++ b/Modules/Dock/DockMenu.qml @@ -18,15 +18,67 @@ PopupWindow { property var onAppClosed: null // Callback function for when an app is closed // Track which menu item is hovered - property int hoveredItem: -1 // -1: none, 0: focus, 1: pin, 2: close + property int hoveredItem: -1 // -1: none, otherwise the index of the item in `items` + + property var items: [] signal requestClose - implicitWidth: 140 * scaling + implicitWidth: 160 * scaling implicitHeight: contextMenuColumn.implicitHeight + (Style.marginM * scaling * 2) color: Color.transparent visible: false + function initItems() { + // Is this a running app? + const isRunning = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel) + + // Is this a pinned app? + const isPinned = root.toplevel && root.isAppPinned(root.toplevel.appId) + + var next = [] + if (isRunning) { + // Focus item + next.push({ + "icon": "eye", + "text": I18n.tr("dock.menu.focus"), + "action": function() { handleFocus() } + }) + } + + // Pin/Unpin item + next.push({ + "icon": !isPinned ? "pin" : "unpin", + "text": !isPinned ? I18n.tr("dock.menu.pin") : I18n.tr("dock.menu.unpin"), + "action": function() { handlePin() } + }) + + if (isRunning) { + // Close item + next.push({ + "icon": "close", + "text": I18n.tr("dock.menu.close"), + "action": function() { handleClose() } + }) + } + + // Create a menu entry for each app-specific action definied in its .desktop file + if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) { + const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId) + if (entry != null) { + entry.actions.forEach(function(action) { + next.push({ + "icon": "", + "text": action.name, + "action": function() { action.execute() } + }) + }) + } + } + + root.items = next + } + // Helper functions for pin/unpin functionality function isAppPinned(appId) { if (!appId) @@ -66,11 +118,13 @@ PopupWindow { anchorItem = item toplevel = toplevelData + initItems() visible = true } function hide() { visible = false + root.items.length = 0 } // Helper function to determine which menu item is under the mouse @@ -83,44 +137,38 @@ PopupWindow { return -1 const itemIndex = Math.floor(relativeY / itemHeight) - return itemIndex >= 0 && itemIndex < 3 ? itemIndex : -1 + return itemIndex >= 0 && itemIndex < root.items.length ? itemIndex : -1 } - // Handle menu item clicks - function handleItemClick(itemIndex) { - switch (itemIndex) { - case 0: - // Focus - if (root.toplevel?.activate) { - root.toplevel.activate() - } - root.requestClose() - break - case 1: - // Pin/Unpin - if (root.toplevel?.appId) { - root.toggleAppPin(root.toplevel.appId) - } - root.requestClose() - break - case 2: - // Close - // Check if toplevel is still valid before trying to close it - const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel) - - if (isValidToplevel && root.toplevel.close) { - root.toplevel.close() - // Trigger immediate dock update callback if provided - if (root.onAppClosed && typeof root.onAppClosed === "function") { - Qt.callLater(root.onAppClosed) - } - } else { - Logger.warn("DockMenu", "Cannot close app - invalid toplevel reference") - } - root.hide() - root.requestClose() - break + function handleFocus() { + if (root.toplevel?.activate) { + root.toplevel.activate() } + root.requestClose() + } + + function handlePin() { + if (root.toplevel?.appId) { + root.toggleAppPin(root.toplevel.appId) + } + root.requestClose() + } + + function handleClose() { + // Check if toplevel is still valid before trying to close it + const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel) + + if (isValidToplevel && root.toplevel.close) { + root.toplevel.close() + // Trigger immediate dock update callback if provided + if (root.onAppClosed && typeof root.onAppClosed === "function") { + Qt.callLater(root.onAppClosed) + } + } else { + Logger.warn("DockMenu", "Cannot close app - invalid toplevel reference") + } + root.hide() + root.requestClose() } Timer { @@ -163,7 +211,7 @@ PopupWindow { onClicked: mouse => { const clickedItem = root.getHoveredItem(mouse.y) if (clickedItem >= 0) { - root.handleItemClick(clickedItem) + root.items[clickedItem].action.call() } } } @@ -174,99 +222,37 @@ PopupWindow { anchors.margins: Style.marginM * scaling spacing: 0 - // Focus item - Rectangle { - Layout.fillWidth: true - height: 32 * scaling - color: root.hoveredItem === 0 ? Color.mTertiary : Color.transparent - radius: Style.radiusXS * scaling + Repeater { + model: root.items - RowLayout { - anchors.left: parent.left - anchors.leftMargin: Style.marginS * scaling - anchors.verticalCenter: parent.verticalCenter - spacing: Style.marginS * scaling + Rectangle { + Layout.fillWidth: true + height: 32 * scaling + color: root.hoveredItem === index ? Color.mTertiary : Color.transparent + radius: Style.radiusXS * scaling - NIcon { - icon: "eye" - pointSize: Style.fontSizeL * scaling - color: root.hoveredItem === 0 ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } + RowLayout { + anchors.left: parent.left + anchors.leftMargin: Style.marginS * scaling + anchors.verticalCenter: parent.verticalCenter + spacing: Style.marginS * scaling - NText { - text: I18n.tr("dock.menu.focus") - pointSize: Style.fontSizeS * scaling - color: root.hoveredItem === 0 ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - } - } + NIcon { + icon: modelData.icon + pointSize: Style.fontSizeL * scaling + color: root.hoveredItem === index ? Color.mOnTertiary : Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignVCenter + } - // Pin/Unpin item - Rectangle { - Layout.fillWidth: true - height: 32 * scaling - color: root.hoveredItem === 1 ? Color.mTertiary : Color.transparent - radius: Style.radiusXS * scaling - - RowLayout { - anchors.left: parent.left - anchors.leftMargin: Style.marginS * scaling - anchors.verticalCenter: parent.verticalCenter - spacing: Style.marginS * scaling - - NIcon { - icon: { - if (!root.toplevel) - return "pin" - return root.isAppPinned(root.toplevel.appId) ? "unpin" : "pin" + NText { + text: modelData.text + pointSize: Style.fontSizeS * scaling + color: root.hoveredItem === index ? Color.mOnTertiary : Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignVCenter + elide: Text.ElideRight } - pointSize: Style.fontSizeL * scaling - color: root.hoveredItem === 1 ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - - NText { - text: { - if (!root.toplevel) - return I18n.tr("dock.menu.pin") - return root.isAppPinned(root.toplevel.appId) ? I18n.tr("dock.menu.unpin") : I18n.tr("dock.menu.pin") - } - pointSize: Style.fontSizeS * scaling - color: root.hoveredItem === 1 ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - } - } - - // Close item - Rectangle { - Layout.fillWidth: true - height: 32 * scaling - color: root.hoveredItem === 2 ? Color.mTertiary : Color.transparent - radius: Style.radiusXS * scaling - - RowLayout { - anchors.left: parent.left - anchors.leftMargin: Style.marginS * scaling - anchors.verticalCenter: parent.verticalCenter - spacing: Style.marginS * scaling - - NIcon { - icon: "close" - pointSize: Style.fontSizeL * scaling - color: root.hoveredItem === 2 ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - - NText { - text: I18n.tr("dock.menu.close") - pointSize: Style.fontSizeS * scaling - color: root.hoveredItem === 2 ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - } + } + } } } } From 7df875dd3e679c7c1a7956df15f6c3926c508441 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 10 Oct 2025 12:04:09 -0400 Subject: [PATCH 059/106] Background: avoid resizing wallpapers if one of the axis fits perfectly on screen. --- Modules/Background/Background.qml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 3760ff0c..4b917522 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -121,6 +121,7 @@ Variants { visible: false cache: false asynchronous: true + sourceSize: undefined onStatusChanged: { if (status === Image.Error) { @@ -137,6 +138,11 @@ Variants { } function calculateSourceSize() { + if (implicitWidth === modelData.width || implicitHeight === modelData.height) { + // Do not resize if one of the dimensions fits perfectly on the screen + return + } + if (implicitWidth > 0 && implicitHeight > 0) { const imageAspectRatio = implicitWidth / implicitHeight if (modelData.width >= modelData.height) { @@ -161,6 +167,7 @@ Variants { visible: false cache: false asynchronous: true + sourceSize: undefined onStatusChanged: { if (status === Image.Error) { @@ -177,6 +184,11 @@ Variants { } function calculateSourceSize() { + if (implicitWidth === modelData.width || implicitHeight === modelData.height) { + // Do not resize if one of the dimensions fits perfectly on the screen + return + } + if (implicitWidth > 0 && implicitHeight > 0) { const imageAspectRatio = implicitWidth / implicitHeight if (modelData.width >= modelData.height) { From 72b2b9e9175181367b1d6758cdfba215fee8da1e Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 10 Oct 2025 13:31:02 -0400 Subject: [PATCH 060/106] autofmt --- Modules/Dock/DockMenu.qml | 92 +++++++++++++----------- Modules/Settings/Tabs/ColorSchemeTab.qml | 2 +- Services/AppThemeService.qml | 4 +- Services/MatugenTemplates.qml | 16 ++--- 4 files changed, 61 insertions(+), 53 deletions(-) diff --git a/Modules/Dock/DockMenu.qml b/Modules/Dock/DockMenu.qml index b3570bc3..73c3dcf4 100644 --- a/Modules/Dock/DockMenu.qml +++ b/Modules/Dock/DockMenu.qml @@ -40,38 +40,46 @@ PopupWindow { if (isRunning) { // Focus item next.push({ - "icon": "eye", - "text": I18n.tr("dock.menu.focus"), - "action": function() { handleFocus() } - }) + "icon": "eye", + "text": I18n.tr("dock.menu.focus"), + "action": function () { + handleFocus() + } + }) } // Pin/Unpin item next.push({ - "icon": !isPinned ? "pin" : "unpin", - "text": !isPinned ? I18n.tr("dock.menu.pin") : I18n.tr("dock.menu.unpin"), - "action": function() { handlePin() } - }) + "icon": !isPinned ? "pin" : "unpin", + "text": !isPinned ? I18n.tr("dock.menu.pin") : I18n.tr("dock.menu.unpin"), + "action": function () { + handlePin() + } + }) if (isRunning) { // Close item next.push({ - "icon": "close", - "text": I18n.tr("dock.menu.close"), - "action": function() { handleClose() } - }) + "icon": "close", + "text": I18n.tr("dock.menu.close"), + "action": function () { + handleClose() + } + }) } // Create a menu entry for each app-specific action definied in its .desktop file if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) { const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId) if (entry != null) { - entry.actions.forEach(function(action) { + entry.actions.forEach(function (action) { next.push({ - "icon": "", - "text": action.name, - "action": function() { action.execute() } - }) + "icon": "", + "text": action.name, + "action": function () { + action.execute() + } + }) }) } } @@ -225,34 +233,34 @@ PopupWindow { Repeater { model: root.items - Rectangle { - Layout.fillWidth: true - height: 32 * scaling - color: root.hoveredItem === index ? Color.mTertiary : Color.transparent - radius: Style.radiusXS * scaling + Rectangle { + Layout.fillWidth: true + height: 32 * scaling + color: root.hoveredItem === index ? Color.mTertiary : Color.transparent + radius: Style.radiusXS * scaling - RowLayout { - anchors.left: parent.left - anchors.leftMargin: Style.marginS * scaling - anchors.verticalCenter: parent.verticalCenter - spacing: Style.marginS * scaling + RowLayout { + anchors.left: parent.left + anchors.leftMargin: Style.marginS * scaling + anchors.verticalCenter: parent.verticalCenter + spacing: Style.marginS * scaling - NIcon { - icon: modelData.icon - pointSize: Style.fontSizeL * scaling - color: root.hoveredItem === index ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - } - - NText { - text: modelData.text - pointSize: Style.fontSizeS * scaling - color: root.hoveredItem === index ? Color.mOnTertiary : Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignVCenter - elide: Text.ElideRight + NIcon { + icon: modelData.icon + pointSize: Style.fontSizeL * scaling + color: root.hoveredItem === index ? Color.mOnTertiary : Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignVCenter } - } - } + + NText { + text: modelData.text + pointSize: Style.fontSizeS * scaling + color: root.hoveredItem === index ? Color.mOnTertiary : Color.mOnSurfaceVariant + Layout.alignment: Qt.AlignVCenter + elide: Text.ElideRight + } + } + } } } } diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 43e05da1..d8e8f338 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -405,7 +405,7 @@ ColumnLayout { AppThemeService.generate() } } - + NCheckbox { label: "KColorScheme" description: I18n.tr("settings.color-scheme.templates.ui.kcolorscheme.description", { diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index c3e60a22..0113216b 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -44,8 +44,8 @@ Singleton { "kcolorscheme": { "input": "kcolorscheme.colors", "outputs": [{ - "path": "~/.local/share/color-schemes/noctalia.colors" - }], + "path": "~/.local/share/color-schemes/noctalia.colors" + }] }, "fuzzel": { "input": "fuzzel.conf", diff --git a/Services/MatugenTemplates.qml b/Services/MatugenTemplates.qml index 3bd5072c..c7da9c3c 100644 --- a/Services/MatugenTemplates.qml +++ b/Services/MatugenTemplates.qml @@ -91,14 +91,14 @@ Singleton { "output": "~/.config/qt6ct/colors/noctalia.conf" }], "input": "qtct.conf" - },{ - "name": "kcolorscheme", - "templates":[{ - "version": "kcolorscheme", - "output": "~/.local/share/color-schemes/noctalia.colors" - }], - "input":"kcolorscheme.colors" - },{ + }, { + "name": "kcolorscheme", + "templates": [{ + "version": "kcolorscheme", + "output": "~/.local/share/color-schemes/noctalia.colors" + }], + "input": "kcolorscheme.colors" + }, { "name": "fuzzel", "templates": [{ "version": "fuzzel", From 9c7dab92d02ce62d59153b9d88eb05679c0d5a96 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 10 Oct 2025 13:54:47 -0400 Subject: [PATCH 061/106] Dock: Context menu improvements. --- Modules/Dock/Dock.qml | 6 ++++-- Modules/Dock/DockMenu.qml | 23 +++++++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index d99fdd5e..b23c90bc 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -482,7 +482,9 @@ Variants { anyAppHovered = true const appName = appButton.appTitle || appButton.appId || "Unknown" const tooltipText = appName.length > 40 ? appName.substring(0, 37) + "..." : appName - TooltipService.show(Screen, appButton, tooltipText, "top") + if (!contextMenu.visible) { + TooltipService.show(Screen, appButton, tooltipText, "top") + } if (autoHide) { showTimer.stop() hideTimer.stop() @@ -508,7 +510,7 @@ Variants { // Close any other existing context menu first root.closeAllContextMenus() // Hide tooltip when showing context menu - TooltipService.hide() + TooltipService.hideImmediately() contextMenu.show(appButton, modelData.toplevel || modelData) return } diff --git a/Modules/Dock/DockMenu.qml b/Modules/Dock/DockMenu.qml index 73c3dcf4..255e3b9d 100644 --- a/Modules/Dock/DockMenu.qml +++ b/Modules/Dock/DockMenu.qml @@ -16,6 +16,7 @@ PopupWindow { property real scaling: 1.0 property bool hovered: menuMouseArea.containsMouse property var onAppClosed: null // Callback function for when an app is closed + property bool canAutoClose: false // Track which menu item is hovered property int hoveredItem: -1 // -1: none, otherwise the index of the item in `items` @@ -24,7 +25,7 @@ PopupWindow { signal requestClose - implicitWidth: 160 * scaling + implicitWidth: Math.max(160 * scaling, contextMenuColumn.implicitWidth) implicitHeight: contextMenuColumn.implicitHeight + (Style.marginM * scaling * 2) color: Color.transparent visible: false @@ -128,6 +129,8 @@ PopupWindow { toplevel = toplevelData initItems() visible = true + canAutoClose = false + gracePeriodTimer.restart() } function hide() { @@ -179,6 +182,19 @@ PopupWindow { root.requestClose() } + // Short delay to ignore spurious events + Timer { + id: gracePeriodTimer + interval: 1500 + repeat: false + onTriggered: { + root.canAutoClose = true + if (!menuMouseArea.containsMouse) { + closeTimer.start() + } + } + } + Timer { id: closeTimer interval: 500 @@ -209,7 +225,10 @@ PopupWindow { onExited: { root.hoveredItem = -1 - closeTimer.start() + if (root.canAutoClose) { + // Only close if grace period has passed + closeTimer.start() + } } onPositionChanged: mouse => { From f0c44734bc5e7376fcac8ba2e85b3a417bead082 Mon Sep 17 00:00:00 2001 From: DuckySoLucky Date: Fri, 10 Oct 2025 20:07:28 +0200 Subject: [PATCH 062/106] feat(Application Launcher): add option to ignore initial mouse position --- Assets/Translations/en.json | 4 +++ Assets/settings-default.json | 5 +-- Commons/Settings.qml | 3 +- Modules/Launcher/Launcher.qml | 52 +++++++++++++++++++++++++-- Modules/Settings/Tabs/LauncherTab.qml | 7 ++++ 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index c4ff50ae..d3a3b630 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -354,6 +354,10 @@ "label": "Use App2Unit to launch applications", "description": "Uses an alternative launch method to better manage app processes and prevent issues." }, + "ignore-initial-mouse": { + "label": "Ignore initial mouse position", + "description": "When enabled, the launcher always defaults to the first item, ignoring where your mouse cursor is positioned. Selection only changes when you move the mouse." + }, "terminal-command": { "label": "Terminal command", "description": "Command to launch a terminal. E.g., 'kitty -e' or 'gnome-terminal --'." diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 9b63d3ae..1df70c5a 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -1,5 +1,5 @@ { - "settingsVersion": 15, + "settingsVersion": 16, "bar": { "position": "top", "backgroundOpacity": 1, @@ -104,7 +104,8 @@ "pinnedExecs": [], "useApp2Unit": false, "sortByMostUsed": true, - "terminalCommand": "xterm -e" + "terminalCommand": "xterm -e", + "ignoreInitialMousePosition": false }, "controlCenter": { "position": "close_to_bar_button", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index d8e8f4bf..355c42e3 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -125,7 +125,7 @@ Singleton { JsonAdapter { id: adapter - property int settingsVersion: 15 + property int settingsVersion: 16 // bar property JsonObject bar: JsonObject { @@ -233,6 +233,7 @@ Singleton { property bool useApp2Unit: false property bool sortByMostUsed: true property string terminalCommand: "xterm -e" + property bool ignoreInitialMousePosition: true } // control center diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index 25bafe37..d31762ea 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -35,6 +35,7 @@ NPanel { property var plugins: [] property var activePlugin: null property bool resultsReady: false + property bool ignoreMouseHover: false readonly property int badgeSize: Math.round(Style.baseWidgetSize * 1.6 * scaling) readonly property int entryHeight: Math.round(badgeSize + Style.marginM * 2 * scaling) @@ -94,6 +95,7 @@ NPanel { // Lifecycle onOpened: { resultsReady = false + ignoreMouseHover = Settings.data.appLauncher.ignoreInitialMousePosition // Use setting value // Notify plugins for (let plugin of plugins) { @@ -110,6 +112,7 @@ NPanel { onClosed: { // Reset search text searchText = "" + ignoreMouseHover = Settings.data.appLauncher.ignoreInitialMousePosition // Use setting value // Notify plugins for (let plugin of plugins) { @@ -154,6 +157,49 @@ NPanel { color: Color.transparent opacity: resultsReady ? 1.0 : 0.0 + // Global MouseArea to detect mouse movement + MouseArea { + id: mouseMovementDetector + anchors.fill: parent + z: -999 + hoverEnabled: true + propagateComposedEvents: true + acceptedButtons: Qt.NoButton + + property real lastX: 0 + property real lastY: 0 + property bool initialized: false + + onPositionChanged: mouse => { + // Store initial position + if (!initialized) { + lastX = mouse.x + lastY = mouse.y + initialized = true + return + } + + // Check if mouse actually moved + const deltaX = Math.abs(mouse.x - lastX) + const deltaY = Math.abs(mouse.y - lastY) + if (deltaX > 1 || deltaY > 1) { + root.ignoreMouseHover = false + lastX = mouse.x + lastY = mouse.y + } + } + + // Reset when launcher opens + Connections { + target: root + function onOpenedChanged() { + if (root.opened) { + mouseMovementDetector.initialized = false + } + } + } + } + Behavior on opacity { NumberAnimation { duration: Style.animationFast @@ -321,7 +367,7 @@ NPanel { delegate: Rectangle { id: entry - property bool isSelected: mouseArea.containsMouse || (index === selectedIndex) + property bool isSelected: (!root.ignoreMouseHover && mouseArea.containsMouse) || (index === selectedIndex) // Accessor for app id property string appId: (modelData && modelData.appId) ? String(modelData.appId) : "" @@ -523,7 +569,9 @@ NPanel { hoverEnabled: true cursorShape: Qt.PointingHandCursor onEntered: { - selectedIndex = index + if (!root.ignoreMouseHover) { + selectedIndex = index + } } onClicked: mouse => { if (mouse.button === Qt.LeftButton) { diff --git a/Modules/Settings/Tabs/LauncherTab.qml b/Modules/Settings/Tabs/LauncherTab.qml index a01c13fb..1149c811 100644 --- a/Modules/Settings/Tabs/LauncherTab.qml +++ b/Modules/Settings/Tabs/LauncherTab.qml @@ -99,6 +99,13 @@ ColumnLayout { onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked } + NToggle { + label: I18n.tr("settings.launcher.settings.ignore-initial-mouse.label") + description: I18n.tr("settings.launcher.settings.ignore-initial-mouse.description") + checked: Settings.data.appLauncher.ignoreInitialMousePosition + onToggled: checked => Settings.data.appLauncher.ignoreInitialMousePosition = checked + } + NTextInput { label: I18n.tr("settings.launcher.settings.terminal-command.label") description: I18n.tr("settings.launcher.settings.terminal-command.description") From b22c2e7d4ddd951c55a353e873e51e1e4128483c Mon Sep 17 00:00:00 2001 From: DuckySoLucky Date: Fri, 10 Oct 2025 21:34:37 +0200 Subject: [PATCH 063/106] refactor: remove setting --- Assets/Translations/en.json | 4 ---- Assets/settings-default.json | 3 +-- Commons/Settings.qml | 1 - Modules/Launcher/Launcher.qml | 4 ++-- Modules/Settings/Tabs/LauncherTab.qml | 7 ------- 5 files changed, 3 insertions(+), 16 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index d3a3b630..c4ff50ae 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -354,10 +354,6 @@ "label": "Use App2Unit to launch applications", "description": "Uses an alternative launch method to better manage app processes and prevent issues." }, - "ignore-initial-mouse": { - "label": "Ignore initial mouse position", - "description": "When enabled, the launcher always defaults to the first item, ignoring where your mouse cursor is positioned. Selection only changes when you move the mouse." - }, "terminal-command": { "label": "Terminal command", "description": "Command to launch a terminal. E.g., 'kitty -e' or 'gnome-terminal --'." diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 1df70c5a..29ebba85 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -104,8 +104,7 @@ "pinnedExecs": [], "useApp2Unit": false, "sortByMostUsed": true, - "terminalCommand": "xterm -e", - "ignoreInitialMousePosition": false + "terminalCommand": "xterm -e" }, "controlCenter": { "position": "close_to_bar_button", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 355c42e3..7e62b2cf 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -233,7 +233,6 @@ Singleton { property bool useApp2Unit: false property bool sortByMostUsed: true property string terminalCommand: "xterm -e" - property bool ignoreInitialMousePosition: true } // control center diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index d31762ea..f4a7fd4d 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -95,7 +95,7 @@ NPanel { // Lifecycle onOpened: { resultsReady = false - ignoreMouseHover = Settings.data.appLauncher.ignoreInitialMousePosition // Use setting value + ignoreMouseHover = true // Notify plugins for (let plugin of plugins) { @@ -112,7 +112,7 @@ NPanel { onClosed: { // Reset search text searchText = "" - ignoreMouseHover = Settings.data.appLauncher.ignoreInitialMousePosition // Use setting value + ignoreMouseHover = true // Notify plugins for (let plugin of plugins) { diff --git a/Modules/Settings/Tabs/LauncherTab.qml b/Modules/Settings/Tabs/LauncherTab.qml index 1149c811..a01c13fb 100644 --- a/Modules/Settings/Tabs/LauncherTab.qml +++ b/Modules/Settings/Tabs/LauncherTab.qml @@ -99,13 +99,6 @@ ColumnLayout { onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked } - NToggle { - label: I18n.tr("settings.launcher.settings.ignore-initial-mouse.label") - description: I18n.tr("settings.launcher.settings.ignore-initial-mouse.description") - checked: Settings.data.appLauncher.ignoreInitialMousePosition - onToggled: checked => Settings.data.appLauncher.ignoreInitialMousePosition = checked - } - NTextInput { label: I18n.tr("settings.launcher.settings.terminal-command.label") description: I18n.tr("settings.launcher.settings.terminal-command.description") From f0d14f3c61e87815bdaa7454951c1ffe105f9b33 Mon Sep 17 00:00:00 2001 From: DuckySoLucky Date: Fri, 10 Oct 2025 21:38:36 +0200 Subject: [PATCH 064/106] fix: forgot to revert settings change --- Assets/settings-default.json | 2 +- Commons/Settings.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 29ebba85..9b63d3ae 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -1,5 +1,5 @@ { - "settingsVersion": 16, + "settingsVersion": 15, "bar": { "position": "top", "backgroundOpacity": 1, diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 7e62b2cf..d8e8f4bf 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -125,7 +125,7 @@ Singleton { JsonAdapter { id: adapter - property int settingsVersion: 16 + property int settingsVersion: 15 // bar property JsonObject bar: JsonObject { From f77bbaa5e34129f50a36c1b981b6e4cc387a1af9 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 10 Oct 2025 15:47:14 -0400 Subject: [PATCH 065/106] More math rounding to avoid any blur. --- Modules/Bar/Extras/BarWidgetLoader.qml | 2 +- Modules/Bar/Extras/TrayMenu.qml | 2 +- Modules/Bar/Widgets/Taskbar.qml | 4 ++-- Modules/Bar/Widgets/Tray.qml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/Bar/Extras/BarWidgetLoader.qml b/Modules/Bar/Extras/BarWidgetLoader.qml index 69b08623..d4194d1e 100644 --- a/Modules/Bar/Extras/BarWidgetLoader.qml +++ b/Modules/Bar/Extras/BarWidgetLoader.qml @@ -27,7 +27,7 @@ Item { } function getImplicitSize(item, prop) { - return (item && item.visible) ? item[prop] : 0 + return (item && item.visible) ? Math.round(item[prop]) : 0 } Loader { diff --git a/Modules/Bar/Extras/TrayMenu.qml b/Modules/Bar/Extras/TrayMenu.qml index ec16a860..9b6088b9 100644 --- a/Modules/Bar/Extras/TrayMenu.qml +++ b/Modules/Bar/Extras/TrayMenu.qml @@ -28,7 +28,7 @@ PopupWindow { readonly property int menuWidth: 180 - implicitWidth: menuWidth * scaling + implicitWidth: Math.round(menuWidth * scaling) // Use the content height of the Flickable for implicit height implicitHeight: Math.min(screen ? screen.height * 0.9 : Screen.height * 0.9, flickable.contentHeight + (Style.marginS * 2 * scaling)) diff --git a/Modules/Bar/Widgets/Taskbar.qml b/Modules/Bar/Widgets/Taskbar.qml index a551533c..e9035002 100644 --- a/Modules/Bar/Widgets/Taskbar.qml +++ b/Modules/Bar/Widgets/Taskbar.qml @@ -36,8 +36,8 @@ Rectangle { } // Always visible when there are toplevels - implicitWidth: isVerticalBar ? Math.round(Style.capsuleHeight * scaling) : taskbarLayout.implicitWidth + Style.marginM * scaling * 2 - implicitHeight: isVerticalBar ? taskbarLayout.implicitHeight + Style.marginM * scaling * 2 : Math.round(Style.capsuleHeight * scaling) + implicitWidth: isVerticalBar ? Math.round(Style.capsuleHeight * scaling) : Math.round(taskbarLayout.implicitWidth + Style.marginM * scaling * 2) + implicitHeight: isVerticalBar ? Math.round(taskbarLayout.implicitHeight + Style.marginM * scaling * 2) : Math.round(Style.capsuleHeight * scaling) radius: Math.round(Style.radiusM * scaling) color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index e24da899..0365cc2e 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -145,8 +145,8 @@ Rectangle { } visible: filteredItems.length > 0 - implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : (trayFlow.implicitWidth + Style.marginM * 2 * scaling) - implicitHeight: isVertical ? (trayFlow.implicitHeight + Style.marginM * 2 * scaling) : Math.round(Style.capsuleHeight * scaling) + implicitWidth: isVertical ? Math.round(Style.capsuleHeight * scaling) : Math.round(trayFlow.implicitWidth + Style.marginM * 2 * scaling) + implicitHeight: isVertical ? Math.round(trayFlow.implicitHeight + Style.marginM * 2 * scaling) : Math.round(Style.capsuleHeight * scaling) radius: Math.round(Style.radiusM * scaling) color: Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent From bb68222eea32d865a8865398fb9b67fc21486ad3 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 10 Oct 2025 15:52:09 -0400 Subject: [PATCH 066/106] autofmt --- Modules/Launcher/Launcher.qml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index f4a7fd4d..f6ec7ae9 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -171,23 +171,23 @@ NPanel { property bool initialized: false onPositionChanged: mouse => { - // Store initial position - if (!initialized) { - lastX = mouse.x - lastY = mouse.y - initialized = true - return - } + // Store initial position + if (!initialized) { + lastX = mouse.x + lastY = mouse.y + initialized = true + return + } - // Check if mouse actually moved - const deltaX = Math.abs(mouse.x - lastX) - const deltaY = Math.abs(mouse.y - lastY) - if (deltaX > 1 || deltaY > 1) { - root.ignoreMouseHover = false - lastX = mouse.x - lastY = mouse.y - } - } + // Check if mouse actually moved + const deltaX = Math.abs(mouse.x - lastX) + const deltaY = Math.abs(mouse.y - lastY) + if (deltaX > 1 || deltaY > 1) { + root.ignoreMouseHover = false + lastX = mouse.x + lastY = mouse.y + } + } // Reset when launcher opens Connections { From b3b5ec7f148724f819546c58df4b9e084e2b1c35 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 10 Oct 2025 21:26:40 -0400 Subject: [PATCH 067/106] v2.17.0 --- Services/UpdateService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index d565fd2c..a8bd4874 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -8,7 +8,7 @@ Singleton { id: root // Public properties - property string baseVersion: "2.16.1" + property string baseVersion: "2.17.0" property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From bffbd0730de06977f5cdded459bc3a12c7e74c69 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 10 Oct 2025 21:29:04 -0400 Subject: [PATCH 068/106] v2.17.1 --- Services/UpdateService.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index a8bd4874..28b1e151 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -8,8 +8,8 @@ Singleton { id: root // Public properties - property string baseVersion: "2.17.0" - property bool isDevelopment: true + property string baseVersion: "2.17.1" + property bool isDevelopment: false property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From 32c929c943ad338c463f14d2b71ae7ca0c7563a6 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 10 Oct 2025 21:29:26 -0400 Subject: [PATCH 069/106] dev --- Services/UpdateService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index 28b1e151..cdefd487 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -9,7 +9,7 @@ Singleton { // Public properties property string baseVersion: "2.17.1" - property bool isDevelopment: false + property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From 586e28919cd3dcde036470265e660b97ed31d6ff Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Fri, 10 Oct 2025 22:14:57 -0400 Subject: [PATCH 070/106] fix: Alignment issue on current day vs other days --- Modules/Bar/Calendar/CalendarPanel.qml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index 21246815..0d1faade 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -87,15 +87,17 @@ NPanel { // Today day number NText { - visible: content.isCurrentMonth + opacity: content.isCurrentMonth ? 1.0 : 0.0 + Layout.preferredWidth: content.isCurrentMonth ? implicitWidth : 0 Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft text: Time.date.getDate() pointSize: Style.fontSizeXXXL * 1.5 * scaling font.weight: Style.fontWeightBold color: Color.mOnPrimary - } - Item { - visible: !content.isCurrentMonth + + Behavior on opacity { + NumberAnimation { duration: Style.animationFast } + } } // Month, year, location From e86f4e56cb72cfb37ab545c72acc013ea1d7369a Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 04:16:12 +0200 Subject: [PATCH 071/106] ControlCenter: revert to old layout --- Commons/Settings.qml | 4 + .../ControlCenter/Cards/PowerProfilesCard.qml | 66 +++++++++ Modules/ControlCenter/Cards/ProfileCard.qml | 113 +++++++++++++++ .../ControlCenter/Cards/SystemMonitorCard.qml | 27 ++-- Modules/ControlCenter/Cards/UtilitiesCard.qml | 66 +++++++++ Modules/ControlCenter/Cards/WeatherCard.qml | 130 ++++++++++++++++++ Modules/ControlCenter/ControlCenterPanel.qml | 80 ++++++----- Modules/Settings/SettingsPanel.qml | 14 +- Services/UpdateService.qml | 4 +- 9 files changed, 449 insertions(+), 55 deletions(-) create mode 100644 Modules/ControlCenter/Cards/PowerProfilesCard.qml create mode 100644 Modules/ControlCenter/Cards/ProfileCard.qml create mode 100644 Modules/ControlCenter/Cards/UtilitiesCard.qml create mode 100644 Modules/ControlCenter/Cards/WeatherCard.qml diff --git a/Commons/Settings.qml b/Commons/Settings.qml index d8e8f4bf..8375d67a 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -159,6 +159,10 @@ Singleton { "id": "Tray" }, { "id": "NotificationHistory" + }, { + "id": "WiFi" + }, { + "id": "Bluetooth" }, { "id": "Battery" }, { diff --git a/Modules/ControlCenter/Cards/PowerProfilesCard.qml b/Modules/ControlCenter/Cards/PowerProfilesCard.qml new file mode 100644 index 00000000..d93b26ba --- /dev/null +++ b/Modules/ControlCenter/Cards/PowerProfilesCard.qml @@ -0,0 +1,66 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.UPower +import qs.Commons +import qs.Services +import qs.Widgets + +// Power Profiles: performance, balanced, eco +NBox { + + property real spacing: 0 + + // Centralized service + readonly property bool hasPP: PowerProfileService.available + + RowLayout { + id: powerRow + anchors.fill: parent + anchors.margins: Style.marginS * scaling + spacing: spacing + Item { + Layout.fillWidth: true + } + // Performance + NIconButton { + icon: PowerProfileService.getIcon(PowerProfile.Performance) + tooltipText: I18n.tr("tooltips.set-power-profile", { + "profile": PowerProfileService.getName(PowerProfile.Performance) + }) + enabled: hasPP + opacity: enabled ? Style.opacityFull : Style.opacityMedium + colorBg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mPrimary : Color.mSurfaceVariant + colorFg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mOnPrimary : Color.mPrimary + onClicked: PowerProfileService.setProfile(PowerProfile.Performance) + } + // Balanced + NIconButton { + icon: PowerProfileService.getIcon(PowerProfile.Balanced) + tooltipText: I18n.tr("tooltips.set-power-profile", { + "profile": PowerProfileService.getName(PowerProfile.Balanced) + }) + enabled: hasPP + opacity: enabled ? Style.opacityFull : Style.opacityMedium + colorBg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mPrimary : Color.mSurfaceVariant + colorFg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnPrimary : Color.mPrimary + onClicked: PowerProfileService.setProfile(PowerProfile.Balanced) + } + // Eco + NIconButton { + icon: PowerProfileService.getIcon(PowerProfile.PowerSaver) + tooltipText: I18n.tr("tooltips.set-power-profile", { + "profile": PowerProfileService.getName(PowerProfile.PowerSaver) + }) + enabled: hasPP + opacity: enabled ? Style.opacityFull : Style.opacityMedium + colorBg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mPrimary : Color.mSurfaceVariant + colorFg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mOnPrimary : Color.mPrimary + onClicked: PowerProfileService.setProfile(PowerProfile.PowerSaver) + } + Item { + Layout.fillWidth: true + } + } +} diff --git a/Modules/ControlCenter/Cards/ProfileCard.qml b/Modules/ControlCenter/Cards/ProfileCard.qml new file mode 100644 index 00000000..ec9b4527 --- /dev/null +++ b/Modules/ControlCenter/Cards/ProfileCard.qml @@ -0,0 +1,113 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import qs.Modules.Settings +import qs.Modules.ControlCenter +import qs.Commons +import qs.Services +import qs.Widgets + +// Header card with avatar, user and quick actions +NBox { + id: root + + property string uptimeText: "--" + + RowLayout { + id: content + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM * scaling + spacing: Style.marginM * scaling + + NImageCircled { + width: Style.baseWidgetSize * 1.25 * scaling + height: Style.baseWidgetSize * 1.25 * scaling + imagePath: Settings.data.general.avatarImage + fallbackIcon: "person" + borderColor: Color.mPrimary + borderWidth: Math.max(1, Style.borderM * scaling) + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXXS * scaling + NText { + text: Quickshell.env("USER") || "user" + font.weight: Style.fontWeightBold + font.capitalization: Font.Capitalize + } + NText { + text: I18n.tr("system.uptime", { + "uptime": uptimeText + }) + pointSize: Style.fontSizeS * scaling + color: Color.mOnSurfaceVariant + } + } + + RowLayout { + spacing: Style.marginS * scaling + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + Item { + Layout.fillWidth: true + } + NIconButton { + icon: "settings" + tooltipText: I18n.tr("tooltips.open-settings") + onClicked: { + settingsPanel.requestedTab = SettingsPanel.Tab.General + settingsPanel.open() + } + } + + NIconButton { + icon: "power" + tooltipText: I18n.tr("tooltips.session-menu") + onClicked: { + sessionMenuPanel.open() + controlCenterPanel.close() + } + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("tooltips.close") + onClicked: { + controlCenterPanel.close() + } + } + } + } + + // ---------------------------------- + // Uptime + Timer { + interval: 60000 + repeat: true + running: true + onTriggered: uptimeProcess.running = true + } + + Process { + id: uptimeProcess + command: ["cat", "/proc/uptime"] + running: true + + stdout: StdioCollector { + onStreamFinished: { + var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0]) + uptimeText = Time.formatVagueHumanReadableDuration(uptimeSeconds) + uptimeProcess.running = false + } + } + } + + function updateSystemInfo() { + uptimeProcess.running = true + } +} diff --git a/Modules/ControlCenter/Cards/SystemMonitorCard.qml b/Modules/ControlCenter/Cards/SystemMonitorCard.qml index b0572f8a..67ca2c68 100644 --- a/Modules/ControlCenter/Cards/SystemMonitorCard.qml +++ b/Modules/ControlCenter/Cards/SystemMonitorCard.qml @@ -9,10 +9,15 @@ import qs.Widgets NBox { id: root - RowLayout { + ColumnLayout { id: content - anchors.fill: parent - anchors.margins: Style.marginXS * scaling + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.leftMargin: Style.marginS * scaling + anchors.rightMargin: Style.marginS * scaling + anchors.topMargin: Style.marginXS * scaling + anchors.bottomMargin: Style.marginM * scaling spacing: Style.marginS * scaling NCircleStat { @@ -20,8 +25,8 @@ NBox { icon: "cpu-usage" flat: true contentScale: 0.8 - Layout.fillWidth: true - Layout.fillHeight: true + width: 72 * scaling + height: 68 * scaling } NCircleStat { value: SystemStatService.cpuTemp @@ -29,24 +34,24 @@ NBox { icon: "cpu-temperature" flat: true contentScale: 0.8 - Layout.fillWidth: true - Layout.fillHeight: true + width: 72 * scaling + height: 68 * scaling } NCircleStat { value: SystemStatService.memPercent icon: "memory" flat: true contentScale: 0.8 - Layout.fillWidth: true - Layout.fillHeight: true + width: 72 * scaling + height: 68 * scaling } NCircleStat { value: SystemStatService.diskPercent icon: "storage" flat: true contentScale: 0.8 - Layout.fillWidth: true - Layout.fillHeight: true + width: 72 * scaling + height: 68 * scaling } } } diff --git a/Modules/ControlCenter/Cards/UtilitiesCard.qml b/Modules/ControlCenter/Cards/UtilitiesCard.qml new file mode 100644 index 00000000..decd9659 --- /dev/null +++ b/Modules/ControlCenter/Cards/UtilitiesCard.qml @@ -0,0 +1,66 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Modules.Settings +import qs.Services +import qs.Widgets + +// Utilities: record & wallpaper +NBox { + + property real spacing: 0 + + RowLayout { + id: utilRow + anchors.fill: parent + anchors.margins: Style.marginS * scaling + spacing: spacing + Item { + Layout.fillWidth: true + } + // Screen Recorder + NIconButton { + icon: "camera-video" + enabled: ScreenRecorderService.isAvailable + tooltipText: ScreenRecorderService.isAvailable ? (ScreenRecorderService.isRecording ? I18n.tr("tooltips.stop-screen-recording") : I18n.tr("tooltips.start-screen-recording")) : I18n.tr("tooltips.screen-recorder-not-installed") + colorBg: ScreenRecorderService.isRecording ? Color.mPrimary : Color.mSurfaceVariant + colorFg: ScreenRecorderService.isRecording ? Color.mOnPrimary : Color.mPrimary + onClicked: { + if (!ScreenRecorderService.isAvailable) + return + ScreenRecorderService.toggleRecording() + // If we were not recording and we just initiated a start, close the panel + if (!ScreenRecorderService.isRecording) { + var panel = PanelService.getPanel("controlCenterPanel") + panel?.close() + } + } + } + + // Idle Inhibitor + NIconButton { + icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off" + tooltipText: IdleInhibitorService.isInhibited ? I18n.tr("tooltips.disable-keep-awake") : I18n.tr("tooltips.enable-keep-awake") + colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : Color.mSurfaceVariant + colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mPrimary + onClicked: { + IdleInhibitorService.manualToggle() + } + } + + // Wallpaper + NIconButton { + visible: Settings.data.wallpaper.enabled + icon: "wallpaper-selector" + tooltipText: I18n.tr("tooltips.wallpaper-selector") + onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this) + onRightClicked: WallpaperService.setRandomWallpaper() + } + + Item { + Layout.fillWidth: true + } + } +} diff --git a/Modules/ControlCenter/Cards/WeatherCard.qml b/Modules/ControlCenter/Cards/WeatherCard.qml new file mode 100644 index 00000000..8e6257a1 --- /dev/null +++ b/Modules/ControlCenter/Cards/WeatherCard.qml @@ -0,0 +1,130 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services +import qs.Widgets + +// Weather overview card (placeholder data) +NBox { + id: root + + readonly property bool weatherReady: (LocationService.data.weather !== null) + + ColumnLayout { + id: content + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginM * scaling + spacing: Style.marginM * scaling + clip: true + + RowLayout { + spacing: Style.marginS * scaling + NIcon { + Layout.alignment: Qt.AlignVCenter + icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : "" + pointSize: Style.fontSizeXXXL * 1.75 * scaling + color: Color.mPrimary + } + + ColumnLayout { + spacing: Style.marginXXS * scaling + NText { + text: { + // Ensure the name is not too long if one had to specify the country + const chunks = Settings.data.location.name.split(",") + return chunks[0] + } + pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + } + + RowLayout { + NText { + visible: weatherReady + text: { + if (!weatherReady) { + return "" + } + var temp = LocationService.data.weather.current_weather.temperature + var suffix = "C" + if (Settings.data.location.useFahrenheit) { + temp = LocationService.celsiusToFahrenheit(temp) + var suffix = "F" + } + temp = Math.round(temp) + return `${temp}°${suffix}` + } + pointSize: Style.fontSizeXL * scaling + font.weight: Style.fontWeightBold + } + + NText { + text: weatherReady ? `(${LocationService.data.weather.timezone_abbreviation})` : "" + pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + visible: LocationService.data.weather + } + } + } + } + + NDivider { + visible: weatherReady + Layout.fillWidth: true + } + + RowLayout { + visible: weatherReady + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + spacing: Style.marginL * scaling + Repeater { + model: weatherReady ? LocationService.data.weather.daily.time : [] + delegate: ColumnLayout { + Layout.alignment: Qt.AlignHCenter + spacing: Style.marginL * scaling + NText { + text: { + var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")) + return Qt.locale().toString(weatherDate, "ddd") + } + color: Color.mOnSurface + Layout.alignment: Qt.AlignHCenter + } + NIcon { + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index]) + pointSize: Style.fontSizeXXL * 1.6 * scaling + color: Color.mPrimary + } + NText { + Layout.alignment: Qt.AlignHCenter + text: { + var max = LocationService.data.weather.daily.temperature_2m_max[index] + var min = LocationService.data.weather.daily.temperature_2m_min[index] + if (Settings.data.location.useFahrenheit) { + max = LocationService.celsiusToFahrenheit(max) + min = LocationService.celsiusToFahrenheit(min) + } + max = Math.round(max) + min = Math.round(min) + return `${max}°/${min}°` + } + pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + } + } + } + } + + RowLayout { + visible: !weatherReady + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + NBusyIndicator {} + } + } +} diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 2d5ba9bf..aa9a8b47 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -10,29 +10,10 @@ import qs.Widgets NPanel { id: root - preferredWidth: 400 - preferredHeight: topHeight + midHeight + bottomHeight + audioHeight + Math.round(Style.marginL * 5) + preferredWidth: 460 + preferredHeight: 734 panelKeyboardFocus: true - readonly property int topHeight: { - const columns = (Settings.data.controlCenter.quickSettingsStyle === "compact") ? 4 : 3 - const rowsCount = Math.ceil(Settings.data.controlCenter.widgets.quickSettings.length / columns) - - var buttonHeight - if (Settings.data.controlCenter.quickSettingsStyle === "classic") { - buttonHeight = Style.baseWidgetSize - } else if (Settings.data.controlCenter.quickSettingsStyle === "compact") { - buttonHeight = Style.baseWidgetSize * 0.8 // Smaller for compact - } else { - buttonHeight = 56 - } - - return (rowsCount * buttonHeight) + 120 - } - readonly property int midHeight: 220 - readonly property int bottomHeight: 80 - readonly property int audioHeight: 120 - // Positioning readonly property string controlCenterPosition: Settings.data.controlCenter.position panelAnchorHorizontalCenter: controlCenterPosition !== "close_to_bar_button" && controlCenterPosition.endsWith("_center") @@ -50,33 +31,60 @@ NPanel { // Layout content ColumnLayout { id: layout - anchors.fill: parent - anchors.margins: content.cardSpacing + x: content.cardSpacing + y: content.cardSpacing + width: parent.width - (2 * content.cardSpacing) spacing: content.cardSpacing - // Top Card: profile + utilities - TopCard { - id: topCard + // Cards (consistent inter-card spacing via ColumnLayout spacing) + ProfileCard { Layout.fillWidth: true - Layout.preferredHeight: topHeight * scaling + Layout.preferredHeight: Math.max(64 * scaling) } - // Audio controls card - AudioCard { + WeatherCard { Layout.fillWidth: true - Layout.preferredHeight: audioHeight * scaling + Layout.preferredHeight: Math.max(220 * scaling) } - // Media card - MediaCard { + // Middle section: media + stats column + RowLayout { Layout.fillWidth: true - Layout.preferredHeight: midHeight * scaling + Layout.preferredHeight: Math.max(310 * scaling) + spacing: content.cardSpacing + + // Media card + MediaCard { + Layout.fillWidth: true + Layout.fillHeight: true + } + + // System monitors combined in one card + SystemMonitorCard { + Layout.preferredWidth: Style.baseWidgetSize * 2.625 * scaling + Layout.fillHeight: true + } } - // System monitors combined in one card - SystemMonitorCard { + // Bottom actions (two grouped rows of round buttons) + RowLayout { Layout.fillWidth: true - Layout.preferredHeight: bottomHeight * scaling + Layout.preferredHeight: Math.max(60 * scaling) + spacing: content.cardSpacing + + // Power Profiles switcher + PowerProfilesCard { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: content.cardSpacing + } + + // Utilities buttons + UtilitiesCard { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: content.cardSpacing + } } } } diff --git a/Modules/Settings/SettingsPanel.qml b/Modules/Settings/SettingsPanel.qml index cb76dff4..28b21800 100644 --- a/Modules/Settings/SettingsPanel.qml +++ b/Modules/Settings/SettingsPanel.qml @@ -129,12 +129,14 @@ NPanel { "label": "settings.bar.title", "icon": "settings-bar", "source": barTab - }, { - "id": SettingsPanel.Tab.ControlCenter, - "label": "settings.control-center.title", - "icon": "settings-bar", - "source": controlCenterTab - }, { + }, + //{ + // "id": SettingsPanel.Tab.ControlCenter, + // "label": "settings.control-center.title", + // "icon": "settings-bar", + // "source": controlCenterTab + //}, + { "id": SettingsPanel.Tab.Dock, "label": "settings.dock.title", "icon": "settings-dock", diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index cdefd487..e1110eb1 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -8,8 +8,8 @@ Singleton { id: root // Public properties - property string baseVersion: "2.17.1" - property bool isDevelopment: true + property string baseVersion: "2.17.2" + property bool isDevelopment: false property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From 02f4fa855780ca1fc30bc17966dac4d98112c47d Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 04:24:48 +0200 Subject: [PATCH 072/106] Set version to dev --- Services/UpdateService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index e1110eb1..b826fc69 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -9,7 +9,7 @@ Singleton { // Public properties property string baseVersion: "2.17.2" - property bool isDevelopment: false + property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From e07e7e2bd14a640cc9404079b3cd007de3e7c424 Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Fri, 10 Oct 2025 23:34:56 -0400 Subject: [PATCH 073/106] fix: Refine header layout and animations --- Modules/Bar/Calendar/CalendarPanel.qml | 220 +++++++++++-------------- 1 file changed, 92 insertions(+), 128 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index 0d1faade..70a2fa74 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -43,14 +43,20 @@ NPanel { ColumnLayout { id: blueColumn - anchors.fill: parent - anchors.margins: Style.marginM * scaling + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.topMargin: Style.marginM * scaling + anchors.leftMargin: Style.marginM * scaling + anchors.bottomMargin: Style.marginM * scaling + anchors.rightMargin: clockItem.width + (Style.marginM * scaling * 2) spacing: 0 // Combined layout for weather icon, date, and weather text RowLayout { Layout.fillWidth: true - Layout.preferredHeight: 60 * scaling + height: 60 * scaling + clip: true spacing: Style.marginS * scaling // Weather icon and temperature @@ -70,14 +76,14 @@ NPanel { text: { if (!weatherReady) return "" - var temp = LocationService.data.weather.current_weather.temperature - var suffix = "C" - if (Settings.data.location.useFahrenheit) { - temp = LocationService.celsiusToFahrenheit(temp) - suffix = "F" - } - temp = Math.round(temp) - return `${temp}°${suffix}` + var temp = LocationService.data.weather.current_weather.temperature + var suffix = "C" + if (Settings.data.location.useFahrenheit) { + temp = LocationService.celsiusToFahrenheit(temp) + suffix = "F" + } + temp = Math.round(temp) + return `${temp}°${suffix}` } pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold @@ -85,24 +91,27 @@ NPanel { } } - // Today day number + // Today day number - with simple, stable animation NText { opacity: content.isCurrentMonth ? 1.0 : 0.0 Layout.preferredWidth: content.isCurrentMonth ? implicitWidth : 0 + elide: Text.ElideNone + clip: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft text: Time.date.getDate() pointSize: Style.fontSizeXXXL * 1.5 * scaling font.weight: Style.fontWeightBold color: Color.mOnPrimary - Behavior on opacity { - NumberAnimation { duration: Style.animationFast } - } + Behavior on opacity { NumberAnimation { duration: Style.animationFast } } + Behavior on Layout.preferredWidth { NumberAnimation { duration: Style.animationFast; easing.type: Easing.InOutQuad } } } // Month, year, location ColumnLayout { - Layout.fillWidth: false + // Give the whole column a fixed width to stabilize the layout + Layout.preferredWidth: 170 * scaling Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft spacing: -Style.marginXS * scaling @@ -115,7 +124,6 @@ NPanel { font.weight: Style.fontWeightBold color: Color.mOnPrimary Layout.alignment: Qt.AlignBaseline - Layout.maximumWidth: 150 * scaling elide: Text.ElideRight } @@ -135,8 +143,8 @@ NPanel { text: { if (!weatherReady) return I18n.tr("calendar.weather.loading") - const chunks = Settings.data.location.name.split(",") - return chunks[0] + const chunks = Settings.data.location.name.split(",") + return chunks[0] } pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightMedium @@ -154,96 +162,86 @@ NPanel { } } - // Spacer between date and clock + // Spacer to push content left Item { Layout.fillWidth: true } + } + } - // Digital clock with circular progress - Item { - width: Style.fontSizeXXXL * 1.9 * scaling - height: Style.fontSizeXXXL * 1.9 * scaling - Layout.alignment: Qt.AlignVCenter + // The Clock, anchored separately for stability + Item { + id: clockItem + anchors.right: parent.right + anchors.rightMargin: Style.marginM * scaling + anchors.verticalCenter: parent.verticalCenter + width: Style.fontSizeXXXL * 1.9 * scaling + height: Style.fontSizeXXXL * 1.9 * scaling - // Seconds circular progress - Canvas { - id: secondsProgress - anchors.fill: parent - - property real progress: Time.date.getSeconds() / 60 - onProgressChanged: requestPaint() - - Connections { - target: Time - function onDateChanged() { - const total = Time.date.getSeconds() * 1000 + Time.date.getMilliseconds() - secondsProgress.progress = total / 60000 - } - } - - onPaint: { - var ctx = getContext("2d") - var centerX = width / 2 - var centerY = height / 2 - var radius = Math.min(width, height) / 2 - 3 * scaling - - ctx.reset() - - // Background circle - ctx.beginPath() - ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI) - ctx.lineWidth = 2.5 * scaling - ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15) - ctx.stroke() - - // Progress arc - ctx.beginPath() - ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI) - ctx.lineWidth = 2.5 * scaling - ctx.strokeStyle = Color.mOnPrimary - ctx.lineCap = "round" - ctx.stroke() - } + Canvas { + id: secondsProgress + anchors.fill: parent + property real progress: Time.date.getSeconds() / 60 + onProgressChanged: requestPaint() + Connections { + target: Time + function onDateChanged() { + const total = Time.date.getSeconds() * 1000 + Time.date.getMilliseconds() + secondsProgress.progress = total / 60000 } + } + onPaint: { + var ctx = getContext("2d") + var centerX = width / 2 + var centerY = height / 2 + var radius = Math.min(width, height) / 2 - 3 * scaling + ctx.reset() + ctx.beginPath() + ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI) + ctx.lineWidth = 2.5 * scaling + ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15) + ctx.stroke() + ctx.beginPath() + ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI) + ctx.lineWidth = 2.5 * scaling + ctx.strokeStyle = Color.mOnPrimary + ctx.lineCap = "round" + ctx.stroke() + } + } - // Digital clock - ColumnLayout { - anchors.centerIn: parent - spacing: -Style.marginXXS * scaling - - NText { - text: { - var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH") - return t.split(" ")[0] - } - pointSize: Style.fontSizeXS * scaling - font.weight: Style.fontWeightBold - color: Color.mOnPrimary - family: Settings.data.ui.fontFixed - Layout.alignment: Qt.AlignHCenter - } - - NText { - text: Qt.formatTime(Time.date, "mm") - pointSize: Style.fontSizeXXS * scaling - font.weight: Style.fontWeightBold - color: Color.mOnPrimary - family: Settings.data.ui.fontFixed - Layout.alignment: Qt.AlignHCenter - } + ColumnLayout { + anchors.centerIn: parent + spacing: -Style.marginXXS * scaling + NText { + text: { + var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH") + return t.split(" ")[0] } + pointSize: Style.fontSizeXS * scaling + font.weight: Style.fontWeightBold + color: Color.mOnPrimary + family: Settings.data.ui.fontFixed + Layout.alignment: Qt.AlignHCenter + } + NText { + text: Qt.formatTime(Time.date, "mm") + pointSize: Style.fontSizeXXS * scaling + font.weight: Style.fontWeightBold + color: Color.mOnPrimary + family: Settings.data.ui.fontFixed + Layout.alignment: Qt.AlignHCenter } } } } - // 6-day forecast (outside blue banner) + // ... (rest of the file is unchanged) ... RowLayout { visible: weatherReady Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter spacing: Style.marginL * scaling - Repeater { model: weatherReady ? Math.min(6, LocationService.data.weather.daily.time.length) : 0 delegate: ColumnLayout { @@ -251,7 +249,6 @@ NPanel { Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter spacing: Style.marginS * scaling - NText { text: { var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")) @@ -262,14 +259,12 @@ NPanel { font.weight: Style.fontWeightMedium Layout.alignment: Qt.AlignHCenter } - NIcon { Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index]) pointSize: Style.fontSizeXXL * 1.5 * scaling color: Color.mPrimary } - NText { Layout.alignment: Qt.AlignHCenter text: { @@ -290,27 +285,19 @@ NPanel { } } } - - // Loading indicator for weather RowLayout { visible: !weatherReady Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter NBusyIndicator {} } - - // Spacer Item {} - - // Navigation and divider RowLayout { Layout.fillWidth: true spacing: Style.marginS * scaling - NDivider { Layout.fillWidth: true } - NIconButton { icon: "chevron-left" onClicked: { @@ -320,7 +307,6 @@ NPanel { content.isCurrentMonth = content.checkIsCurrentMonth() } } - NIconButton { icon: "calendar" onClicked: { @@ -329,7 +315,6 @@ NPanel { content.isCurrentMonth = true } } - NIconButton { icon: "chevron-right" onClicked: { @@ -340,31 +325,24 @@ NPanel { } } } - - // Names of days of the week RowLayout { Layout.fillWidth: true spacing: 0 - Item { visible: Settings.data.location.showWeekNumberInCalendar Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0 } - GridLayout { Layout.fillWidth: true columns: 7 rows: 1 columnSpacing: 0 rowSpacing: 0 - Repeater { model: 7 - Item { Layout.fillWidth: true Layout.preferredHeight: Style.baseWidgetSize * 0.6 * scaling - NText { anchors.centerIn: parent text: { @@ -381,27 +359,20 @@ NPanel { } } } - - // Grid with weeks and days RowLayout { Layout.fillWidth: true Layout.fillHeight: true spacing: 0 - - // Column of week numbers ColumnLayout { visible: Settings.data.location.showWeekNumberInCalendar Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0 Layout.fillHeight: true spacing: 0 - Repeater { model: 6 - Item { Layout.fillWidth: true Layout.fillHeight: true - NText { anchors.centerIn: parent color: Color.mOutline @@ -433,42 +404,35 @@ NPanel { } } } - - // Days Grid MonthGrid { id: grid - Layout.fillWidth: true Layout.fillHeight: true spacing: Style.marginXXS * scaling month: Time.date.getMonth() year: Time.date.getFullYear() locale: Qt.locale() - delegate: Item { Rectangle { width: Style.baseWidgetSize * 0.9 * scaling height: Style.baseWidgetSize * 0.9 * scaling anchors.centerIn: parent radius: Style.radiusM * scaling - color: model.today ? Color.mSecondary : Color.transparent - NText { anchors.centerIn: parent text: model.day color: { if (model.today) return Color.mOnSecondary - if (model.month === grid.month) - return Color.mOnSurface - return Color.mOnSurfaceVariant + if (model.month === grid.month) + return Color.mOnSurface + return Color.mOnSurfaceVariant } opacity: model.month === grid.month ? 1.0 : 0.4 pointSize: Style.fontSizeM * scaling font.weight: model.today ? Style.fontWeightBold : Style.fontWeightMedium } - Behavior on color { ColorAnimation { duration: Style.animationFast From ee799df56d4dca60616a4862215d242fd1225193 Mon Sep 17 00:00:00 2001 From: MrDowntempo Date: Fri, 10 Oct 2025 23:56:17 -0400 Subject: [PATCH 074/106] Just some clean up and restored comments --- Modules/Bar/Calendar/CalendarPanel.qml | 48 +++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index 70a2fa74..c0617865 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -91,7 +91,7 @@ NPanel { } } - // Today day number - with simple, stable animation + // Today day number NText { opacity: content.isCurrentMonth ? 1.0 : 0.0 Layout.preferredWidth: content.isCurrentMonth ? implicitWidth : 0 @@ -110,7 +110,6 @@ NPanel { // Month, year, location ColumnLayout { - // Give the whole column a fixed width to stabilize the layout Layout.preferredWidth: 170 * scaling Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft spacing: -Style.marginXS * scaling @@ -169,7 +168,7 @@ NPanel { } } - // The Clock, anchored separately for stability + // Digital clock with circular progress Item { id: clockItem anchors.right: parent.right @@ -178,6 +177,7 @@ NPanel { width: Style.fontSizeXXXL * 1.9 * scaling height: Style.fontSizeXXXL * 1.9 * scaling + // Seconds circular progress Canvas { id: secondsProgress anchors.fill: parent @@ -196,11 +196,15 @@ NPanel { var centerY = height / 2 var radius = Math.min(width, height) / 2 - 3 * scaling ctx.reset() + + // Background circle ctx.beginPath() ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI) ctx.lineWidth = 2.5 * scaling ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15) ctx.stroke() + + // Progress arc ctx.beginPath() ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI) ctx.lineWidth = 2.5 * scaling @@ -210,6 +214,7 @@ NPanel { } } + // Digital clock ColumnLayout { anchors.centerIn: parent spacing: -Style.marginXXS * scaling @@ -236,12 +241,13 @@ NPanel { } } - // ... (rest of the file is unchanged) ... + // 6-day forecast (outside blue banner) RowLayout { visible: weatherReady Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter spacing: Style.marginL * scaling + Repeater { model: weatherReady ? Math.min(6, LocationService.data.weather.daily.time.length) : 0 delegate: ColumnLayout { @@ -249,6 +255,7 @@ NPanel { Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter spacing: Style.marginS * scaling + NText { text: { var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")) @@ -259,12 +266,14 @@ NPanel { font.weight: Style.fontWeightMedium Layout.alignment: Qt.AlignHCenter } + NIcon { Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index]) pointSize: Style.fontSizeXXL * 1.5 * scaling color: Color.mPrimary } + NText { Layout.alignment: Qt.AlignHCenter text: { @@ -285,19 +294,27 @@ NPanel { } } } + + // Loading indicator for weather RowLayout { visible: !weatherReady Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter NBusyIndicator {} } + + // Spacer Item {} + + // Navigation and divider RowLayout { Layout.fillWidth: true spacing: Style.marginS * scaling + NDivider { Layout.fillWidth: true } + NIconButton { icon: "chevron-left" onClicked: { @@ -307,6 +324,7 @@ NPanel { content.isCurrentMonth = content.checkIsCurrentMonth() } } + NIconButton { icon: "calendar" onClicked: { @@ -315,6 +333,7 @@ NPanel { content.isCurrentMonth = true } } + NIconButton { icon: "chevron-right" onClicked: { @@ -325,24 +344,31 @@ NPanel { } } } + + // Names of days of the week RowLayout { Layout.fillWidth: true spacing: 0 + Item { visible: Settings.data.location.showWeekNumberInCalendar Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0 } + GridLayout { Layout.fillWidth: true columns: 7 rows: 1 columnSpacing: 0 rowSpacing: 0 + Repeater { model: 7 + Item { Layout.fillWidth: true Layout.preferredHeight: Style.baseWidgetSize * 0.6 * scaling + NText { anchors.centerIn: parent text: { @@ -359,20 +385,27 @@ NPanel { } } } + + // Grid with weeks and days RowLayout { Layout.fillWidth: true Layout.fillHeight: true spacing: 0 + + // Column of week numbers ColumnLayout { visible: Settings.data.location.showWeekNumberInCalendar Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0 Layout.fillHeight: true spacing: 0 + Repeater { model: 6 + Item { Layout.fillWidth: true Layout.fillHeight: true + NText { anchors.centerIn: parent color: Color.mOutline @@ -404,21 +437,27 @@ NPanel { } } } + + // Days Grid MonthGrid { id: grid + Layout.fillWidth: true Layout.fillHeight: true spacing: Style.marginXXS * scaling month: Time.date.getMonth() year: Time.date.getFullYear() locale: Qt.locale() + delegate: Item { Rectangle { width: Style.baseWidgetSize * 0.9 * scaling height: Style.baseWidgetSize * 0.9 * scaling anchors.centerIn: parent radius: Style.radiusM * scaling + color: model.today ? Color.mSecondary : Color.transparent + NText { anchors.centerIn: parent text: model.day @@ -433,6 +472,7 @@ NPanel { pointSize: Style.fontSizeM * scaling font.weight: model.today ? Style.fontWeightBold : Style.fontWeightMedium } + Behavior on color { ColorAnimation { duration: Style.animationFast From 0b928b0e1d4760a85babbe6f88392c838a00f0bc Mon Sep 17 00:00:00 2001 From: MrDowntempo Date: Sat, 11 Oct 2025 00:02:50 -0400 Subject: [PATCH 075/106] Deleted some spaces some final cleanup --- Modules/Bar/Calendar/CalendarPanel.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index c0617865..5ea0585e 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -391,7 +391,7 @@ NPanel { Layout.fillWidth: true Layout.fillHeight: true spacing: 0 - + // Column of week numbers ColumnLayout { visible: Settings.data.location.showWeekNumberInCalendar From 6d83a3ebed0ac6c8ec70a2419b21ca9858f1458c Mon Sep 17 00:00:00 2001 From: MrDowntempo Date: Sat, 11 Oct 2025 00:43:06 -0400 Subject: [PATCH 076/106] Indentation fixes Should be all cleaned up and ready to go --- Modules/Bar/Calendar/CalendarPanel.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index 5ea0585e..9f0b0172 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -142,8 +142,8 @@ NPanel { text: { if (!weatherReady) return I18n.tr("calendar.weather.loading") - const chunks = Settings.data.location.name.split(",") - return chunks[0] + const chunks = Settings.data.location.name.split(",") + return chunks[0] } pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightMedium @@ -464,9 +464,9 @@ NPanel { color: { if (model.today) return Color.mOnSecondary - if (model.month === grid.month) - return Color.mOnSurface - return Color.mOnSurfaceVariant + if (model.month === grid.month) + return Color.mOnSurface + return Color.mOnSurfaceVariant } opacity: model.month === grid.month ? 1.0 : 0.4 pointSize: Style.fontSizeM * scaling From 4fb884a5c683f8705b0b980510c44bdb2944b5ea Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Sat, 11 Oct 2025 02:09:51 -0400 Subject: [PATCH 077/106] fix: fine tune vertical centering and ensure room for long month names --- Modules/Bar/Calendar/CalendarPanel.qml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index 9f0b0172..8ae325b2 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -112,6 +112,8 @@ NPanel { ColumnLayout { Layout.preferredWidth: 170 * scaling Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.bottomMargin: Style.marginXXS * scaling + Layout.topMargin: -Style.marginXXS * scaling spacing: -Style.marginXS * scaling RowLayout { @@ -119,7 +121,7 @@ NPanel { NText { text: Qt.locale().monthName(grid.month, Locale.LongFormat).toUpperCase() - pointSize: Style.fontSizeXL * 1.2 * scaling + pointSize: Style.fontSizeXL * 1.1 * scaling font.weight: Style.fontWeightBold color: Color.mOnPrimary Layout.alignment: Qt.AlignBaseline @@ -128,7 +130,7 @@ NPanel { NText { text: ` ${grid.year}` - pointSize: Style.fontSizeL * scaling + pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold color: Qt.alpha(Color.mOnPrimary, 0.7) Layout.alignment: Qt.AlignBaseline @@ -171,11 +173,12 @@ NPanel { // Digital clock with circular progress Item { id: clockItem + Layout.alignment: Qt.AlignVCenter anchors.right: parent.right anchors.rightMargin: Style.marginM * scaling anchors.verticalCenter: parent.verticalCenter - width: Style.fontSizeXXXL * 1.9 * scaling - height: Style.fontSizeXXXL * 1.9 * scaling + height: Math.round((Style.fontSizeXXXL * 1.9 * scaling) / 2) * 2 + width: clockItem.height // Seconds circular progress Canvas { From 357c30617dccf6a6c1220780c9ed115220ff3a0e Mon Sep 17 00:00:00 2001 From: MrDowntempo Date: Sat, 11 Oct 2025 02:38:46 -0400 Subject: [PATCH 078/106] More indentation cleanup --- Modules/Bar/Calendar/CalendarPanel.qml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index 8ae325b2..32bfde04 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -76,14 +76,14 @@ NPanel { text: { if (!weatherReady) return "" - var temp = LocationService.data.weather.current_weather.temperature - var suffix = "C" - if (Settings.data.location.useFahrenheit) { - temp = LocationService.celsiusToFahrenheit(temp) - suffix = "F" - } - temp = Math.round(temp) - return `${temp}°${suffix}` + var temp = LocationService.data.weather.current_weather.temperature + var suffix = "C" + if (Settings.data.location.useFahrenheit) { + temp = LocationService.celsiusToFahrenheit(temp) + suffix = "F" + } + temp = Math.round(temp) + return `${temp}°${suffix}` } pointSize: Style.fontSizeM * scaling font.weight: Style.fontWeightBold From 76770bbb3cd88ce02b08b5b9052d5e6e61c5385f Mon Sep 17 00:00:00 2001 From: MrDowntempo Date: Sat, 11 Oct 2025 02:43:34 -0400 Subject: [PATCH 079/106] Even more cleanup --- Modules/Bar/Calendar/CalendarPanel.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index 32bfde04..f60c7295 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -163,7 +163,7 @@ NPanel { } } - // Spacer to push content left + // Spacer between date and clock Item { Layout.fillWidth: true } @@ -226,12 +226,14 @@ NPanel { var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH") return t.split(" ")[0] } + pointSize: Style.fontSizeXS * scaling font.weight: Style.fontWeightBold color: Color.mOnPrimary family: Settings.data.ui.fontFixed Layout.alignment: Qt.AlignHCenter } + NText { text: Qt.formatTime(Time.date, "mm") pointSize: Style.fontSizeXXS * scaling From c25ae8bec40a446519b7e4e5a230631543204d9f Mon Sep 17 00:00:00 2001 From: MrDowntempo Date: Sat, 11 Oct 2025 02:50:18 -0400 Subject: [PATCH 080/106] More consistent line breaks in source code --- Modules/Bar/Calendar/CalendarPanel.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index f60c7295..64c68738 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -221,6 +221,7 @@ NPanel { ColumnLayout { anchors.centerIn: parent spacing: -Style.marginXXS * scaling + NText { text: { var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH") From eadebacea9fc68b37497179e854b1f6459ac5679 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 15:17:21 +0200 Subject: [PATCH 081/106] DockTab: fix NComboBox Autoformat --- Modules/Bar/Calendar/CalendarPanel.qml | 207 +++++++++++-------------- Modules/Settings/SettingsPanel.qml | 11 +- Modules/Settings/Tabs/DockTab.qml | 40 ++--- 3 files changed, 113 insertions(+), 145 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index 21246815..fc520bf2 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -43,14 +43,20 @@ NPanel { ColumnLayout { id: blueColumn - anchors.fill: parent - anchors.margins: Style.marginM * scaling + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.topMargin: Style.marginM * scaling + anchors.leftMargin: Style.marginM * scaling + anchors.bottomMargin: Style.marginM * scaling + anchors.rightMargin: clockItem.width + (Style.marginM * scaling * 2) spacing: 0 // Combined layout for weather icon, date, and weather text RowLayout { Layout.fillWidth: true - Layout.preferredHeight: 60 * scaling + height: 60 * scaling + clip: true spacing: Style.marginS * scaling // Weather icon and temperature @@ -85,22 +91,36 @@ NPanel { } } - // Today day number + // Today day number - with simple, stable animation NText { - visible: content.isCurrentMonth + opacity: content.isCurrentMonth ? 1.0 : 0.0 + Layout.preferredWidth: content.isCurrentMonth ? implicitWidth : 0 + elide: Text.ElideNone + clip: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft text: Time.date.getDate() pointSize: Style.fontSizeXXXL * 1.5 * scaling font.weight: Style.fontWeightBold color: Color.mOnPrimary - } - Item { - visible: !content.isCurrentMonth + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + Behavior on Layout.preferredWidth { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.InOutQuad + } + } } // Month, year, location ColumnLayout { - Layout.fillWidth: false + // Give the whole column a fixed width to stabilize the layout + Layout.preferredWidth: 170 * scaling Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft spacing: -Style.marginXS * scaling @@ -113,7 +133,6 @@ NPanel { font.weight: Style.fontWeightBold color: Color.mOnPrimary Layout.alignment: Qt.AlignBaseline - Layout.maximumWidth: 150 * scaling elide: Text.ElideRight } @@ -152,96 +171,86 @@ NPanel { } } - // Spacer between date and clock + // Spacer to push content left Item { Layout.fillWidth: true } + } + } - // Digital clock with circular progress - Item { - width: Style.fontSizeXXXL * 1.9 * scaling - height: Style.fontSizeXXXL * 1.9 * scaling - Layout.alignment: Qt.AlignVCenter + // The Clock, anchored separately for stability + Item { + id: clockItem + anchors.right: parent.right + anchors.rightMargin: Style.marginM * scaling + anchors.verticalCenter: parent.verticalCenter + width: Style.fontSizeXXXL * 1.9 * scaling + height: Style.fontSizeXXXL * 1.9 * scaling - // Seconds circular progress - Canvas { - id: secondsProgress - anchors.fill: parent - - property real progress: Time.date.getSeconds() / 60 - onProgressChanged: requestPaint() - - Connections { - target: Time - function onDateChanged() { - const total = Time.date.getSeconds() * 1000 + Time.date.getMilliseconds() - secondsProgress.progress = total / 60000 - } - } - - onPaint: { - var ctx = getContext("2d") - var centerX = width / 2 - var centerY = height / 2 - var radius = Math.min(width, height) / 2 - 3 * scaling - - ctx.reset() - - // Background circle - ctx.beginPath() - ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI) - ctx.lineWidth = 2.5 * scaling - ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15) - ctx.stroke() - - // Progress arc - ctx.beginPath() - ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI) - ctx.lineWidth = 2.5 * scaling - ctx.strokeStyle = Color.mOnPrimary - ctx.lineCap = "round" - ctx.stroke() - } + Canvas { + id: secondsProgress + anchors.fill: parent + property real progress: Time.date.getSeconds() / 60 + onProgressChanged: requestPaint() + Connections { + target: Time + function onDateChanged() { + const total = Time.date.getSeconds() * 1000 + Time.date.getMilliseconds() + secondsProgress.progress = total / 60000 } + } + onPaint: { + var ctx = getContext("2d") + var centerX = width / 2 + var centerY = height / 2 + var radius = Math.min(width, height) / 2 - 3 * scaling + ctx.reset() + ctx.beginPath() + ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI) + ctx.lineWidth = 2.5 * scaling + ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15) + ctx.stroke() + ctx.beginPath() + ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI) + ctx.lineWidth = 2.5 * scaling + ctx.strokeStyle = Color.mOnPrimary + ctx.lineCap = "round" + ctx.stroke() + } + } - // Digital clock - ColumnLayout { - anchors.centerIn: parent - spacing: -Style.marginXXS * scaling - - NText { - text: { - var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH") - return t.split(" ")[0] - } - pointSize: Style.fontSizeXS * scaling - font.weight: Style.fontWeightBold - color: Color.mOnPrimary - family: Settings.data.ui.fontFixed - Layout.alignment: Qt.AlignHCenter - } - - NText { - text: Qt.formatTime(Time.date, "mm") - pointSize: Style.fontSizeXXS * scaling - font.weight: Style.fontWeightBold - color: Color.mOnPrimary - family: Settings.data.ui.fontFixed - Layout.alignment: Qt.AlignHCenter - } + ColumnLayout { + anchors.centerIn: parent + spacing: -Style.marginXXS * scaling + NText { + text: { + var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH") + return t.split(" ")[0] } + pointSize: Style.fontSizeXS * scaling + font.weight: Style.fontWeightBold + color: Color.mOnPrimary + family: Settings.data.ui.fontFixed + Layout.alignment: Qt.AlignHCenter + } + NText { + text: Qt.formatTime(Time.date, "mm") + pointSize: Style.fontSizeXXS * scaling + font.weight: Style.fontWeightBold + color: Color.mOnPrimary + family: Settings.data.ui.fontFixed + Layout.alignment: Qt.AlignHCenter } } } } - // 6-day forecast (outside blue banner) + // ... (rest of the file is unchanged) ... RowLayout { visible: weatherReady Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter spacing: Style.marginL * scaling - Repeater { model: weatherReady ? Math.min(6, LocationService.data.weather.daily.time.length) : 0 delegate: ColumnLayout { @@ -249,7 +258,6 @@ NPanel { Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter spacing: Style.marginS * scaling - NText { text: { var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")) @@ -260,14 +268,12 @@ NPanel { font.weight: Style.fontWeightMedium Layout.alignment: Qt.AlignHCenter } - NIcon { Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index]) pointSize: Style.fontSizeXXL * 1.5 * scaling color: Color.mPrimary } - NText { Layout.alignment: Qt.AlignHCenter text: { @@ -288,27 +294,19 @@ NPanel { } } } - - // Loading indicator for weather RowLayout { visible: !weatherReady Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter NBusyIndicator {} } - - // Spacer Item {} - - // Navigation and divider RowLayout { Layout.fillWidth: true spacing: Style.marginS * scaling - NDivider { Layout.fillWidth: true } - NIconButton { icon: "chevron-left" onClicked: { @@ -318,7 +316,6 @@ NPanel { content.isCurrentMonth = content.checkIsCurrentMonth() } } - NIconButton { icon: "calendar" onClicked: { @@ -327,7 +324,6 @@ NPanel { content.isCurrentMonth = true } } - NIconButton { icon: "chevron-right" onClicked: { @@ -338,31 +334,24 @@ NPanel { } } } - - // Names of days of the week RowLayout { Layout.fillWidth: true spacing: 0 - Item { visible: Settings.data.location.showWeekNumberInCalendar Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0 } - GridLayout { Layout.fillWidth: true columns: 7 rows: 1 columnSpacing: 0 rowSpacing: 0 - Repeater { model: 7 - Item { Layout.fillWidth: true Layout.preferredHeight: Style.baseWidgetSize * 0.6 * scaling - NText { anchors.centerIn: parent text: { @@ -379,27 +368,20 @@ NPanel { } } } - - // Grid with weeks and days RowLayout { Layout.fillWidth: true Layout.fillHeight: true spacing: 0 - - // Column of week numbers ColumnLayout { visible: Settings.data.location.showWeekNumberInCalendar Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 * scaling : 0 Layout.fillHeight: true spacing: 0 - Repeater { model: 6 - Item { Layout.fillWidth: true Layout.fillHeight: true - NText { anchors.centerIn: parent color: Color.mOutline @@ -431,27 +413,21 @@ NPanel { } } } - - // Days Grid MonthGrid { id: grid - Layout.fillWidth: true Layout.fillHeight: true spacing: Style.marginXXS * scaling month: Time.date.getMonth() year: Time.date.getFullYear() locale: Qt.locale() - delegate: Item { Rectangle { width: Style.baseWidgetSize * 0.9 * scaling height: Style.baseWidgetSize * 0.9 * scaling anchors.centerIn: parent radius: Style.radiusM * scaling - color: model.today ? Color.mSecondary : Color.transparent - NText { anchors.centerIn: parent text: model.day @@ -466,7 +442,6 @@ NPanel { pointSize: Style.fontSizeM * scaling font.weight: model.today ? Style.fontWeightBold : Style.fontWeightMedium } - Behavior on color { ColorAnimation { duration: Style.animationFast diff --git a/Modules/Settings/SettingsPanel.qml b/Modules/Settings/SettingsPanel.qml index 28b21800..ff499cfe 100644 --- a/Modules/Settings/SettingsPanel.qml +++ b/Modules/Settings/SettingsPanel.qml @@ -129,12 +129,11 @@ NPanel { "label": "settings.bar.title", "icon": "settings-bar", "source": barTab - }, - //{ - // "id": SettingsPanel.Tab.ControlCenter, - // "label": "settings.control-center.title", - // "icon": "settings-bar", - // "source": controlCenterTab + }, //{ + // "id": SettingsPanel.Tab.ControlCenter, + // "label": "settings.control-center.title", + // "icon": "settings-bar", + // "source": controlCenterTab //}, { "id": SettingsPanel.Tab.Dock, diff --git a/Modules/Settings/Tabs/DockTab.qml b/Modules/Settings/Tabs/DockTab.qml index 60ad8a9f..5d31c074 100644 --- a/Modules/Settings/Tabs/DockTab.qml +++ b/Modules/Settings/Tabs/DockTab.qml @@ -29,30 +29,24 @@ ColumnLayout { description: I18n.tr("settings.dock.appearance.section.description") } - ColumnLayout { - spacing: Style.marginXXS * scaling + NComboBox { Layout.fillWidth: true - NLabel { - label: I18n.tr("settings.dock.appearance.display.label") - description: I18n.tr("settings.dock.appearance.display.description") - } - NComboBox { - Layout.fillWidth: true - model: [{ - "key": "always_visible", - "name": I18n.tr("settings.dock.appearance.display.always-visible") - }, { - "key": "auto_hide", - "name": I18n.tr("settings.dock.appearance.display.auto-hide") - }, { - "key": "exclusive", - "name": I18n.tr("settings.dock.appearance.display.exclusive") - }] - currentKey: Settings.data.dock.displayMode - onSelected: key => { - Settings.data.dock.displayMode = key - } - } + label: I18n.tr("settings.dock.appearance.display.label") + description: I18n.tr("settings.dock.appearance.display.description") + model: [{ + "key": "always_visible", + "name": I18n.tr("settings.dock.appearance.display.always-visible") + }, { + "key": "auto_hide", + "name": I18n.tr("settings.dock.appearance.display.auto-hide") + }, { + "key": "exclusive", + "name": I18n.tr("settings.dock.appearance.display.exclusive") + }] + currentKey: Settings.data.dock.displayMode + onSelected: key => { + Settings.data.dock.displayMode = key + } } ColumnLayout { From 263f3c5fd27e0790989c77614ef8df39a5fb1fc9 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 15:44:06 +0200 Subject: [PATCH 082/106] Matugen: fix discord theming ColorSchemeTab: fix predefined color scheme preview --- Modules/Settings/Tabs/ColorSchemeTab.qml | 128 +++++++++++++++++++++-- Services/AppThemeService.qml | 2 +- 2 files changed, 123 insertions(+), 7 deletions(-) diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index d8e8f338..6789a2d0 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -12,6 +12,9 @@ ColumnLayout { // Cache for scheme JSON (can be flat or {dark, light}) property var schemeColorsCache: ({}) + // Signal to notify when cache is updated + signal cacheUpdated + spacing: Style.marginL * scaling // Helper function to extract scheme name from path @@ -52,12 +55,126 @@ ColumnLayout { return "#000000" } + // Alternative function that tries to load colors directly + function getSchemeColorDirect(schemePath, colorKey) { + var schemeName = extractSchemeName(schemePath) + + // Try to load the file directly using ColorSchemeService's resolveSchemePath + var filePath = ColorSchemeService.resolveSchemePath(schemeName) + if (!filePath) + return "#000000" + + // For now, return a placeholder color based on the scheme name + // This is a temporary solution until we can properly load the files + var colors = { + "Ayu": { + "mSurface": "#1e222a", + "mPrimary": "#E6B450", + "mSecondary": "#AAD94C", + "mTertiary": "#39BAE6", + "mError": "#D95757" + }, + "Catppuccin": { + "mSurface": "#1e1e2e", + "mPrimary": "#cba6f7", + "mSecondary": "#fab387", + "mTertiary": "#94e2d5", + "mError": "#f38ba8" + }, + "Dracula": { + "mSurface": "#282a36", + "mPrimary": "#bd93f9", + "mSecondary": "#ff79c6", + "mTertiary": "#8be9fd", + "mError": "#ff5555" + }, + "Everforest": { + "mSurface": "#2d353b", + "mPrimary": "#a7c080", + "mSecondary": "#dbbc7f", + "mTertiary": "#7fbbb3", + "mError": "#e67e80" + }, + "Gruvbox": { + "mSurface": "#282828", + "mPrimary": "#fabd2f", + "mSecondary": "#fe8019", + "mTertiary": "#8ec07c", + "mError": "#fb4934" + }, + "Kanagawa": { + "mSurface": "#1f1f28", + "mPrimary": "#c8c093", + "mSecondary": "#d27e99", + "mTertiary": "#7aa89f", + "mError": "#c34043" + }, + "Monochrome": { + "mSurface": "#1a1a1a", + "mPrimary": "#ffffff", + "mSecondary": "#cccccc", + "mTertiary": "#999999", + "mError": "#ff0000" + }, + "Noctalia (default)": { + "mSurface": "#1c1822", + "mPrimary": "#c7a1d8", + "mSecondary": "#a984c4", + "mTertiary": "#e0b7c9", + "mError": "#e9899d" + }, + "Noctalia (legacy)": { + "mSurface": "#1c1822", + "mPrimary": "#c7a1d8", + "mSecondary": "#a984c4", + "mTertiary": "#e0b7c9", + "mError": "#e9899d" + }, + "Nord": { + "mSurface": "#2e3440", + "mPrimary": "#88c0d0", + "mSecondary": "#81a1c1", + "mTertiary": "#8fbcbb", + "mError": "#bf616a" + }, + "Rosepine": { + "mSurface": "#191724", + "mPrimary": "#c4a7e7", + "mSecondary": "#ebbcba", + "mTertiary": "#9ccfd8", + "mError": "#eb6f92" + }, + "Solarized": { + "mSurface": "#002b36", + "mPrimary": "#268bd2", + "mSecondary": "#2aa198", + "mTertiary": "#859900", + "mError": "#dc322f" + }, + "Tokyo Night": { + "mSurface": "#1a1b26", + "mPrimary": "#7aa2f7", + "mSecondary": "#bb9af7", + "mTertiary": "#9ece6a", + "mError": "#f7768e" + } + } + + if (colors[schemeName] && colors[schemeName][colorKey]) { + return colors[schemeName][colorKey] + } + + return "#000000" + } + // This function is called by the FileView Repeater when a scheme file is loaded function schemeLoaded(schemeName, jsonData) { var value = jsonData || {} var newCache = schemeColorsCache newCache[schemeName] = value schemeColorsCache = newCache + // Force UI update by triggering our custom signal + cacheUpdated() } // When the list of available schemes changes, clear the cache. @@ -103,7 +220,6 @@ ColumnLayout { delegate: Item { FileView { path: modelData - blockLoading: true onLoaded: { // Extract scheme name from path var schemeName = extractSchemeName(path) @@ -237,7 +353,7 @@ ColumnLayout { Layout.alignment: Qt.AlignHCenter height: 50 * scaling radius: Style.radiusS * scaling - color: getSchemeColor(modelData, "mSurface") + color: getSchemeColorDirect(modelData, "mSurface") border.width: Math.max(1, Style.borderL * scaling) border.color: { if (Settings.data.colorSchemes.predefinedScheme === extractSchemeName(modelData)) { @@ -271,28 +387,28 @@ ColumnLayout { width: 14 * scaling height: 14 * scaling radius: width * 0.5 - color: getSchemeColor(modelData, "mPrimary") + color: getSchemeColorDirect(modelData, "mPrimary") } Rectangle { width: 14 * scaling height: 14 * scaling radius: width * 0.5 - color: getSchemeColor(modelData, "mSecondary") + color: getSchemeColorDirect(modelData, "mSecondary") } Rectangle { width: 14 * scaling height: 14 * scaling radius: width * 0.5 - color: getSchemeColor(modelData, "mTertiary") + color: getSchemeColorDirect(modelData, "mTertiary") } Rectangle { width: 14 * scaling height: 14 * scaling radius: width * 0.5 - color: getSchemeColor(modelData, "mError") + color: getSchemeColorDirect(modelData, "mError") } } diff --git a/Services/AppThemeService.qml b/Services/AppThemeService.qml index 0113216b..3fbf1153 100644 --- a/Services/AppThemeService.qml +++ b/Services/AppThemeService.qml @@ -61,7 +61,7 @@ Singleton { }], "postProcess": () => `${colorsApplyScript} pywalfox\n` }, - "vesktop": { + "discord_vesktop": { "input": "vesktop.css", "outputs": [{ "path": "~/.config/vesktop/themes/noctalia.theme.css" From 656e15f5892393be195055478f56aee08c770199 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 15:51:21 +0200 Subject: [PATCH 083/106] ColorSchemeTab: properly fix predefined colorscheme preview --- Modules/Settings/Tabs/ColorSchemeTab.qml | 200 ++++++----------------- 1 file changed, 46 insertions(+), 154 deletions(-) diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 6789a2d0..a5c66ead 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -11,19 +11,16 @@ ColumnLayout { // Cache for scheme JSON (can be flat or {dark, light}) property var schemeColorsCache: ({}) - - // Signal to notify when cache is updated - signal cacheUpdated + property int cacheVersion: 0 // Increment to trigger UI updates spacing: Style.marginL * scaling // Helper function to extract scheme name from path function extractSchemeName(schemePath) { var pathParts = schemePath.split("/") - var filename = pathParts[pathParts.length - 1] // Get filename - var schemeName = filename.replace(".json", "") // Remove .json extension + var filename = pathParts[pathParts.length - 1] + var schemeName = filename.replace(".json", "") - // Convert folder names back to display names if (schemeName === "Noctalia-default") { schemeName = "Noctalia (default)" } else if (schemeName === "Noctalia-legacy") { @@ -36,153 +33,52 @@ ColumnLayout { } // Helper function to get color from scheme file (supports dark/light variants) - function getSchemeColor(schemePath, colorKey) { - // Extract scheme name from path - var schemeName = extractSchemeName(schemePath) + function getSchemeColor(schemeName, colorKey) { + // Access cache version to create dependency + var _ = cacheVersion - // Try to get from cached data first if (schemeColorsCache[schemeName]) { var entry = schemeColorsCache[schemeName] var variant = entry + + // Check if scheme has dark/light variants if (entry.dark || entry.light) { variant = Settings.data.colorSchemes.darkMode ? (entry.dark || entry.light) : (entry.light || entry.dark) } - if (variant && variant[colorKey]) + + if (variant && variant[colorKey]) { return variant[colorKey] - } - - // Return a default color if not cached yet - return "#000000" - } - - // Alternative function that tries to load colors directly - function getSchemeColorDirect(schemePath, colorKey) { - var schemeName = extractSchemeName(schemePath) - - // Try to load the file directly using ColorSchemeService's resolveSchemePath - var filePath = ColorSchemeService.resolveSchemePath(schemeName) - if (!filePath) - return "#000000" - - // For now, return a placeholder color based on the scheme name - // This is a temporary solution until we can properly load the files - var colors = { - "Ayu": { - "mSurface": "#1e222a", - "mPrimary": "#E6B450", - "mSecondary": "#AAD94C", - "mTertiary": "#39BAE6", - "mError": "#D95757" - }, - "Catppuccin": { - "mSurface": "#1e1e2e", - "mPrimary": "#cba6f7", - "mSecondary": "#fab387", - "mTertiary": "#94e2d5", - "mError": "#f38ba8" - }, - "Dracula": { - "mSurface": "#282a36", - "mPrimary": "#bd93f9", - "mSecondary": "#ff79c6", - "mTertiary": "#8be9fd", - "mError": "#ff5555" - }, - "Everforest": { - "mSurface": "#2d353b", - "mPrimary": "#a7c080", - "mSecondary": "#dbbc7f", - "mTertiary": "#7fbbb3", - "mError": "#e67e80" - }, - "Gruvbox": { - "mSurface": "#282828", - "mPrimary": "#fabd2f", - "mSecondary": "#fe8019", - "mTertiary": "#8ec07c", - "mError": "#fb4934" - }, - "Kanagawa": { - "mSurface": "#1f1f28", - "mPrimary": "#c8c093", - "mSecondary": "#d27e99", - "mTertiary": "#7aa89f", - "mError": "#c34043" - }, - "Monochrome": { - "mSurface": "#1a1a1a", - "mPrimary": "#ffffff", - "mSecondary": "#cccccc", - "mTertiary": "#999999", - "mError": "#ff0000" - }, - "Noctalia (default)": { - "mSurface": "#1c1822", - "mPrimary": "#c7a1d8", - "mSecondary": "#a984c4", - "mTertiary": "#e0b7c9", - "mError": "#e9899d" - }, - "Noctalia (legacy)": { - "mSurface": "#1c1822", - "mPrimary": "#c7a1d8", - "mSecondary": "#a984c4", - "mTertiary": "#e0b7c9", - "mError": "#e9899d" - }, - "Nord": { - "mSurface": "#2e3440", - "mPrimary": "#88c0d0", - "mSecondary": "#81a1c1", - "mTertiary": "#8fbcbb", - "mError": "#bf616a" - }, - "Rosepine": { - "mSurface": "#191724", - "mPrimary": "#c4a7e7", - "mSecondary": "#ebbcba", - "mTertiary": "#9ccfd8", - "mError": "#eb6f92" - }, - "Solarized": { - "mSurface": "#002b36", - "mPrimary": "#268bd2", - "mSecondary": "#2aa198", - "mTertiary": "#859900", - "mError": "#dc322f" - }, - "Tokyo Night": { - "mSurface": "#1a1b26", - "mPrimary": "#7aa2f7", - "mSecondary": "#bb9af7", - "mTertiary": "#9ece6a", - "mError": "#f7768e" } } - if (colors[schemeName] && colors[schemeName][colorKey]) { - return colors[schemeName][colorKey] - } - - return "#000000" + // Return visible defaults while loading + if (colorKey === "mSurface") + return Color.mSurfaceVariant + if (colorKey === "mPrimary") + return Color.mPrimary + if (colorKey === "mSecondary") + return Color.mSecondary + if (colorKey === "mTertiary") + return Color.mTertiary + if (colorKey === "mError") + return Color.mError + return Color.mOnSurfaceVariant } // This function is called by the FileView Repeater when a scheme file is loaded function schemeLoaded(schemeName, jsonData) { var value = jsonData || {} - var newCache = schemeColorsCache - newCache[schemeName] = value - schemeColorsCache = newCache - // Force UI update by triggering our custom signal - cacheUpdated() + schemeColorsCache[schemeName] = value + // Force UI update by incrementing cache version + cacheVersion++ } - // When the list of available schemes changes, clear the cache. - // The Repeater below will automatically re-create the FileViews. + // When the list of available schemes changes, clear the cache Connections { target: ColorSchemeService function onSchemesChanged() { schemeColorsCache = {} + cacheVersion++ } } @@ -194,12 +90,10 @@ ColumnLayout { onExited: function (exitCode) { if (exitCode === 0) { - // Matugen exists, enable it Settings.data.colorSchemes.useWallpaperColors = true AppThemeService.generate() ToastService.showNotice(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.enabled")) } else { - // Matugen not found ToastService.showWarning(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.not-installed")) } } @@ -208,7 +102,7 @@ ColumnLayout { stderr: StdioCollector {} } - // A non-visual Item to host the Repeater that loads the color scheme files. + // A non-visual Item to host the Repeater that loads the color scheme files Item { visible: false id: fileLoaders @@ -216,20 +110,19 @@ ColumnLayout { Repeater { model: ColorSchemeService.schemes - // The delegate is a Component, which correctly wraps the non-visual FileView delegate: Item { FileView { path: modelData + blockLoading: false onLoaded: { - // Extract scheme name from path - var schemeName = extractSchemeName(path) + var schemeName = root.extractSchemeName(path) try { var jsonData = JSON.parse(text()) root.schemeLoaded(schemeName, jsonData) } catch (e) { Logger.warn("ColorSchemeTab", "Failed to parse JSON for scheme:", schemeName, e) - root.schemeLoaded(schemeName, null) // Load defaults on parse error + root.schemeLoaded(schemeName, null) } } } @@ -243,13 +136,16 @@ ColumnLayout { description: I18n.tr("settings.color-scheme.color-source.section.description") } - // Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants) + // Dark Mode Toggle NToggle { label: I18n.tr("settings.color-scheme.color-source.dark-mode.label") description: I18n.tr("settings.color-scheme.color-source.dark-mode.description") checked: Settings.data.colorSchemes.darkMode enabled: true - onToggled: checked => Settings.data.colorSchemes.darkMode = checked + onToggled: checked => { + Settings.data.colorSchemes.darkMode = checked + root.cacheVersion++ // Force UI update for dark/light variants + } } // Use Wallpaper Colors @@ -259,14 +155,12 @@ ColumnLayout { checked: Settings.data.colorSchemes.useWallpaperColors onToggled: checked => { if (checked) { - // Check if matugen is installed matugenCheck.running = true } else { Settings.data.colorSchemes.useWallpaperColors = false ToastService.showNotice(I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label"), I18n.tr("toast.wallpaper-colors.disabled")) if (Settings.data.colorSchemes.predefinedScheme) { - ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) } } @@ -311,7 +205,6 @@ ColumnLayout { onSelected: key => { Settings.data.colorSchemes.matugenSchemeType = key - AppThemeService.generate() } } @@ -348,15 +241,16 @@ ColumnLayout { id: schemeItem property string schemePath: modelData + property string schemeName: root.extractSchemeName(modelData) Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter height: 50 * scaling radius: Style.radiusS * scaling - color: getSchemeColorDirect(modelData, "mSurface") + color: root.getSchemeColor(schemeName, "mSurface") border.width: Math.max(1, Style.borderL * scaling) border.color: { - if (Settings.data.colorSchemes.predefinedScheme === extractSchemeName(modelData)) { + if (Settings.data.colorSchemes.predefinedScheme === schemeName) { return Color.mSecondary } if (itemMouseArea.containsMouse) { @@ -371,12 +265,11 @@ ColumnLayout { spacing: Style.marginXS * scaling NText { - text: extractSchemeName(schemePath) + text: schemeItem.schemeName pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightMedium color: Color.mOnSurface Layout.fillWidth: true - // Layout.maximumWidth: 150 * scaling elide: Text.ElideRight verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap @@ -387,28 +280,28 @@ ColumnLayout { width: 14 * scaling height: 14 * scaling radius: width * 0.5 - color: getSchemeColorDirect(modelData, "mPrimary") + color: root.getSchemeColor(schemeItem.schemeName, "mPrimary") } Rectangle { width: 14 * scaling height: 14 * scaling radius: width * 0.5 - color: getSchemeColorDirect(modelData, "mSecondary") + color: root.getSchemeColor(schemeItem.schemeName, "mSecondary") } Rectangle { width: 14 * scaling height: 14 * scaling radius: width * 0.5 - color: getSchemeColorDirect(modelData, "mTertiary") + color: root.getSchemeColor(schemeItem.schemeName, "mTertiary") } Rectangle { width: 14 * scaling height: 14 * scaling radius: width * 0.5 - color: getSchemeColorDirect(modelData, "mError") + color: root.getSchemeColor(schemeItem.schemeName, "mError") } } @@ -421,14 +314,14 @@ ColumnLayout { Settings.data.colorSchemes.useWallpaperColors = false Logger.log("ColorSchemeTab", "Disabled wallpaper colors") - Settings.data.colorSchemes.predefinedScheme = extractSchemeName(schemePath) + Settings.data.colorSchemes.predefinedScheme = schemeItem.schemeName ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) } } // Selection indicator Rectangle { - visible: (Settings.data.colorSchemes.predefinedScheme === extractSchemeName(schemePath)) + visible: (Settings.data.colorSchemes.predefinedScheme === schemeItem.schemeName) anchors.right: parent.right anchors.top: parent.top anchors.rightMargin: -3 * scaling @@ -466,7 +359,6 @@ ColumnLayout { checked: Settings.data.colorSchemes.generateTemplatesForPredefined onToggled: checked => { Settings.data.colorSchemes.generateTemplatesForPredefined = checked - // Re-generate templates if a predefined scheme is currently active if (!Settings.data.colorSchemes.useWallpaperColors && Settings.data.colorSchemes.predefinedScheme) { ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) } From 54fa04f303a563dfd5698ce85a719c2828536d03 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 11 Oct 2025 10:29:28 -0400 Subject: [PATCH 084/106] Compositor: proper monitor scaling detection and display in settings + fixes blurry wallpapers on compositor scaled monitors. --- Assets/Translations/en.json | 2 +- Modules/Background/Background.qml | 99 +++++++++++++--------------- Modules/Settings/Tabs/DisplayTab.qml | 14 ++-- Services/CompositorService.qml | 85 ++++++++++++++++++++++++ Services/HyprlandService.qml | 71 +++++++++++++++++++- Services/NiriService.qml | 62 ++++++++++++++++- Services/SwayService.qml | 67 +++++++++++++++++++ 7 files changed, 337 insertions(+), 63 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index c4ff50ae..a90e24a7 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1394,7 +1394,7 @@ "system": { "uptime": "Uptime: {uptime}", "welcome-back": "Welcome back,", - "monitor-description": "{model} ({width}x{height})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", "scaling-percentage": "{percentage}%", "location-display": "{name} ({coordinates})", "signal-strength": "{signal}%", diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 4b917522..2bcbb04f 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -44,19 +44,6 @@ Variants { property real fillMode: WallpaperService.getFillModeUniform() property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r, Settings.data.wallpaper.fillColor.g, Settings.data.wallpaper.fillColor.b, 1.0) - property int monitoredWidth: modelData.width - property int monitoredHeight: modelData.height - - onMonitoredWidthChanged: { - Logger.log("Background", "Screen width changed to:", monitoredWidth, "for", modelData.name) - recalculateImageSizes() - } - - onMonitoredHeightChanged: { - Logger.log("Background", "Screen height changed to:", monitoredHeight, "for", modelData.name) - recalculateImageSizes() - } - Component.onCompleted: setWallpaperInitial() Component.onDestruction: { @@ -87,6 +74,13 @@ Variants { } } + Connections { + target: CompositorService + function onDisplayScalesChanged() { + setWallpaperInitial() + } + } + color: Color.transparent screen: modelData WlrLayershell.layer: WlrLayer.Background @@ -122,38 +116,21 @@ Variants { cache: false asynchronous: true sourceSize: undefined - onStatusChanged: { if (status === Image.Error) { Logger.warn("Current wallpaper failed to load:", source) } else if (status === Image.Ready && !dimensionsCalculated) { dimensionsCalculated = true - calculateSourceSize() + const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight) + if (optimalSize !== false) { + sourceSize = optimalSize + } } } - onSourceChanged: { dimensionsCalculated = false sourceSize = undefined } - - function calculateSourceSize() { - if (implicitWidth === modelData.width || implicitHeight === modelData.height) { - // Do not resize if one of the dimensions fits perfectly on the screen - return - } - - if (implicitWidth > 0 && implicitHeight > 0) { - const imageAspectRatio = implicitWidth / implicitHeight - if (modelData.width >= modelData.height) { - const w = Math.min(modelData.width, implicitWidth) - sourceSize = Qt.size(w, w / imageAspectRatio) - } else { - const h = Math.min(modelData.height, implicitHeight) - sourceSize = Qt.size(h * imageAspectRatio, h) - } - } - } } Image { @@ -168,38 +145,21 @@ Variants { cache: false asynchronous: true sourceSize: undefined - onStatusChanged: { if (status === Image.Error) { Logger.warn("Next wallpaper failed to load:", source) } else if (status === Image.Ready && !dimensionsCalculated) { dimensionsCalculated = true - calculateSourceSize() + const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight) + if (optimalSize !== false) { + sourceSize = optimalSize + } } } - onSourceChanged: { dimensionsCalculated = false sourceSize = undefined } - - function calculateSourceSize() { - if (implicitWidth === modelData.width || implicitHeight === modelData.height) { - // Do not resize if one of the dimensions fits perfectly on the screen - return - } - - if (implicitWidth > 0 && implicitHeight > 0) { - const imageAspectRatio = implicitWidth / implicitHeight - if (modelData.width >= modelData.height) { - const w = Math.min(modelData.width, implicitWidth) - sourceSize = Qt.size(w, w / imageAspectRatio) - } else { - const h = Math.min(modelData.height, implicitHeight) - sourceSize = Qt.size(h * imageAspectRatio, h) - } - } - } } // Dynamic shader loader - only loads the active transition shader @@ -356,6 +316,31 @@ Variants { } } + // ------------------------------------------------------ + function calculateOptimalWallpaperSize(wpWidth, wpHeight) { + const compositorScale = CompositorService.getDisplayScale(modelData.name) + const screenWidth = modelData.width * compositorScale + const screenHeight = modelData.height * compositorScale + if (wpWidth <= screenWidth || wpHeight <= screenHeight || wpWidth <= 0 || wpHeight <= 0) { + // Do not resize if wallpaper is smaller than one of the screen dimension + return + } + + const imageAspectRatio = wpWidth / wpHeight + var dim = Qt.size(0, 0) + if (screenWidth >= screenHeight) { + const w = Math.min(screenWidth, wpWidth) + dim = Qt.size(w, w / imageAspectRatio) + } else { + const h = Math.min(screenHeight, wpHeight) + dim = Qt.size(h * imageAspectRatio, h) + } + + Logger.log("Background", `Wallpaper resized on ${modelData.name} ${screenWidth}x${screenHeight} @ ${compositorScale}x`, "src:", wpWidth, wpHeight, "dst:", dim.width, dim.height) + return dim + } + + // ------------------------------------------------------ function recalculateImageSizes() { if (currentWallpaper.status === Image.Ready) { currentWallpaper.calculateSourceSize() @@ -365,6 +350,7 @@ Variants { } } + // ------------------------------------------------------ function setWallpaperInitial() { // On startup, defer assigning wallpaper until the service cache is ready, retries every tick if (!WallpaperService || !WallpaperService.isInitialized) { @@ -375,6 +361,7 @@ Variants { setWallpaperImmediate(WallpaperService.getWallpaper(modelData.name)) } + // ------------------------------------------------------ function setWallpaperImmediate(source) { transitionAnimation.stop() transitionProgress = 0.0 @@ -388,6 +375,7 @@ Variants { }) } + // ------------------------------------------------------ function setWallpaperWithTransition(source) { if (source === currentWallpaper.source) { return @@ -421,6 +409,7 @@ Variants { transitionAnimation.start() } + // ------------------------------------------------------ // Main method that actually trigger the wallpaper change function changeWallpaper() { // Get the transitionType from the settings diff --git a/Modules/Settings/Tabs/DisplayTab.qml b/Modules/Settings/Tabs/DisplayTab.qml index d6ae8420..6eed0a7b 100644 --- a/Modules/Settings/Tabs/DisplayTab.qml +++ b/Modules/Settings/Tabs/DisplayTab.qml @@ -90,11 +90,15 @@ ColumnLayout { NLabel { label: modelData.name || "Unknown" - description: I18n.tr("system.monitor-description", { - "model": modelData.model, - "width": modelData.width, - "height": modelData.height - }) + description: { + const compositorScale = CompositorService.getDisplayScale(modelData.name) + I18n.tr("system.monitor-description", { + "model": modelData.model, + "width": modelData.width * compositorScale, + "height": modelData.height * compositorScale, + "scale": compositorScale + }) + } } // Scale diff --git a/Services/CompositorService.qml b/Services/CompositorService.qml index fdbde343..a9dd46e6 100644 --- a/Services/CompositorService.qml +++ b/Services/CompositorService.qml @@ -2,6 +2,7 @@ pragma Singleton import QtQuick import Quickshell +import Quickshell.Io import qs.Commons import qs.Services @@ -18,6 +19,10 @@ Singleton { property ListModel windows: ListModel {} property int focusedWindowIndex: -1 + // Display scale data + property var displayScales: ({}) + property bool displayScalesLoaded: false + // Generic events signal workspaceChanged signal activeWindowChanged @@ -26,7 +31,18 @@ Singleton { // Backend service loader property var backend: null + // Cache file path + property string displayCachePath: "" + Component.onCompleted: { + // Setup cache path (needs Settings to be available) + Qt.callLater(() => { + if (typeof Settings !== 'undefined' && Settings.cacheDir) { + displayCachePath = Settings.cacheDir + "display.json" + displayCacheFileView.path = displayCachePath + } + }) + detectCompositor() } @@ -69,6 +85,31 @@ Singleton { } } + // Cache FileView for display scales + FileView { + id: displayCacheFileView + printErrors: false + watchChanges: false + + adapter: JsonAdapter { + id: displayCacheAdapter + property var displays: ({}) + } + + onLoaded: { + // Load cached display scales + displayScales = displayCacheAdapter.displays || {} + displayScalesLoaded = true + // Logger.log("CompositorService", "Loaded display scales from cache:", JSON.stringify(displayScales)) + } + + onLoadFailed: { + // Cache doesn't exist yet, will be created on first update + displayScalesLoaded = true + // Logger.log("CompositorService", "No display cache found, will create on first update") + } + } + // Hyprland backend component Component { id: hyprlandComponent @@ -151,6 +192,50 @@ Singleton { windowListChanged() } + // Update display scales from backend + function updateDisplayScales() { + if (!backend || !backend.queryDisplayScales) { + Logger.warn("CompositorService", "Backend does not support display scale queries") + return + } + + backend.queryDisplayScales() + } + + // Called by backend when display scales are ready + function onDisplayScalesUpdated(scales) { + displayScales = scales + saveDisplayScalesToCache() + displayScalesChanged() + Logger.log("CompositorService", "Display scales updated") + } + + // Save display scales to cache + function saveDisplayScalesToCache() { + if (!displayCachePath) { + return + } + + displayCacheAdapter.displays = displayScales + displayCacheFileView.writeAdapter() + } + + // Public function to get scale for a specific display + function getDisplayScale(displayName) { + if (!displayName || !displayScales[displayName]) { + return 1.0 + } + return displayScales[displayName].scale || 1.0 + } + + // Public function to get all display info for a specific display + function getDisplayInfo(displayName) { + if (!displayName || !displayScales[displayName]) { + return null + } + return displayScales[displayName] + } + // Get focused window function getFocusedWindow() { if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.count) { diff --git a/Services/HyprlandService.qml b/Services/HyprlandService.qml index 81d09549..0cafa965 100644 --- a/Services/HyprlandService.qml +++ b/Services/HyprlandService.qml @@ -1,6 +1,7 @@ import QtQuick import Quickshell import Quickshell.Hyprland +import Quickshell.Io import qs.Commons Item { @@ -15,6 +16,7 @@ Item { signal workspaceChanged signal activeWindowChanged signal windowListChanged + signal displayScalesChanged // Hyprland-specific properties property bool initialized: false @@ -40,6 +42,7 @@ Item { Qt.callLater(() => { safeUpdateWorkspaces() safeUpdateWindows() + queryDisplayScales() }) initialized = true Logger.log("HyprlandService", "Initialized successfully") @@ -48,6 +51,67 @@ Item { } } + // Query display scales + function queryDisplayScales() { + hyprlandMonitorsProcess.running = true + } + + // Hyprland monitors process for display scale detection + // Hyprland monitors process for display scale detection + Process { + id: hyprlandMonitorsProcess + running: false + command: ["hyprctl", "monitors", "-j"] + + property string accumulatedOutput: "" + + stdout: SplitParser { + onRead: function (line) { + // Accumulate lines instead of parsing each one + hyprlandMonitorsProcess.accumulatedOutput += line + } + } + + onExited: function (exitCode) { + if (exitCode !== 0 || !accumulatedOutput) { + Logger.error("HyprlandService", "Failed to query monitors, exit code:", exitCode) + accumulatedOutput = "" + return + } + + try { + const monitorsData = JSON.parse(accumulatedOutput) + const scales = {} + + for (const monitor of monitorsData) { + if (monitor.name) { + scales[monitor.name] = { + "name": monitor.name, + "scale": monitor.scale || 1.0, + "width": monitor.width || 0, + "height": monitor.height || 0, + "refresh_rate": monitor.refreshRate || 0, + "x": monitor.x || 0, + "y": monitor.y || 0, + "active_workspace": monitor.activeWorkspace ? monitor.activeWorkspace.id : -1, + "vrr": monitor.vrr || false, + "focused": monitor.focused || false + } + } + } + + // Notify CompositorService (it will emit displayScalesChanged) + if (CompositorService && CompositorService.onDisplayScalesUpdated) { + CompositorService.onDisplayScalesUpdated(scales) + } + } catch (e) { + Logger.error("HyprlandService", "Failed to parse monitors:", e) + } finally { + // Clear accumulated output for next query + accumulatedOutput = "" + } + } + } // Safe update wrapper function safeUpdate() { safeUpdateWindows() @@ -188,7 +252,7 @@ Item { "id": windowId, "title": title, "appId": appId, - "workspaceId": wsId, + "workspaceId": wsId || -1, "isFocused": focused, "output": output } @@ -268,6 +332,11 @@ Item { safeUpdateWorkspaces() workspaceChanged() updateTimer.restart() + + const monitorsEvents = ["configreloaded", "monitoradded", "monitorremoved", "monitoraddedv2", "monitorremovedv2"] + if (monitorsEvents.includes(event.name)) { + Qt.callLater(queryDisplayScales) + } } } diff --git a/Services/NiriService.qml b/Services/NiriService.qml index 9f3df88b..16226348 100644 --- a/Services/NiriService.qml +++ b/Services/NiriService.qml @@ -20,12 +20,14 @@ Item { signal workspaceChanged signal activeWindowChanged signal windowListChanged + signal displayScalesChanged // Initialization function initialize() { niriEventStream.running = true updateWorkspaces() updateWindows() + queryDisplayScales() Logger.log("NiriService", "Initialized successfully") } @@ -39,6 +41,60 @@ Item { niriWindowsProcess.running = true } + // Query display scales + function queryDisplayScales() { + niriOutputsProcess.running = true + } + + // Niri outputs process for display scale detection + Process { + id: niriOutputsProcess + running: false + command: ["niri", "msg", "--json", "outputs"] + + stdout: SplitParser { + onRead: function (line) { + try { + const outputsData = JSON.parse(line) + const scales = {} + + // Niri returns an object with display names as keys + for (const outputName in outputsData) { + const output = outputsData[outputName] + if (output && output.name) { + const logical = output.logical || {} + const currentModeIdx = output.current_mode || 0 + const modes = output.modes || [] + const currentMode = modes[currentModeIdx] || {} + + scales[output.name] = { + "name": output.name, + "scale": logical.scale || 1.0, + "width": logical.width || 0, + "height": logical.height || 0, + "x": logical.x || 0, + "y": logical.y || 0, + "physical_width": (output.physical_size && output.physical_size[0]) || 0, + "physical_height": (output.physical_size && output.physical_size[1]) || 0, + "refresh_rate": currentMode.refresh_rate || 0, + "vrr_supported": output.vrr_supported || false, + "vrr_enabled": output.vrr_enabled || false, + "transform": logical.transform || "Normal" + } + } + } + + // Notify CompositorService (it will emit displayScalesChanged) + if (CompositorService && CompositorService.onDisplayScalesUpdated) { + CompositorService.onDisplayScalesUpdated(scales) + } + } catch (e) { + Logger.error("NiriService", "Failed to parse outputs:", e, line) + } + } + } + } + // Niri workspace process Process { id: niriWorkspaceProcess @@ -86,7 +142,7 @@ Item { } } - // Niri windows process (for initial load) + // Niri windows process Process { id: niriWindowsProcess running: false @@ -131,6 +187,10 @@ Item { handleWindowLayoutsChanged(event.WindowLayoutsChanged) } else if (event.OverviewOpenedOrClosed) { handleOverviewOpenedOrClosed(event.OverviewOpenedOrClosed) + } else if (event.OutputsChanged) { + queryDisplayScales() + } else if (event.ConfigLoaded) { + queryDisplayScales() } } catch (e) { Logger.error("NiriService", "Error parsing event stream:", e, data) diff --git a/Services/SwayService.qml b/Services/SwayService.qml index ad54125c..9e1ddd71 100644 --- a/Services/SwayService.qml +++ b/Services/SwayService.qml @@ -2,6 +2,7 @@ import QtQuick import Quickshell import Quickshell.I3 import Quickshell.Wayland +import Quickshell.Io import qs.Commons Item { @@ -16,6 +17,7 @@ Item { signal workspaceChanged signal activeWindowChanged signal windowListChanged + signal displayScalesChanged // I3-specific properties property bool initialized: false @@ -38,6 +40,7 @@ Item { Qt.callLater(() => { safeUpdateWorkspaces() safeUpdateWindows() + queryDisplayScales() }) initialized = true Logger.log("SwayService", "Initialized successfully") @@ -46,6 +49,66 @@ Item { } } + // Query display scales + function queryDisplayScales() { + swayOutputsProcess.running = true + } + + // Sway outputs process for display scale detection + Process { + id: swayOutputsProcess + running: false + command: ["swaymsg", "-t", "get_outputs", "-r"] + + property string accumulatedOutput: "" + + stdout: SplitParser { + onRead: function (line) { + swayOutputsProcess.accumulatedOutput += line + } + } + + onExited: function (exitCode) { + if (exitCode !== 0 || !accumulatedOutput) { + Logger.error("SwayService", "Failed to query outputs, exit code:", exitCode) + accumulatedOutput = "" + return + } + + try { + const outputsData = JSON.parse(accumulatedOutput) + const scales = {} + + for (const output of outputsData) { + if (output.name) { + scales[output.name] = { + "name": output.name, + "scale": output.scale || 1.0, + "width": output.current_mode ? output.current_mode.width : 0, + "height": output.current_mode ? output.current_mode.height : 0, + "refresh_rate": output.current_mode ? output.current_mode.refresh : 0, + "x": output.rect ? output.rect.x : 0, + "y": output.rect ? output.rect.y : 0, + "active": output.active || false, + "focused": output.focused || false, + "current_workspace": output.current_workspace || "" + } + } + } + + // Notify CompositorService (it will emit displayScalesChanged) + if (CompositorService && CompositorService.onDisplayScalesUpdated) { + CompositorService.onDisplayScalesUpdated(scales) + } + } catch (e) { + Logger.error("SwayService", "Failed to parse outputs:", e) + } finally { + // Clear accumulated output for next query + accumulatedOutput = "" + } + } + } + // Safe update wrapper function safeUpdate() { safeUpdateWindows() @@ -197,6 +260,10 @@ Item { safeUpdateWorkspaces() workspaceChanged() updateTimer.restart() + + if (event.type === "output") { + Qt.callLater(queryDisplayScales) + } } } From 7449e7a282848f480b1aa2ba9d4541ed6c19aaba Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 11 Oct 2025 10:33:25 -0400 Subject: [PATCH 085/106] Compositor: scale translations --- Assets/Translations/de.json | 2 +- Assets/Translations/es.json | 2 +- Assets/Translations/fr.json | 2 +- Assets/Translations/pt.json | 2 +- Assets/Translations/zh-CN.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index a930f9fa..369eee4a 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -1411,7 +1411,7 @@ "system": { "uptime": "Laufzeit: {uptime}", "welcome-back": "Willkommen zurück,", - "monitor-description": "{model} ({width}x{height})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", "scaling-percentage": "{percentage}%", "location-display": "{name} ({coordinates})", "signal-strength": "{signal}%", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 604303c3..8c79a64f 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -1394,7 +1394,7 @@ "system": { "uptime": "Actividad: {uptime}", "welcome-back": "¡Bienvenido de nuevo,", - "monitor-description": "{model} ({width}x{height})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", "scaling-percentage": "{percentage}%", "location-display": "{name} ({coordinates})", "signal-strength": "{signal}%", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 59e9504a..32c01982 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -1393,7 +1393,7 @@ }, "system": { "welcome-back": "Bon retour,", - "monitor-description": "{model} ({width}x{height})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", "scaling-percentage": "{percentage}%", "location-display": "{name} ({coordinates})", "signal-strength": "{signal}%", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 5cbd6769..67fa8ead 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -1394,7 +1394,7 @@ "system": { "uptime": "Atividade: {uptime}", "welcome-back": "Bem-vindo(a) de volta, {user}!", - "monitor-description": "{model} ({width}x{height})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", "scaling-percentage": "{percentage}%", "location-display": "{name} ({coordinates})", "signal-strength": "{signal}%", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index b257da86..9f421d69 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -1394,7 +1394,7 @@ "system": { "uptime": "系统运行时间:{uptime}", "welcome-back": "欢迎回来,", - "monitor-description": "{model} ({width}x{height})", + "monitor-description": "{model} ({width}x{height} @ {scale}x)", "scaling-percentage": "{percentage}%", "location-display": "{name} ({coordinates})", "signal-strength": "{signal}%", From 5a8da9695da8a5d0e101082812f0d8a88ead6257 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 16:58:23 +0200 Subject: [PATCH 086/106] MediaCard: resize, made title text bigger SystemMonitorCard: made more compact WeatherCard: made more compact ControlCenterPanel: adjust height --- Modules/ControlCenter/Cards/MediaCard.qml | 4 +- .../ControlCenter/Cards/SystemMonitorCard.qml | 85 ++++++++++--------- Modules/ControlCenter/Cards/WeatherCard.qml | 2 +- Modules/ControlCenter/ControlCenterPanel.qml | 12 ++- 4 files changed, 56 insertions(+), 47 deletions(-) diff --git a/Modules/ControlCenter/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml index 2b362fb0..b4d7b7a6 100644 --- a/Modules/ControlCenter/Cards/MediaCard.qml +++ b/Modules/ControlCenter/Cards/MediaCard.qml @@ -110,7 +110,7 @@ NBox { } } - // Player selector - positioned at the very top + // Player selector Rectangle { id: playerSelectorButton anchors.top: parent.top @@ -306,7 +306,7 @@ NBox { NText { visible: MediaService.trackTitle !== "" text: MediaService.trackTitle - pointSize: Style.fontSizeM * scaling + pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold elide: Text.ElideRight wrapMode: Text.Wrap diff --git a/Modules/ControlCenter/Cards/SystemMonitorCard.qml b/Modules/ControlCenter/Cards/SystemMonitorCard.qml index 67ca2c68..4e7254a3 100644 --- a/Modules/ControlCenter/Cards/SystemMonitorCard.qml +++ b/Modules/ControlCenter/Cards/SystemMonitorCard.qml @@ -9,49 +9,52 @@ import qs.Widgets NBox { id: root - ColumnLayout { + Item { id: content - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.leftMargin: Style.marginS * scaling - anchors.rightMargin: Style.marginS * scaling - anchors.topMargin: Style.marginXS * scaling - anchors.bottomMargin: Style.marginM * scaling - spacing: Style.marginS * scaling + anchors.fill: parent + anchors.margins: Style.marginS * scaling - NCircleStat { - value: SystemStatService.cpuUsage - icon: "cpu-usage" - flat: true - contentScale: 0.8 - width: 72 * scaling - height: 68 * scaling - } - NCircleStat { - value: SystemStatService.cpuTemp - suffix: "°C" - icon: "cpu-temperature" - flat: true - contentScale: 0.8 - width: 72 * scaling - height: 68 * scaling - } - NCircleStat { - value: SystemStatService.memPercent - icon: "memory" - flat: true - contentScale: 0.8 - width: 72 * scaling - height: 68 * scaling - } - NCircleStat { - value: SystemStatService.diskPercent - icon: "storage" - flat: true - contentScale: 0.8 - width: 72 * scaling - height: 68 * scaling + ColumnLayout { + anchors.centerIn: parent + spacing: 0 + + NCircleStat { + value: SystemStatService.cpuUsage + icon: "cpu-usage" + flat: true + contentScale: 0.8 + width: 70 * scaling + height: 65 * scaling + Layout.alignment: Qt.AlignHCenter + } + NCircleStat { + value: SystemStatService.cpuTemp + suffix: "°C" + icon: "cpu-temperature" + flat: true + contentScale: 0.8 + width: 70 * scaling + height: 65 * scaling + Layout.alignment: Qt.AlignHCenter + } + NCircleStat { + value: SystemStatService.memPercent + icon: "memory" + flat: true + contentScale: 0.8 + width: 70 * scaling + height: 65 * scaling + Layout.alignment: Qt.AlignHCenter + } + NCircleStat { + value: SystemStatService.diskPercent + icon: "storage" + flat: true + contentScale: 0.8 + width: 70 * scaling + height: 65 * scaling + Layout.alignment: Qt.AlignHCenter + } } } } diff --git a/Modules/ControlCenter/Cards/WeatherCard.qml b/Modules/ControlCenter/Cards/WeatherCard.qml index 8e6257a1..9b7d6aec 100644 --- a/Modules/ControlCenter/Cards/WeatherCard.qml +++ b/Modules/ControlCenter/Cards/WeatherCard.qml @@ -85,7 +85,7 @@ NBox { model: weatherReady ? LocationService.data.weather.daily.time : [] delegate: ColumnLayout { Layout.alignment: Qt.AlignHCenter - spacing: Style.marginL * scaling + spacing: Style.marginXS * scaling NText { text: { var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")) diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index aa9a8b47..07a2cf4e 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -11,7 +11,7 @@ NPanel { id: root preferredWidth: 460 - preferredHeight: 734 + preferredHeight: 740 panelKeyboardFocus: true // Positioning @@ -44,13 +44,13 @@ NPanel { WeatherCard { Layout.fillWidth: true - Layout.preferredHeight: Math.max(220 * scaling) + Layout.preferredHeight: Math.max(190 * scaling) } // Middle section: media + stats column RowLayout { Layout.fillWidth: true - Layout.preferredHeight: Math.max(310 * scaling) + Layout.preferredHeight: Math.max(260 * scaling) spacing: content.cardSpacing // Media card @@ -66,6 +66,12 @@ NPanel { } } + // Audio card below media and system monitor + AudioCard { + Layout.fillWidth: true + Layout.preferredHeight: Math.max(120 * scaling) + } + // Bottom actions (two grouped rows of round buttons) RowLayout { Layout.fillWidth: true From c487f1982e6d4c278aec5088180905cae140ac8c Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 17:04:33 +0200 Subject: [PATCH 087/106] AudioCard: remove spacing between in/output ControlCenterPanel: fix height --- Modules/ControlCenter/Cards/AudioCard.qml | 2 +- Modules/ControlCenter/ControlCenterPanel.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/ControlCenter/Cards/AudioCard.qml b/Modules/ControlCenter/Cards/AudioCard.qml index efa0117a..8436aa13 100644 --- a/Modules/ControlCenter/Cards/AudioCard.qml +++ b/Modules/ControlCenter/Cards/AudioCard.qml @@ -42,7 +42,7 @@ NBox { ColumnLayout { anchors.fill: parent anchors.margins: Style.marginM * scaling - spacing: Style.marginM * scaling + spacing: 0 // Output Volume Section ColumnLayout { diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 07a2cf4e..19e6e30a 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -11,7 +11,7 @@ NPanel { id: root preferredWidth: 460 - preferredHeight: 740 + preferredHeight: 790 panelKeyboardFocus: true // Positioning From df6fdf36561226940ba0717843220e5f033b9842 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 11 Oct 2025 11:28:03 -0400 Subject: [PATCH 088/106] Fix NPanel scaling on Qt6.10 --- Widgets/NPanel.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml index 9c7bf2db..e5fae6cd 100644 --- a/Widgets/NPanel.qml +++ b/Widgets/NPanel.qml @@ -143,8 +143,9 @@ Loader { readonly property real verticalBarWidth: Math.round(Style.barHeight * scaling) Component.onCompleted: { - Logger.log("NPanel", "Opened", root.objectName) + Logger.log("NPanel", "Opened", root.objectName, "on", screen.name) dimmingOpacity = Style.opacityHeavy + root.scaling = scaling = ScalingService.getScreenScale(screen) } Connections { From 3dff242606ba4fe3e9bfde574cc592070d3d8e48 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 18:18:43 +0200 Subject: [PATCH 089/106] OSD: fix vertical layout autoformat --- Modules/ControlCenter/Cards/AudioCard.qml | 1 + Modules/ControlCenter/Cards/SystemMonitorCard.qml | 10 ++++++---- Modules/ControlCenter/ControlCenterPanel.qml | 2 ++ Modules/OSD/OSD.qml | 14 +++++--------- Widgets/NCircleStat.qml | 1 + 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Modules/ControlCenter/Cards/AudioCard.qml b/Modules/ControlCenter/Cards/AudioCard.qml index 8436aa13..a4c975fe 100644 --- a/Modules/ControlCenter/Cards/AudioCard.qml +++ b/Modules/ControlCenter/Cards/AudioCard.qml @@ -9,6 +9,7 @@ import qs.Widgets NBox { id: root + property real scaling: 1.0 property real localOutputVolume: AudioService.volume property real localInputVolume: AudioService.inputVolume diff --git a/Modules/ControlCenter/Cards/SystemMonitorCard.qml b/Modules/ControlCenter/Cards/SystemMonitorCard.qml index 4e7254a3..a0772e96 100644 --- a/Modules/ControlCenter/Cards/SystemMonitorCard.qml +++ b/Modules/ControlCenter/Cards/SystemMonitorCard.qml @@ -9,6 +9,8 @@ import qs.Widgets NBox { id: root + property real scaling: 1.0 + Item { id: content anchors.fill: parent @@ -23,8 +25,8 @@ NBox { icon: "cpu-usage" flat: true contentScale: 0.8 - width: 70 * scaling height: 65 * scaling + scaling: root.scaling Layout.alignment: Qt.AlignHCenter } NCircleStat { @@ -33,8 +35,8 @@ NBox { icon: "cpu-temperature" flat: true contentScale: 0.8 - width: 70 * scaling height: 65 * scaling + scaling: root.scaling Layout.alignment: Qt.AlignHCenter } NCircleStat { @@ -42,8 +44,8 @@ NBox { icon: "memory" flat: true contentScale: 0.8 - width: 70 * scaling height: 65 * scaling + scaling: root.scaling Layout.alignment: Qt.AlignHCenter } NCircleStat { @@ -51,8 +53,8 @@ NBox { icon: "storage" flat: true contentScale: 0.8 - width: 70 * scaling height: 65 * scaling + scaling: root.scaling Layout.alignment: Qt.AlignHCenter } } diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 19e6e30a..28e9b49b 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -63,6 +63,7 @@ NPanel { SystemMonitorCard { Layout.preferredWidth: Style.baseWidgetSize * 2.625 * scaling Layout.fillHeight: true + scaling: root.scaling } } @@ -70,6 +71,7 @@ NPanel { AudioCard { Layout.fillWidth: true Layout.preferredHeight: Math.max(120 * scaling) + scaling: root.scaling } // Bottom actions (two grouped rows of round buttons) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index a4ec5701..0ba637df 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -348,15 +348,11 @@ Variants { })() property int vMarginTop: Math.max(Math.round(osdItem.radius), Math.round(Style.marginS * root.scaling)) property int balanceDelta: Math.round(Style.marginS * root.scaling) - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.bottom: parent.bottom + anchors.fill: parent anchors.topMargin: vMarginTop - anchors.bottomMargin: vMargin - width: (function () { - const w = parent.width - (vMargin * 2) - return (w % 2 === 0) ? w : w - 1 - })() + anchors.leftMargin: vMargin + anchors.rightMargin: vMargin + anchors.bottomMargin: 0 spacing: Math.round(Style.marginS * root.scaling) // Percentage text at top @@ -418,7 +414,7 @@ Variants { color: root.getIconColor() pointSize: Style.fontSizeXL * root.scaling Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom - Layout.bottomMargin: vMargin + Math.round(Style.marginM * root.scaling) + balanceDelta + Layout.bottomMargin: Math.round(Style.marginS * root.scaling) Behavior on color { ColorAnimation { duration: Style.animationNormal diff --git a/Widgets/NCircleStat.qml b/Widgets/NCircleStat.qml index f6d4332d..b5cb53a0 100644 --- a/Widgets/NCircleStat.qml +++ b/Widgets/NCircleStat.qml @@ -8,6 +8,7 @@ import qs.Widgets Rectangle { id: root + property real scaling: 1.0 property real value: 0 // 0..100 (or any range visually mapped) property string icon: "" property string suffix: "%" From 28c2352b31532371edd7640f2fbbe351ad7ed3c1 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 18:24:13 +0200 Subject: [PATCH 090/106] OSD: fix centering --- Modules/OSD/OSD.qml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 0ba637df..414aa7f7 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -349,10 +349,10 @@ Variants { property int vMarginTop: Math.max(Math.round(osdItem.radius), Math.round(Style.marginS * root.scaling)) property int balanceDelta: Math.round(Style.marginS * root.scaling) anchors.fill: parent - anchors.topMargin: vMarginTop + anchors.topMargin: vMargin anchors.leftMargin: vMargin anchors.rightMargin: vMargin - anchors.bottomMargin: 0 + anchors.bottomMargin: vMargin spacing: Math.round(Style.marginS * root.scaling) // Percentage text at top @@ -414,7 +414,6 @@ Variants { color: root.getIconColor() pointSize: Style.fontSizeXL * root.scaling Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom - Layout.bottomMargin: Math.round(Style.marginS * root.scaling) Behavior on color { ColorAnimation { duration: Style.animationNormal From c9729789a788ae266fce257fa08b49067362a6f5 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 18:36:39 +0200 Subject: [PATCH 091/106] Launcher: fix signal issue --- Modules/Launcher/Launcher.qml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index f6ec7ae9..1c056b05 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -192,10 +192,8 @@ NPanel { // Reset when launcher opens Connections { target: root - function onOpenedChanged() { - if (root.opened) { - mouseMovementDetector.initialized = false - } + function onOpened() { + mouseMovementDetector.initialized = false } } } From e3582398627d914b28b16f4ba1badb044621330b Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 19:14:23 +0200 Subject: [PATCH 092/106] OSD: fix display logic --- Modules/OSD/OSD.qml | 9 ++++++++- Services/AudioService.qml | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 414aa7f7..dfd9aa56 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -41,6 +41,7 @@ Variants { // Brightness properties property bool brightnessInitialized: false + property int brightnessChangeCount: 0 readonly property real currentBrightness: { if (BrightnessService.monitors.length > 0) { return BrightnessService.monitors[0].brightness || 0 @@ -508,6 +509,8 @@ Variants { muteInitialized = true inputVolumeInitialized = true inputMuteInitialized = true + // Don't initialize brightness here - let it initialize on first change like volume + connectBrightnessMonitors() } } @@ -528,6 +531,7 @@ Variants { } function connectBrightnessMonitors() { + brightnessChangeCount = 0 // Reset change count when reconnecting for (var i = 0; i < BrightnessService.monitors.length; i++) { let monitor = BrightnessService.monitors[i] // Disconnect first to avoid duplicate connections @@ -537,7 +541,10 @@ Variants { } function onBrightnessChanged(newBrightness) { - if (!brightnessInitialized) { + brightnessChangeCount++ + + if (brightnessChangeCount <= BrightnessService.monitors.length) { + // This is likely the initial brightness value(s), don't show OSD brightnessInitialized = true } else { showOSD("brightness") diff --git a/Services/AudioService.qml b/Services/AudioService.qml index f600f9d9..c187c87f 100644 --- a/Services/AudioService.qml +++ b/Services/AudioService.qml @@ -111,6 +111,14 @@ Singleton { } } + function increaseInputVolume() { + setInputVolume(inputVolume + stepVolume) + } + + function decreaseInputVolume() { + setInputVolume(inputVolume - stepVolume) + } + function setInputVolume(newVolume: real) { if (source?.ready && source?.audio) { // Clamp it accordingly From f9c9d00b60d1429e35a47335e6ed4f21204e0e5f Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 19:16:37 +0200 Subject: [PATCH 093/106] OSD: add always on top setting --- Assets/Translations/de.json | 4 ++++ Assets/Translations/en.json | 4 ++++ Assets/Translations/es.json | 4 ++++ Assets/Translations/fr.json | 4 ++++ Assets/Translations/pt.json | 4 ++++ Assets/Translations/zh-CN.json | 4 ++++ Commons/Settings.qml | 1 + Modules/OSD/OSD.qml | 1 + Modules/Settings/Tabs/OsdTab.qml | 7 +++++++ 9 files changed, 33 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 369eee4a..d62d629c 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -426,6 +426,10 @@ "label": "Bildschirmanzeige aktivieren", "description": "Lautstärke- und Helligkeitsänderungen in Echtzeit anzeigen." }, + "always-on-top": { + "label": "Immer im Vordergrund", + "description": "Bildschirmanzeige über Vollbildfenstern und anderen Ebenen anzeigen." + }, "location": { "label": "Position", "description": "Wo Bildschirmanzeigen erscheinen." diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index a90e24a7..30f62d4c 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -426,6 +426,10 @@ "label": "Enable on screen display", "description": "Show volume and brightness changes in real-time." }, + "always-on-top": { + "label": "Always on top", + "description": "Display OSD above fullscreen windows and other layers." + }, "location": { "label": "Location", "description": "Where on-screen displays appear." diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 8c79a64f..ee6c4843 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -426,6 +426,10 @@ "label": "Activar visualización en pantalla", "description": "Mostrar cambios de volumen y brillo en tiempo real." }, + "always-on-top": { + "label": "Siempre encima", + "description": "Mostrar OSD por encima de ventanas de pantalla completa y otras capas." + }, "location": { "label": "Ubicación", "description": "Dónde aparece la visualización en pantalla." diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 32c01982..e79310b5 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -426,6 +426,10 @@ "label": "Activer l'affichage à l'écran", "description": "Afficher en temps réel les changements de volume et de luminosité." }, + "always-on-top": { + "label": "Toujours au premier plan", + "description": "Afficher l'OSD au-dessus des fenêtres plein écran et autres couches." + }, "location": { "label": "Emplacement", "description": "Emplacement des affichages à l'écran." diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 67fa8ead..efe785a6 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -766,6 +766,10 @@ "label": "Ativar exibição na tela", "description": "Mostrar alterações de volume e brilho em tempo real." }, + "always-on-top": { + "label": "Sempre no topo", + "description": "Exibir OSD acima de janelas em tela cheia e outras camadas." + }, "location": { "label": "Localização", "description": "Onde a exibição na tela aparece." diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 9f421d69..2efff789 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -426,6 +426,10 @@ "label": "启用屏幕显示", "description": "实时显示音量与亮度变化。" }, + "always-on-top": { + "label": "始终置顶", + "description": "在全屏窗口和其他图层之上显示OSD。" + }, "location": { "label": "位置", "description": "屏幕显示出现的位置。" diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 8375d67a..18a9286f 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -297,6 +297,7 @@ Singleton { property string location: "top_right" property list monitors: [] property int autoHideMs: 2000 + property bool alwaysOnTop: false } // audio diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index dfd9aa56..aef4ac37 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -204,6 +204,7 @@ Variants { color: Color.transparent WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + WlrLayershell.layer: (Settings.data.osd && Settings.data.osd.alwaysOnTop) ? WlrLayer.Overlay : WlrLayer.Top exclusionMode: PanelWindow.ExclusionMode.Ignore Rectangle { diff --git a/Modules/Settings/Tabs/OsdTab.qml b/Modules/Settings/Tabs/OsdTab.qml index 93e4a733..785fad63 100644 --- a/Modules/Settings/Tabs/OsdTab.qml +++ b/Modules/Settings/Tabs/OsdTab.qml @@ -83,6 +83,13 @@ ColumnLayout { onToggled: checked => Settings.data.osd.enabled = checked } + NToggle { + label: I18n.tr("settings.osd.always-on-top.label") + description: I18n.tr("settings.osd.always-on-top.description") + checked: Settings.data.osd.alwaysOnTop + onToggled: checked => Settings.data.osd.alwaysOnTop = checked + } + NLabel { label: I18n.tr("settings.osd.duration.auto-hide.label") description: I18n.tr("settings.osd.duration.auto-hide.description") From b9e8e8becb164da8704dff347ffce63d3f11f826 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 19:22:12 +0200 Subject: [PATCH 094/106] OSD: possible layout fix, reintroduce scaling --- Modules/OSD/OSD.qml | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index aef4ac37..552d44c3 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -123,6 +123,9 @@ Variants { sourceComponent: PanelWindow { id: panel screen: modelData + + // PanelWindow scaling + property real scaling: ScalingService.getScreenScale(screen) readonly property string location: (Settings.data.osd && Settings.data.osd.location) ? Settings.data.osd.location : "top_right" readonly property bool isTop: (location === "top") || (location.length >= 3 && location.substring(0, 3) === "top") @@ -131,11 +134,12 @@ Variants { readonly property bool isRight: (location.indexOf("_right") >= 0) || (location === "right") readonly property bool isCentered: (location === "top" || location === "bottom") readonly property bool verticalMode: (location === "left" || location === "right") - readonly property int hWidth: Math.round(320 * root.scaling) - readonly property int hHeight: Math.round(64 * root.scaling) + readonly property int hWidth: Math.round(320 * scaling) + readonly property int hHeight: Math.round(64 * scaling) + readonly property int vHeight: Math.round(320 * scaling) // Vertical OSD height (matches horizontal width) // Ensure an even width to keep the vertical bar perfectly centered readonly property int barThickness: (function () { - const base = Math.max(6, Math.round(6 * root.scaling)) + const base = Math.max(8, Math.round(8 * scaling)) return (base % 2 === 0) ? base : base + 1 })() @@ -143,6 +147,15 @@ Variants { connectBrightnessMonitors() } + Connections { + target: ScalingService + function onScaleChanged(screenName, scale) { + if ((screen !== null) && (screenName === screen.name)) { + scaling = scale + } + } + } + Component.onDestruction: { disconnectBrightnessMonitors() } @@ -211,12 +224,12 @@ Variants { id: osdItem width: parent.width - height: panel.verticalMode ? panel.hWidth : Math.round(64 * root.scaling) - radius: Style.radiusL * root.scaling + height: panel.verticalMode ? panel.vHeight : Math.round(64 * scaling) + radius: Style.radiusL * scaling color: Color.mSurface border.color: Color.mOutline border.width: (function () { - const bw = Math.max(2, Math.round(Style.borderM * root.scaling)) + const bw = Math.max(2, Math.round(Style.borderM * scaling)) return (bw % 2 === 0) ? bw : bw + 1 })() visible: false @@ -282,7 +295,7 @@ Variants { NIcon { icon: root.getIcon() color: root.getIconColor() - pointSize: Style.fontSizeXL * root.scaling + pointSize: Style.fontSizeXL * scaling Layout.alignment: Qt.AlignVCenter Behavior on color { @@ -328,7 +341,7 @@ Variants { NText { text: root.getDisplayPercentage() color: Color.mOnSurface - pointSize: Style.fontSizeS * root.scaling + pointSize: Style.fontSizeS * scaling family: Settings.data.ui.fontFixed Layout.alignment: Qt.AlignVCenter horizontalAlignment: Text.AlignLeft @@ -344,18 +357,18 @@ Variants { ColumnLayout { // Ensure inner padding respects the rounded corners; avoid clipping the icon/text property int vMargin: (function () { - const styleMargin = Math.round(Style.marginL * root.scaling) + const styleMargin = Math.round(Style.marginL * scaling) const cornerGuard = Math.round(osdItem.radius) return Math.max(styleMargin, cornerGuard) })() - property int vMarginTop: Math.max(Math.round(osdItem.radius), Math.round(Style.marginS * root.scaling)) - property int balanceDelta: Math.round(Style.marginS * root.scaling) + property int vMarginTop: Math.max(Math.round(osdItem.radius), Math.round(Style.marginS * scaling)) + property int balanceDelta: Math.round(Style.marginS * scaling) anchors.fill: parent anchors.topMargin: vMargin anchors.leftMargin: vMargin anchors.rightMargin: vMargin anchors.bottomMargin: vMargin - spacing: Math.round(Style.marginS * root.scaling) + spacing: Math.round(Style.marginS * scaling) // Percentage text at top Item { @@ -365,7 +378,7 @@ Variants { id: percentText text: root.getDisplayPercentage() color: Color.mOnSurface - pointSize: Style.fontSizeS * root.scaling + pointSize: Style.fontSizeS * scaling family: Settings.data.ui.fontFixed anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter @@ -377,7 +390,7 @@ Variants { // Progress bar Item { Layout.fillWidth: true - Layout.fillHeight: true + Layout.fillHeight: true // Fill remaining space between text and icon Rectangle { anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top @@ -414,7 +427,7 @@ Variants { NIcon { icon: root.getIcon() color: root.getIconColor() - pointSize: Style.fontSizeXL * root.scaling + pointSize: Style.fontSizeXL * scaling Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Behavior on color { ColorAnimation { From a5341691c8c4582347554627bdbe82430dcbccb5 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 19:22:32 +0200 Subject: [PATCH 095/106] Autoformat --- Modules/OSD/OSD.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 552d44c3..6643d88c 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -123,7 +123,7 @@ Variants { sourceComponent: PanelWindow { id: panel screen: modelData - + // PanelWindow scaling property real scaling: ScalingService.getScreenScale(screen) From 26099bb8fb1e211e8c2c7b74e90546d190d90cfe Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 20:10:47 +0200 Subject: [PATCH 096/106] Release v2.17.3 --- Services/UpdateService.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index b826fc69..3689cc1e 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -8,8 +8,8 @@ Singleton { id: root // Public properties - property string baseVersion: "2.17.2" - property bool isDevelopment: true + property string baseVersion: "2.17.3" + property bool isDevelopment: false property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From ce5208fc7c5aeca34a993a127fa4627968381b8e Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 20:13:10 +0200 Subject: [PATCH 097/106] Set version to dev --- Services/UpdateService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index 3689cc1e..c738d6e3 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -9,7 +9,7 @@ Singleton { // Public properties property string baseVersion: "2.17.3" - property bool isDevelopment: false + property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From 83b8f307a1cc5534216bb12691456b59ace1c296 Mon Sep 17 00:00:00 2001 From: Corey Woodworth Date: Sat, 11 Oct 2025 14:51:14 -0400 Subject: [PATCH 098/106] fix: fixed issues caused my my poor merge. removed incorrect OnPaint and moved brackets --- Modules/Bar/Calendar/CalendarPanel.qml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index d1a00070..fb71e723 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -236,26 +236,6 @@ NPanel { var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH") return t.split(" ")[0] } - } - onPaint: { - var ctx = getContext("2d") - var centerX = width / 2 - var centerY = height / 2 - var radius = Math.min(width, height) / 2 - 3 * scaling - ctx.reset() - ctx.beginPath() - ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI) - ctx.lineWidth = 2.5 * scaling - ctx.strokeStyle = Qt.alpha(Color.mOnPrimary, 0.15) - ctx.stroke() - ctx.beginPath() - ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI) - ctx.lineWidth = 2.5 * scaling - ctx.strokeStyle = Color.mOnPrimary - ctx.lineCap = "round" - ctx.stroke() - } - } pointSize: Style.fontSizeXS * scaling font.weight: Style.fontWeightBold From c90fa5fec24f53725a58aa453667bd4b9626acd1 Mon Sep 17 00:00:00 2001 From: lysec Date: Sat, 11 Oct 2025 21:06:33 +0200 Subject: [PATCH 099/106] LockScreen: fix clock hour not updating CalendarPanel: fix clock hour not updating --- Modules/Bar/Calendar/CalendarPanel.qml | 2 +- Modules/LockScreen/LockScreen.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index fb71e723..e8240e84 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -233,7 +233,7 @@ NPanel { NText { text: { - var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH") + var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(Time.date, "hh AP") : Qt.locale().toString(Time.date, "HH") return t.split(" ")[0] } diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 82654abf..0f62b8b9 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -405,7 +405,7 @@ Loader { NText { text: { - var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(new Date(), "hh AP") : Qt.locale().toString(new Date(), "HH") + var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(Time.date, "hh AP") : Qt.locale().toString(Time.date, "HH") return t } pointSize: Style.fontSizeL * scaling From 7cc412289d3f5471787719c67dc03bd04b16c7cb Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sun, 12 Oct 2025 07:52:50 +0800 Subject: [PATCH 100/106] fix: Resolve intermittent OSD brightness display issue --- Modules/OSD/OSD.qml | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 6643d88c..7ca3998d 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -40,14 +40,8 @@ Variants { property bool inputMuteInitialized: false // Brightness properties - property bool brightnessInitialized: false - property int brightnessChangeCount: 0 - readonly property real currentBrightness: { - if (BrightnessService.monitors.length > 0) { - return BrightnessService.monitors[0].brightness || 0 - } - return 0 - } + property real lastUpdatedBrightness: 0 + readonly property real currentBrightness: lastUpdatedBrightness // Get appropriate icon based on current OSD type function getIcon() { @@ -144,7 +138,6 @@ Variants { })() Component.onCompleted: { - connectBrightnessMonitors() } Connections { @@ -157,7 +150,6 @@ Variants { } Component.onDestruction: { - disconnectBrightnessMonitors() } // Anchor selection based on location (window edges) @@ -545,7 +537,6 @@ Variants { } function connectBrightnessMonitors() { - brightnessChangeCount = 0 // Reset change count when reconnecting for (var i = 0; i < BrightnessService.monitors.length; i++) { let monitor = BrightnessService.monitors[i] // Disconnect first to avoid duplicate connections @@ -555,14 +546,8 @@ Variants { } function onBrightnessChanged(newBrightness) { - brightnessChangeCount++ - - if (brightnessChangeCount <= BrightnessService.monitors.length) { - // This is likely the initial brightness value(s), don't show OSD - brightnessInitialized = true - } else { - showOSD("brightness") - } + root.lastUpdatedBrightness = newBrightness + showOSD("brightness") } function showOSD(type) { From 7d37d5dc17861bf9099e98e2f10fed4c607eb209 Mon Sep 17 00:00:00 2001 From: lysec Date: Sun, 12 Oct 2025 10:20:09 +0200 Subject: [PATCH 101/106] OSD: hide initial brightness update (prevent showing brightness osd on startup) --- Modules/OSD/OSD.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 7ca3998d..873933ba 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -42,6 +42,7 @@ Variants { // Brightness properties property real lastUpdatedBrightness: 0 readonly property real currentBrightness: lastUpdatedBrightness + property bool brightnessInitialized: false // Get appropriate icon based on current OSD type function getIcon() { @@ -547,6 +548,12 @@ Variants { function onBrightnessChanged(newBrightness) { root.lastUpdatedBrightness = newBrightness + + if (!brightnessInitialized) { + brightnessInitialized = true + return + } + showOSD("brightness") } From b4a344b0b541598fc63a4c99939d500537b6204f Mon Sep 17 00:00:00 2001 From: lysec Date: Sun, 12 Oct 2025 10:25:52 +0200 Subject: [PATCH 102/106] ControlCenter: fix scaling (pass scaling to everything) --- Modules/ControlCenter/Cards/MediaCard.qml | 2 ++ Modules/ControlCenter/Cards/PowerProfilesCard.qml | 1 + Modules/ControlCenter/Cards/ProfileCard.qml | 1 + Modules/ControlCenter/Cards/UtilitiesCard.qml | 1 + Modules/ControlCenter/Cards/WeatherCard.qml | 1 + Modules/ControlCenter/ControlCenterPanel.qml | 5 +++++ Widgets/NCircleStat.qml | 9 ++++++++- Widgets/NPanel.qml | 12 ++++++++++++ 8 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Modules/ControlCenter/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml index b4d7b7a6..8af05e1e 100644 --- a/Modules/ControlCenter/Cards/MediaCard.qml +++ b/Modules/ControlCenter/Cards/MediaCard.qml @@ -11,6 +11,8 @@ import qs.Widgets NBox { id: root + property real scaling: 1.0 + // Background artwork that covers everything Item { anchors.fill: parent diff --git a/Modules/ControlCenter/Cards/PowerProfilesCard.qml b/Modules/ControlCenter/Cards/PowerProfilesCard.qml index d93b26ba..4e6d6ccf 100644 --- a/Modules/ControlCenter/Cards/PowerProfilesCard.qml +++ b/Modules/ControlCenter/Cards/PowerProfilesCard.qml @@ -10,6 +10,7 @@ import qs.Widgets // Power Profiles: performance, balanced, eco NBox { + property real scaling: 1.0 property real spacing: 0 // Centralized service diff --git a/Modules/ControlCenter/Cards/ProfileCard.qml b/Modules/ControlCenter/Cards/ProfileCard.qml index ec9b4527..c36d57dc 100644 --- a/Modules/ControlCenter/Cards/ProfileCard.qml +++ b/Modules/ControlCenter/Cards/ProfileCard.qml @@ -14,6 +14,7 @@ import qs.Widgets NBox { id: root + property real scaling: 1.0 property string uptimeText: "--" RowLayout { diff --git a/Modules/ControlCenter/Cards/UtilitiesCard.qml b/Modules/ControlCenter/Cards/UtilitiesCard.qml index decd9659..62827b73 100644 --- a/Modules/ControlCenter/Cards/UtilitiesCard.qml +++ b/Modules/ControlCenter/Cards/UtilitiesCard.qml @@ -10,6 +10,7 @@ import qs.Widgets // Utilities: record & wallpaper NBox { + property real scaling: 1.0 property real spacing: 0 RowLayout { diff --git a/Modules/ControlCenter/Cards/WeatherCard.qml b/Modules/ControlCenter/Cards/WeatherCard.qml index 9b7d6aec..702cf76b 100644 --- a/Modules/ControlCenter/Cards/WeatherCard.qml +++ b/Modules/ControlCenter/Cards/WeatherCard.qml @@ -9,6 +9,7 @@ import qs.Widgets NBox { id: root + property real scaling: 1.0 readonly property bool weatherReady: (LocationService.data.weather !== null) ColumnLayout { diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index 28e9b49b..5c7989a3 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -40,11 +40,13 @@ NPanel { ProfileCard { Layout.fillWidth: true Layout.preferredHeight: Math.max(64 * scaling) + scaling: root.scaling } WeatherCard { Layout.fillWidth: true Layout.preferredHeight: Math.max(190 * scaling) + scaling: root.scaling } // Middle section: media + stats column @@ -57,6 +59,7 @@ NPanel { MediaCard { Layout.fillWidth: true Layout.fillHeight: true + scaling: root.scaling } // System monitors combined in one card @@ -85,6 +88,7 @@ NPanel { Layout.fillWidth: true Layout.fillHeight: true spacing: content.cardSpacing + scaling: root.scaling } // Utilities buttons @@ -92,6 +96,7 @@ NPanel { Layout.fillWidth: true Layout.fillHeight: true spacing: content.cardSpacing + scaling: root.scaling } } } diff --git a/Widgets/NCircleStat.qml b/Widgets/NCircleStat.qml index b5cb53a0..1305cec2 100644 --- a/Widgets/NCircleStat.qml +++ b/Widgets/NCircleStat.qml @@ -27,6 +27,13 @@ Rectangle { // Repaint gauge when the bound value changes onValueChanged: gauge.requestPaint() + + // Force repaint when scaling changes + onScalingChanged: { + Qt.callLater(() => { + gauge.requestPaint() + }) + } ColumnLayout { id: mainLayout @@ -46,7 +53,7 @@ Rectangle { Canvas { id: gauge anchors.fill: parent - renderStrategy: Canvas.Cooperative + renderStrategy: Canvas.Immediate onPaint: { const ctx = getContext("2d") diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml index e5fae6cd..e7c4c8a3 100644 --- a/Widgets/NPanel.qml +++ b/Widgets/NPanel.qml @@ -146,6 +146,12 @@ Loader { Logger.log("NPanel", "Opened", root.objectName, "on", screen.name) dimmingOpacity = Style.opacityHeavy root.scaling = scaling = ScalingService.getScreenScale(screen) + + // Force refresh panel content when scaling is applied + Qt.callLater(() => { + panelContentLoader.active = false + panelContentLoader.active = true + }) } Connections { @@ -153,6 +159,12 @@ Loader { function onScaleChanged(screenName, scale) { if ((screen !== null) && (screenName === screen.name)) { root.scaling = scaling = scale + + // Force refresh panel content when scaling changes + Qt.callLater(() => { + panelContentLoader.active = false + panelContentLoader.active = true + }) } } } From d5d654e01055fb3374c1ac63da0d7b85597aad3d Mon Sep 17 00:00:00 2001 From: lysec Date: Sun, 12 Oct 2025 11:04:35 +0200 Subject: [PATCH 103/106] LockScreen: add compact mode toggle in GeneralTab Settings: add lockscreen compact mode setting i18n: add translations --- Assets/Translations/de.json | 4 ++ Assets/Translations/en.json | 4 ++ Assets/Translations/es.json | 4 ++ Assets/Translations/fr.json | 4 ++ Assets/Translations/pt.json | 4 ++ Assets/Translations/zh-CN.json | 4 ++ Commons/Settings.qml | 1 + Modules/LockScreen/LockScreen.qml | 101 ++++++++++++++++++++++----- Modules/Settings/Tabs/GeneralTab.qml | 7 ++ 9 files changed, 114 insertions(+), 19 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index d62d629c..16d3042d 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -26,6 +26,10 @@ "label": "Desktop abdunkeln", "description": "Desktop abdunkeln, wenn Panels oder Menüs geöffnet sind." }, + "compact-lockscreen": { + "label": "Kompakter Sperrbildschirm", + "description": "Zeigt nur die Anmeldeeingabe und Systemsteuerungen an, versteckt Wetter- und Medien-Widgets." + }, "border-radius": { "label": "Eckenradius", "description": "Steuert die Rundung der Ecken von Fenstern, Buttons und anderen Elementen." diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 30f62d4c..41800513 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -26,6 +26,10 @@ "label": "Dim desktop", "description": "Dim the desktop when panels or menus are open." }, + "compact-lockscreen": { + "label": "Compact lock screen", + "description": "Show only the login input and system controls, hiding weather and media widgets." + }, "border-radius": { "label": "Border radius", "description": "Controls the corner roundness of windows, buttons, and other elements." diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index ee6c4843..6602519f 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -26,6 +26,10 @@ "label": "Atenuar escritorio", "description": "Atenúa el escritorio cuando los paneles o menús están abiertos." }, + "compact-lockscreen": { + "label": "Pantalla de bloqueo compacta", + "description": "Muestra solo la entrada de inicio de sesión y controles del sistema, ocultando widgets de clima y medios." + }, "border-radius": { "label": "Radio del borde", "description": "Controla la redondez de las esquinas de ventanas, botones y otros elementos." diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index e79310b5..d62cace9 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -26,6 +26,10 @@ "label": "Assombrir le bureau", "description": "Assombrir le bureau lorsque des panneaux ou des menus sont ouverts." }, + "compact-lockscreen": { + "label": "Écran de verrouillage compact", + "description": "Affiche uniquement la saisie de connexion et les contrôles système, masquant les widgets météo et média." + }, "border-radius": { "label": "Rayon de bordure", "description": "Contrôle l'arrondi des coins des fenêtres, des boutons et d'autres éléments." diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index efe785a6..25522957 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -26,6 +26,10 @@ "label": "Escurecer área de trabalho", "description": "Escurece a área de trabalho quando painéis ou menus estão abertos." }, + "compact-lockscreen": { + "label": "Tela de bloqueio compacta", + "description": "Mostra apenas a entrada de login e controles do sistema, ocultando widgets de clima e mídia." + }, "border-radius": { "label": "Raio da borda", "description": "Controla o arredondamento dos cantos de janelas, botões e outros elementos." diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 2efff789..3bbe1d89 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -26,6 +26,10 @@ "label": "调暗桌面", "description": "当面板或菜单打开时调暗桌面。" }, + "compact-lockscreen": { + "label": "紧凑锁屏", + "description": "仅显示登录输入和系统控件,隐藏天气和媒体小部件。" + }, "border-radius": { "label": "边框圆角", "description": "控制窗口、按钮及其他元素的边角圆度。" diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 18a9286f..af6c0598 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -187,6 +187,7 @@ Singleton { property real screenRadiusRatio: 1.0 property real animationSpeed: 1.0 property bool animationDisabled: false + property bool compactLockScreen: false } // location diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 0f62b8b9..a4f1d16d 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -253,7 +253,7 @@ Loader { anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 100 * scaling - radius: 32 * scaling + radius: Style.radiusL * scaling color: Color.mSurface border.color: Qt.alpha(Color.mOutline, 0.2) border.width: 1 @@ -317,11 +317,6 @@ Loader { } } - // Spacer to center the text section - Item { - Layout.fillWidth: true - } - // Center: User Info Column (left-aligned text) ColumnLayout { Layout.alignment: Qt.AlignVCenter @@ -346,12 +341,12 @@ Loader { } } - // Spacer to push cool time to the right + + // Spacer to push time to the right Item { Layout.fillWidth: true } - // Right side: Cool Time (from Calendar) Item { Layout.preferredWidth: 70 * scaling Layout.preferredHeight: 70 * scaling @@ -469,17 +464,82 @@ Loader { } } + // Compact status indicators container (compact mode only) + Rectangle { + width: { + var hasBattery = UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent + var hasKeyboard = keyboardLayout.currentLayout !== "Unknown" + + if (hasBattery && hasKeyboard) { + return 200 * scaling + } else if (hasBattery || hasKeyboard) { + return 120 * scaling + } else { + return 0 + } + } + height: 40 * scaling + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 96 * scaling + (Settings.data.general.compactLockScreen ? 116 * scaling : 220 * scaling) + topLeftRadius: Style.radiusL * scaling + topRightRadius: Style.radiusL * scaling + color: Color.mSurface + visible: Settings.data.general.compactLockScreen && ((UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent) || keyboardLayout.currentLayout !== "Unknown") + + RowLayout { + anchors.centerIn: parent + spacing: 16 * scaling + + // Battery indicator + RowLayout { + spacing: 6 * scaling + visible: UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent + + NIcon { + icon: BatteryService.getIcon(Math.round(UPower.displayDevice.percentage * 100), UPower.displayDevice.state === UPowerDeviceState.Charging, true) + pointSize: Style.fontSizeM * scaling + color: UPower.displayDevice.state === UPowerDeviceState.Charging ? Color.mPrimary : Color.mOnSurfaceVariant + } + + NText { + text: Math.round(UPower.displayDevice.percentage * 100) + "%" + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeM * scaling + font.weight: Font.Medium + } + } + + // Keyboard layout indicator + RowLayout { + spacing: 6 * scaling + visible: keyboardLayout.currentLayout !== "Unknown" + + NIcon { + icon: "keyboard" + pointSize: Style.fontSizeM * scaling + color: Color.mOnSurfaceVariant + } + + NText { + text: keyboardLayout.currentLayout + color: Color.mOnSurfaceVariant + pointSize: Style.fontSizeM * scaling + font.weight: Font.Medium + } + } + } + } + // Bottom container with weather, password input and controls Rectangle { width: 750 * scaling - height: 220 * scaling + height: Settings.data.general.compactLockScreen ? 120 * scaling : 220 * scaling anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: 100 * scaling - radius: 32 * scaling + radius: Style.radiusL * scaling color: Color.mSurface - border.color: Qt.alpha(Color.mOutline, 0.2) - border.width: 1 ColumnLayout { anchors.fill: parent @@ -491,7 +551,7 @@ Loader { Layout.fillWidth: true Layout.preferredHeight: 65 * scaling spacing: 18 * scaling - visible: LocationService.coordinatesReady && LocationService.data.weather !== null + visible: !Settings.data.general.compactLockScreen && LocationService.coordinatesReady && LocationService.data.weather !== null // Media widget with visualizer Rectangle { @@ -695,14 +755,13 @@ Loader { } } - // Battery and Keyboard Layout - ColumnLayout { + // Battery and Keyboard Layout (full mode only) + RowLayout { Layout.preferredWidth: 60 * scaling spacing: 4 * scaling + // Battery RowLayout { - Layout.preferredWidth: 60 * scaling - Layout.preferredHeight: 22 * scaling spacing: 4 * scaling visible: UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent @@ -720,10 +779,10 @@ Loader { } } + // Keyboard Layout RowLayout { - Layout.preferredWidth: 60 * scaling - Layout.preferredHeight: 22 * scaling spacing: 4 * scaling + visible: keyboardLayout.currentLayout !== "Unknown" NIcon { icon: "keyboard" @@ -739,8 +798,10 @@ Loader { } } } + } + // Password input RowLayout { Layout.fillWidth: true @@ -750,6 +811,7 @@ Loader { Layout.preferredWidth: Style.marginM * scaling } + Rectangle { Layout.fillWidth: true Layout.preferredHeight: 48 * scaling @@ -894,6 +956,7 @@ Loader { } } + Item { Layout.preferredWidth: Style.marginM * scaling } diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index 4f9183a1..558a8860 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -89,6 +89,13 @@ ColumnLayout { onToggled: checked => Settings.data.ui.tooltipsEnabled = checked } + NToggle { + label: I18n.tr("settings.general.ui.compact-lockscreen.label") + description: I18n.tr("settings.general.ui.compact-lockscreen.description") + checked: Settings.data.general.compactLockScreen + onToggled: checked => Settings.data.general.compactLockScreen = checked + } + ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true From 14af84ffbe3b944d589519dff72996d6bd527f16 Mon Sep 17 00:00:00 2001 From: lysec Date: Sun, 12 Oct 2025 16:01:31 +0200 Subject: [PATCH 104/106] LockScreen: make compact version buttons not overflow & edit clock CalendarPanel: edit clock Autoformat --- Modules/Bar/Calendar/CalendarPanel.qml | 10 +++-- Modules/LockScreen/LockScreen.qml | 56 ++++++++++++++++---------- Modules/OSD/OSD.qml | 6 ++- Widgets/NCircleStat.qml | 6 +-- Widgets/NPanel.qml | 16 ++++---- 5 files changed, 56 insertions(+), 38 deletions(-) diff --git a/Modules/Bar/Calendar/CalendarPanel.qml b/Modules/Bar/Calendar/CalendarPanel.qml index e8240e84..3b1b26a8 100644 --- a/Modules/Bar/Calendar/CalendarPanel.qml +++ b/Modules/Bar/Calendar/CalendarPanel.qml @@ -10,6 +10,8 @@ import qs.Widgets NPanel { id: root + readonly property var now: Time.date + preferredWidth: Settings.data.location.showWeekNumberInCalendar ? 400 : 380 preferredHeight: 520 @@ -193,12 +195,12 @@ NPanel { Canvas { id: secondsProgress anchors.fill: parent - property real progress: Time.date.getSeconds() / 60 + property real progress: now.getSeconds() / 60 onProgressChanged: requestPaint() Connections { target: Time function onDateChanged() { - const total = Time.date.getSeconds() * 1000 + Time.date.getMilliseconds() + const total = now.getSeconds() * 1000 + now.getMilliseconds() secondsProgress.progress = total / 60000 } } @@ -233,7 +235,7 @@ NPanel { NText { text: { - var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(Time.date, "hh AP") : Qt.locale().toString(Time.date, "HH") + var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(now, "hh AP") : Qt.locale().toString(now, "HH") return t.split(" ")[0] } @@ -245,7 +247,7 @@ NPanel { } NText { - text: Qt.formatTime(Time.date, "mm") + text: Qt.formatTime(now, "mm") pointSize: Style.fontSizeXXS * scaling font.weight: Style.fontWeightBold color: Color.mOnPrimary diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index a4f1d16d..1cb4a532 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -50,6 +50,7 @@ Loader { WlSessionLockSurface { readonly property real scaling: ScalingService.dynamicScale(screen) + readonly property var now: Time.date Item { id: batteryIndicator @@ -341,7 +342,6 @@ Loader { } } - // Spacer to push time to the right Item { Layout.fillWidth: true @@ -469,7 +469,7 @@ Loader { width: { var hasBattery = UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent var hasKeyboard = keyboardLayout.currentLayout !== "Unknown" - + if (hasBattery && hasKeyboard) { return 200 * scaling } else if (hasBattery || hasKeyboard) { @@ -729,7 +729,7 @@ Loader { spacing: 3 * scaling NText { - text: Qt.locale().toString(new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")), "ddd") + text: Qt.locale().toString(now, "ddd") pointSize: Style.fontSizeM * scaling color: Color.mOnSurfaceVariant horizontalAlignment: Text.AlignHCenter @@ -798,10 +798,8 @@ Loader { } } } - } - // Password input RowLayout { Layout.fillWidth: true @@ -811,7 +809,6 @@ Loader { Layout.preferredWidth: Style.marginM * scaling } - Rectangle { Layout.fillWidth: true Layout.preferredHeight: 48 * scaling @@ -956,7 +953,6 @@ Loader { } } - Item { Layout.preferredWidth: Style.marginM * scaling } @@ -965,7 +961,7 @@ Loader { // System control buttons RowLayout { Layout.fillWidth: true - Layout.preferredHeight: 48 * scaling + Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 * scaling : 48 * scaling spacing: 10 * scaling Item { @@ -974,9 +970,11 @@ Loader { Rectangle { Layout.fillWidth: true - Layout.preferredHeight: 48 * scaling - radius: 24 * scaling + Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 * scaling : 48 * scaling + radius: Settings.data.general.compactLockScreen ? 18 * scaling : 24 * scaling color: logoutButtonArea.containsMouse ? Color.mTertiary : "transparent" + border.color: Color.mOutline + border.width: 1 RowLayout { anchors.centerIn: parent @@ -984,14 +982,14 @@ Loader { NIcon { icon: "logout" - pointSize: Style.fontSizeL * scaling + pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM * scaling : Style.fontSizeL * scaling color: logoutButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant } NText { text: I18n.tr("session-menu.logout") color: logoutButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant - pointSize: Style.fontSizeM * scaling + pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS * scaling : Style.fontSizeM * scaling font.weight: Font.Medium } } @@ -1009,13 +1007,22 @@ Loader { easing.type: Easing.OutCubic } } + + Behavior on border.color { + ColorAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + } } Rectangle { Layout.fillWidth: true - Layout.preferredHeight: 48 * scaling - radius: 24 * scaling + Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 * scaling : 48 * scaling + radius: Settings.data.general.compactLockScreen ? 18 * scaling : 24 * scaling color: rebootButtonArea.containsMouse ? Color.mTertiary : "transparent" + border.color: Color.mOutline + border.width: 1 RowLayout { anchors.centerIn: parent @@ -1023,14 +1030,14 @@ Loader { NIcon { icon: "reboot" - pointSize: Style.fontSizeL * scaling + pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM * scaling : Style.fontSizeL * scaling color: rebootButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant } NText { text: I18n.tr("session-menu.reboot") color: rebootButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant - pointSize: Style.fontSizeM * scaling + pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS * scaling : Style.fontSizeM * scaling font.weight: Font.Medium } } @@ -1048,14 +1055,21 @@ Loader { easing.type: Easing.OutCubic } } + + Behavior on border.color { + ColorAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + } } Rectangle { Layout.fillWidth: true - Layout.preferredHeight: 48 * scaling - radius: 24 * scaling + Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 * scaling : 48 * scaling + radius: Settings.data.general.compactLockScreen ? 18 * scaling : 24 * scaling color: shutdownButtonArea.containsMouse ? Color.mError : "transparent" - border.color: shutdownButtonArea.containsMouse ? Color.mError : Color.transparent + border.color: shutdownButtonArea.containsMouse ? Color.mError : Color.mOutline border.width: 1 RowLayout { @@ -1064,14 +1078,14 @@ Loader { NIcon { icon: "shutdown" - pointSize: Style.fontSizeL * scaling + pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM * scaling : Style.fontSizeL * scaling color: shutdownButtonArea.containsMouse ? Color.mOnError : Color.mOnSurfaceVariant } NText { text: I18n.tr("session-menu.shutdown") color: shutdownButtonArea.containsMouse ? Color.mOnError : Color.mOnSurfaceVariant - pointSize: Style.fontSizeM * scaling + pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS * scaling : Style.fontSizeM * scaling font.weight: Font.Medium } } diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 873933ba..12b261af 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -139,6 +139,7 @@ Variants { })() Component.onCompleted: { + } Connections { @@ -151,6 +152,7 @@ Variants { } Component.onDestruction: { + } // Anchor selection based on location (window edges) @@ -548,12 +550,12 @@ Variants { function onBrightnessChanged(newBrightness) { root.lastUpdatedBrightness = newBrightness - + if (!brightnessInitialized) { brightnessInitialized = true return } - + showOSD("brightness") } diff --git a/Widgets/NCircleStat.qml b/Widgets/NCircleStat.qml index 1305cec2..a0650692 100644 --- a/Widgets/NCircleStat.qml +++ b/Widgets/NCircleStat.qml @@ -27,12 +27,12 @@ Rectangle { // Repaint gauge when the bound value changes onValueChanged: gauge.requestPaint() - + // Force repaint when scaling changes onScalingChanged: { Qt.callLater(() => { - gauge.requestPaint() - }) + gauge.requestPaint() + }) } ColumnLayout { diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml index e7c4c8a3..acef644e 100644 --- a/Widgets/NPanel.qml +++ b/Widgets/NPanel.qml @@ -146,12 +146,12 @@ Loader { Logger.log("NPanel", "Opened", root.objectName, "on", screen.name) dimmingOpacity = Style.opacityHeavy root.scaling = scaling = ScalingService.getScreenScale(screen) - + // Force refresh panel content when scaling is applied Qt.callLater(() => { - panelContentLoader.active = false - panelContentLoader.active = true - }) + panelContentLoader.active = false + panelContentLoader.active = true + }) } Connections { @@ -159,12 +159,12 @@ Loader { function onScaleChanged(screenName, scale) { if ((screen !== null) && (screenName === screen.name)) { root.scaling = scaling = scale - + // Force refresh panel content when scaling changes Qt.callLater(() => { - panelContentLoader.active = false - panelContentLoader.active = true - }) + panelContentLoader.active = false + panelContentLoader.active = true + }) } } } From 6da4acee09d8ec7780089cd0710001badf20c841 Mon Sep 17 00:00:00 2001 From: lysec Date: Sun, 12 Oct 2025 17:51:07 +0200 Subject: [PATCH 105/106] Dock, Tray, ActiveWindow, Taskbar: add theming for app/tray icons appicon_colorize: create simple shader to colorize icons by theme color --- Assets/Translations/de.json | 18 +++++++++++ Assets/Translations/en.json | 18 +++++++++++ Assets/Translations/es.json | 18 +++++++++++ Assets/Translations/fr.json | 18 +++++++++++ Assets/Translations/pt.json | 18 +++++++++++ Assets/Translations/zh-CN.json | 18 +++++++++++ Assets/settings-default.json | 3 +- Commons/Settings.qml | 1 + Modules/Bar/Widgets/ActiveWindow.qml | 18 +++++++++++ Modules/Bar/Widgets/Taskbar.qml | 9 ++++++ Modules/Bar/Widgets/Tray.qml | 8 +++++ Modules/Dock/Dock.qml | 9 ++++++ .../WidgetSettings/ActiveWindowSettings.qml | 10 ++++++ .../Bar/WidgetSettings/TaskbarSettings.qml | 10 ++++++ .../Bar/WidgetSettings/TraySettings.qml | 13 +++++++- Modules/Settings/Tabs/DockTab.qml | 14 +++++++++ Services/BarWidgetRegistry.qml | 9 ++++-- Shaders/frag/appicon_colorize.frag | 29 ++++++++++++++++++ Shaders/qsb/appicon_colorize.frag.qsb | Bin 0 -> 1957 bytes 19 files changed, 236 insertions(+), 5 deletions(-) create mode 100644 Shaders/frag/appicon_colorize.frag create mode 100644 Shaders/qsb/appicon_colorize.frag.qsb diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 16d3042d..b97c91b8 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -318,6 +318,10 @@ "floating-distance": { "label": "Dock-Schwebeabstand", "description": "Schwebeabstand vom Bildschirmrand anpassen." + }, + "colorize-icons": { + "label": "Symbole einfärben", + "description": "Theme-Farben auf Dock-App-Symbole anwenden (nur nicht fokussierte Apps)." } }, "monitors": { @@ -929,6 +933,10 @@ "width": { "description": "Steuert die horizontale Größe des Widgets.", "label": "Widget-Breite" + }, + "colorize-icons": { + "label": "Symbole einfärben", + "description": "Theme-Farben auf das aktive Fenster-Symbol anwenden." } }, "system-monitor": { @@ -1122,6 +1130,16 @@ "only-same-output": { "label": "Nur vom gleichen Bildschirm", "description": "Zeige nur Apps vom dem Bildschirm an, wo sich die Taskbar befindet." + }, + "colorize-icons": { + "label": "Symbole einfärben", + "description": "Theme-Farben auf Taskbar-Symbole anwenden." + } + }, + "tray": { + "colorize-icons": { + "label": "Symbole einfärben", + "description": "Theme-Farben auf Tray-Symbole anwenden." } } } diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 41800513..1a0409a2 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -318,6 +318,10 @@ "floating-distance": { "label": "Dock floating distance", "description": "Adjust the floating distance from the screen edge." + }, + "colorize-icons": { + "label": "Colorize Icons", + "description": "Apply theme colors to dock app icons (non-focused apps only)." } }, "monitors": { @@ -912,6 +916,10 @@ "width": { "label": "Widget Width", "description": "Controls the horizontal size of the widget." + }, + "colorize-icons": { + "label": "Colorize Icons", + "description": "Apply theme colors to active window icon." } }, "system-monitor": { @@ -1105,6 +1113,16 @@ "only-same-output": { "label": "Only from same output", "description": "Show only apps from the output where the bar is located." + }, + "colorize-icons": { + "label": "Colorize Icons", + "description": "Apply theme colors to taskbar icons." + } + }, + "tray": { + "colorize-icons": { + "label": "Colorize Icons", + "description": "Apply theme colors to tray icons." } } } diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 6602519f..10aa1ce5 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -318,6 +318,10 @@ "floating-distance": { "label": "Distancia de flotación del dock", "description": "Ajusta la distancia de flotación desde el borde de la pantalla." + }, + "colorize-icons": { + "label": "Colorear iconos", + "description": "Aplicar colores del tema a los iconos de aplicaciones del dock (solo aplicaciones no enfocadas)." } }, "monitors": { @@ -912,6 +916,10 @@ "width": { "description": "Controla el tamaño horizontal del widget.", "label": "Ancho del widget" + }, + "colorize-icons": { + "label": "Colorear iconos", + "description": "Aplicar colores del tema al icono de la ventana activa." } }, "system-monitor": { @@ -1105,6 +1113,16 @@ "only-same-output": { "description": "Muestra solo las aplicaciones del resultado donde se encuentra la barra.", "label": "Solo de la misma salida" + }, + "colorize-icons": { + "label": "Colorear iconos", + "description": "Aplicar colores del tema a los iconos de la barra de tareas." + } + }, + "tray": { + "colorize-icons": { + "label": "Colorear iconos", + "description": "Aplicar colores del tema a los iconos de la bandeja del sistema." } } } diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index d62cace9..004a1224 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -318,6 +318,10 @@ "floating-distance": { "label": "Distance de flottaison du dock", "description": "Ajustez la distance de flottaison par rapport au bord de l'écran." + }, + "colorize-icons": { + "label": "Coloriser les icônes", + "description": "Appliquer les couleurs du thème aux icônes d'applications du dock (applications non focalisées uniquement)." } }, "monitors": { @@ -912,6 +916,10 @@ "width": { "description": "Contrôle la taille horizontale du widget.", "label": "Largeur du widget" + }, + "colorize-icons": { + "label": "Coloriser les icônes", + "description": "Appliquer les couleurs du thème à l'icône de la fenêtre active." } }, "system-monitor": { @@ -1105,6 +1113,16 @@ "only-same-output": { "description": "Afficher uniquement les applications de la sortie où la barre est située.", "label": "Seulement à partir de la même sortie" + }, + "colorize-icons": { + "label": "Coloriser les icônes", + "description": "Appliquer les couleurs du thème aux icônes de la barre des tâches." + } + }, + "tray": { + "colorize-icons": { + "label": "Coloriser les icônes", + "description": "Appliquer les couleurs du thème aux icônes de la barre système." } } } diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 25522957..03b3d37a 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -318,6 +318,10 @@ "floating-distance": { "label": "Distância de flutuação da dock", "description": "Ajuste a distância de flutuação da borda da tela." + }, + "colorize-icons": { + "label": "Colorir ícones", + "description": "Aplicar cores do tema aos ícones de aplicativos da dock (apenas aplicativos não focados)." } }, "monitors": { @@ -912,6 +916,10 @@ "width": { "description": "Controla o tamanho horizontal do widget.", "label": "Largura do Widget" + }, + "colorize-icons": { + "label": "Colorir ícones", + "description": "Aplicar cores do tema ao ícone da janela ativa." } }, "system-monitor": { @@ -1105,6 +1113,16 @@ "only-same-output": { "description": "Mostrar apenas os aplicativos da saída onde a barra está localizada.", "label": "Apenas da mesma saída" + }, + "colorize-icons": { + "label": "Colorir ícones", + "description": "Aplicar cores do tema aos ícones da barra de tarefas." + } + }, + "tray": { + "colorize-icons": { + "label": "Colorir ícones", + "description": "Aplicar cores do tema aos ícones da bandeja do sistema." } } } diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 3bbe1d89..609bdb31 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -318,6 +318,10 @@ "floating-distance": { "label": "Dock 浮动距离", "description": "调整距离屏幕边缘的浮动距离。" + }, + "colorize-icons": { + "label": "着色图标", + "description": "将主题颜色应用到 Dock 应用图标(仅限非聚焦应用)。" } }, "monitors": { @@ -912,6 +916,10 @@ "width": { "description": "控制小部件的水平尺寸。", "label": "小部件宽度" + }, + "colorize-icons": { + "label": "着色图标", + "description": "将主题颜色应用到活动窗口图标。" } }, "system-monitor": { @@ -1105,6 +1113,16 @@ "only-same-output": { "label": "仅显示同屏幕", "description": "仅显示任务栏所在屏幕上的应用程序" + }, + "colorize-icons": { + "label": "着色图标", + "description": "将主题颜色应用到任务栏图标。" + } + }, + "tray": { + "colorize-icons": { + "label": "着色图标", + "description": "将主题颜色应用到系统托盘图标。" } } } diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 9b63d3ae..55d8bf07 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -138,7 +138,8 @@ "floatingRatio": 1, "onlySameOutput": true, "monitors": [], - "pinnedApps": [] + "pinnedApps": [], + "colorizeIcons": false }, "network": { "wifiEnabled": true diff --git a/Commons/Settings.qml b/Commons/Settings.qml index af6c0598..676c4575 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -272,6 +272,7 @@ Singleton { property list monitors: [] // Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop") property list pinnedApps: [] + property bool colorizeIcons: false } // network diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index de10760d..817d7cc4 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -149,6 +149,15 @@ Item { asynchronous: true smooth: true visible: source !== "" + + // Apply dock shader to active window icon (always themed) + layer.enabled: widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Color.mOnSurface + property real colorizeMode: 0.0 // Dock mode (grayscale) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } } } @@ -315,6 +324,15 @@ Item { asynchronous: true smooth: true visible: source !== "" + + // Apply dock shader to active window icon (always themed) + layer.enabled: widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Color.mOnSurface + property real colorizeMode: 0.0 // Dock mode (grayscale) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } } } } diff --git a/Modules/Bar/Widgets/Taskbar.qml b/Modules/Bar/Widgets/Taskbar.qml index e9035002..6f1782f3 100644 --- a/Modules/Bar/Widgets/Taskbar.qml +++ b/Modules/Bar/Widgets/Taskbar.qml @@ -81,6 +81,15 @@ Rectangle { asynchronous: true opacity: modelData.isFocused ? Style.opacityFull : 0.6 + // Apply dock shader to all taskbar icons + layer.enabled: widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Color.mOnSurface + property real colorizeMode: 0.0 // Dock mode (grayscale) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + Rectangle { anchors.bottomMargin: -2 * scaling anchors.bottom: parent.bottom diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 0365cc2e..5237a82e 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -193,6 +193,14 @@ Rectangle { } opacity: status === Image.Ready ? 1 : 0 + layer.enabled: widgetSettings.colorizeIcons !== false + layer.effect: ShaderEffect { + property color targetColor: Color.mOnSurface + property real colorizeMode: 1.0 // Tray mode (intensity-based) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + MouseArea { anchors.fill: parent hoverEnabled: true diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index b23c90bc..c0dd0a5a 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -401,6 +401,15 @@ Variants { scale: appButton.hovered ? 1.15 : 1.0 + // Apply dock-specific colorization shader only to non-focused apps + layer.enabled: !appButton.isActive && Settings.data.dock.colorizeIcons + layer.effect: ShaderEffect { + property color targetColor: Color.mOnSurface + property real colorizeMode: 0.0 // Dock mode (grayscale) + + fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/appicon_colorize.frag.qsb") + } + Behavior on scale { NumberAnimation { duration: Style.animationNormal diff --git a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml index 3d1be163..81184595 100644 --- a/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml @@ -18,6 +18,7 @@ ColumnLayout { property bool valueAutoHide: widgetData.autoHide !== undefined ? widgetData.autoHide : widgetMetadata.autoHide property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode property int valueWidth: widgetData.width !== undefined ? widgetData.width : widgetMetadata.width + property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons function saveSettings() { var settings = Object.assign({}, widgetData || {}) @@ -25,6 +26,7 @@ ColumnLayout { settings.showIcon = valueShowIcon settings.scrollingMode = valueScrollingMode settings.width = parseInt(widthInput.text) || widgetMetadata.width + settings.colorizeIcons = valueColorizeIcons return settings } @@ -44,6 +46,14 @@ ColumnLayout { onToggled: checked => root.valueShowIcon = checked } + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.active-window.colorize-icons.label") + description: I18n.tr("bar.widget-settings.active-window.colorize-icons.description") + checked: root.valueColorizeIcons + onToggled: checked => root.valueColorizeIcons = checked + } + NTextInput { id: widthInput Layout.fillWidth: true diff --git a/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml b/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml index c80dd09f..5ff3b418 100644 --- a/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/TaskbarSettings.qml @@ -16,11 +16,13 @@ ColumnLayout { // Local state property bool valueOnlyActiveWorkspaces: widgetData.onlyActiveWorkspaces !== undefined ? widgetData.onlyActiveWorkspaces : widgetMetadata.onlyActiveWorkspaces property bool valueOnlySameOutput: widgetData.onlySameOutput !== undefined ? widgetData.onlySameOutput : widgetMetadata.onlySameOutput + property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons function saveSettings() { var settings = Object.assign({}, widgetData || {}) settings.onlySameOutput = valueOnlySameOutput settings.onlyActiveWorkspaces = valueOnlyActiveWorkspaces + settings.colorizeIcons = valueColorizeIcons return settings } @@ -39,4 +41,12 @@ ColumnLayout { checked: root.valueOnlyActiveWorkspaces onToggled: checked => root.valueOnlyActiveWorkspaces = checked } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.taskbar.colorize-icons.label") + description: I18n.tr("bar.widget-settings.taskbar.colorize-icons.description") + checked: root.valueColorizeIcons + onToggled: checked => root.valueColorizeIcons = checked + } } diff --git a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml index 25d08bce..d1f38261 100644 --- a/Modules/Settings/Bar/WidgetSettings/TraySettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/TraySettings.qml @@ -5,12 +5,14 @@ import qs.Commons import qs.Widgets ColumnLayout { + id: root // Properties to receive data from parent property var widgetData: ({}) // Expected by BarWidgetSettingsDialog property var widgetMetadata: ({}) // Expected by BarWidgetSettingsDialog - // Local state for the blacklist + // Local state property var localBlacklist: widgetData.blacklist || [] + property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons ListModel { id: blacklistModel @@ -27,6 +29,14 @@ ColumnLayout { spacing: Style.marginM * scaling + NToggle { + Layout.fillWidth: true + label: I18n.tr("bar.widget-settings.tray.colorize-icons.label") + description: I18n.tr("bar.widget-settings.tray.colorize-icons.description") + checked: root.valueColorizeIcons + onToggled: checked => root.valueColorizeIcons = checked + } + ColumnLayout { Layout.fillWidth: true spacing: Style.marginS * scaling @@ -135,6 +145,7 @@ ColumnLayout { // Return the updated settings for this widget instance var settings = Object.assign({}, widgetData || {}) settings.blacklist = newBlacklist + settings.colorizeIcons = root.valueColorizeIcons return settings } } diff --git a/Modules/Settings/Tabs/DockTab.qml b/Modules/Settings/Tabs/DockTab.qml index 5d31c074..d272aacf 100644 --- a/Modules/Settings/Tabs/DockTab.qml +++ b/Modules/Settings/Tabs/DockTab.qml @@ -93,6 +93,20 @@ ColumnLayout { Layout.bottomMargin: Style.marginXL * scaling } + NToggle { + Layout.fillWidth: true + label: I18n.tr("settings.dock.appearance.colorize-icons.label") + description: I18n.tr("settings.dock.appearance.colorize-icons.description") + checked: Settings.data.dock.colorizeIcons + onToggled: checked => Settings.data.dock.colorizeIcons = checked + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + // Monitor Configuration ColumnLayout { spacing: Style.marginM * scaling diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index efb85537..f2809e14 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -43,7 +43,8 @@ Singleton { "showIcon": true, "autoHide": false, "scrollingMode": "hover", - "width": 145 + "width": 145, + "colorizeIcons": false }, "Battery": { "allowUserSettings": true, @@ -114,11 +115,13 @@ Singleton { "Taskbar": { "allowUserSettings": true, "onlySameOutput": true, - "onlyActiveWorkspaces": true + "onlyActiveWorkspaces": true, + "colorizeIcons": false }, "Tray": { "allowUserSettings": true, - "blacklist": [] + "blacklist": [], + "colorizeIcons": false }, "Workspace": { "allowUserSettings": true, diff --git a/Shaders/frag/appicon_colorize.frag b/Shaders/frag/appicon_colorize.frag new file mode 100644 index 00000000..ae108701 --- /dev/null +++ b/Shaders/frag/appicon_colorize.frag @@ -0,0 +1,29 @@ +#version 450 +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; +layout(binding = 1) uniform sampler2D source; +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + vec4 targetColor; + float colorizeMode; // 0.0 = dock mode (grayscale), 1.0 = tray mode (intensity) +} ubuf; + +void main() { + vec4 tex = texture(source, qt_TexCoord0); + + float intensity; + + if (ubuf.colorizeMode < 0.5) { + // Dock mode: Convert to grayscale using proper luminance weights + intensity = dot(tex.rgb, vec3(0.299, 0.587, 0.114)); + } else { + // Tray mode: Use the maximum RGB channel value as intensity + intensity = max(max(tex.r, tex.g), tex.b); + + // Normalize intensity to make all icons more uniform + intensity = smoothstep(0.1, 0.9, intensity); + } + + fragColor = vec4(ubuf.targetColor.rgb * intensity, tex.a) * ubuf.qt_Opacity; +} diff --git a/Shaders/qsb/appicon_colorize.frag.qsb b/Shaders/qsb/appicon_colorize.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..15618b86dc21605375f53137209ccf520f623aa7 GIT binary patch literal 1957 zcmV;W2U_?502|48ob6fNbKAxdU-3hfVK+`3N4A?bIa^5zIw}M3o1#fMk?h#16*-Y6 z?F>i5KqNq;B>@IFIFvJb?pvQao#|`;iv9(8O8b^SBCmOA+L=x}d$%BukSwc7C!+`7 z3<>Pse*AWCZ*d?2;2Z#C0AK+CuEJ-)p$ofU!vr?K2Ok3Hg8=|30D%9{fMW(4xC9Oa zV8K8r4~4ok|4h|I*oO!l09b-(df!*1(99)5xCIt)2*8C20Gua@+lusy@y%sY*@iJ# z&;u7Z9FWu7F}mbf1`ZYkFaR6)kzgLG_SJC}dQ(vso`4No;6op5^7rY}{Hd9^03rAg zK>$6Ws8iZ-?gx)Wx+;myy^&6>zfi7VIlv2$ zo+7>oIT03qMwaVIeSo&eQ(EwIXah3v69BkQdNF;2d?Jl{uTAMRyh62i4>IB&M^@0; zDV?MBPABz5%Va*A>{6u%hH8+4ym^nL*VzhC}yJEnC2_{*K;^_b2Bz}J&+TC1=q^ly@mPIHL& zmxLF`+e8-}~a2#hH<7g4)9OVn?wh{;CXI)vR+jO5( z{M}8+ZxNmev|NT z!;)CPw}|(qvNtgPKSGx3Ny^+LtSiL(C!~Z=w7*K&F&FQU{eptsAbu=UBpFkYDG~ny z3=};_mtZFkYe0Yd~XvUG3V$R@PPQ3w>I(q4GU10-8Vzewgq~A zQcF&KxANEyLf7}0X&Bl#uzRkc9=U@@W9AHfi#Lz3U5__4E#yJe<7_{2v@bNq7#mr< z%s%7YEsF>4q$zm90y4M9R?p=JlKa^1l^M5!fz3Dkp&zKSJ!H5~>@B}H#P0C6&$$U06i7a{jNRP@cp1~;GFOF8RkRJ z6LN{ip0E~v^C+;3o4T~MI;Wjik-8qYy>J?o>o6VR71RjWEoKxdc_AQu3DH=SwOHTh zI=3f<$-$FV1X9wCLa|YA;3zj5rJ~WOm+JLu&8*idt7xrWGYrEpi={@bR;`+5saQ9Q zV#+kjIAs_{rBOA?)pEI3tCSi>wOGj$^h5D&4@3I|Oe1Te<453H#herl^78tG>?Bpf zk?-?IA-Bi6Q7{RGS#B7nSvAc@sa~#CD^&?o2G^)n%}U)Y7ma$OUM^v9Q-gD02gB~& zz#42?-0B#6tRJJTF1vQ<+>qOWzHh{Z68N8YN7irsKvr#eQWieC)ADC)Y;5&@=Q~blbGBf1 z+fEe@QCJJP#oeC7lH7vo-26VIPO|gx{>PtmHv&HlyZ60u#3|RuSYvB!_s(xNwzogt zG-N=>*w~JE!ieIQt+Aa?y1OE+WJ0t96zIE+#1+j7w_X5 znycNGDp8oLCHAAwY1<1LJFyz!d&>XohCY$&7;6e7&uhBA^xZm|Hy{q#TD1< z4I?bjk$rEnY41GMsUhBo zr#&~2JB-S}NT1dTIvt^_8~MGxV>Dgwgg(^iM4=t%L%-+E)Y0_F_7vChPLB;T*JGVd zTm^f3tE}gHA-6n{oVP^%u}()mgaj^8oT6j*WyhX2>`o^>sYtoFH_Pdr(Rhdv5?9E? zML9!~{Afz2`bs$k*1e+s*FI#u3R+Zv9#3`@RPy rrDgcCQ(&veirS%*Q{wa_|2V^O%-=YP-g2UB{KH#PM~wdh_)P1Oa0Tkw literal 0 HcmV?d00001 From 853d1d969cf0647362fa0f8ece47bee4a970a11a Mon Sep 17 00:00:00 2001 From: lysec Date: Sun, 12 Oct 2025 19:12:34 +0200 Subject: [PATCH 106/106] OSD: attempting to fix layout misalignment --- Modules/OSD/OSD.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 12b261af..18986b76 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -231,7 +231,7 @@ Variants { opacity: 0 scale: 0.85 - anchors.horizontalCenter: verticalMode ? undefined : parent.horizontalCenter + anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: verticalMode ? parent.verticalCenter : undefined Behavior on opacity {