From 8ece8052730904e8a2bc3ec10523b905bb3867a1 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 11:39:04 -0400 Subject: [PATCH 01/54] File Picker: Using platform's native picker - removed custom picker. --- Modules/SettingsPanel/Tabs/GeneralTab.qml | 20 +- .../SettingsPanel/Tabs/ScreenRecorderTab.qml | 24 +- Modules/SettingsPanel/Tabs/WallpaperTab.qml | 54 +- Services/FilePickerService.qml | 47 - Widgets/NFilePicker.qml | 945 +++--------------- Widgets/NPanel.qml | 12 +- ...{NInputButton.qml => NTextInputButton.qml} | 0 7 files changed, 216 insertions(+), 886 deletions(-) delete mode 100644 Services/FilePickerService.qml rename Widgets/{NInputButton.qml => NTextInputButton.qml} (100%) diff --git a/Modules/SettingsPanel/Tabs/GeneralTab.qml b/Modules/SettingsPanel/Tabs/GeneralTab.qml index 1036a4a3..0e0d8932 100644 --- a/Modules/SettingsPanel/Tabs/GeneralTab.qml +++ b/Modules/SettingsPanel/Tabs/GeneralTab.qml @@ -30,7 +30,7 @@ ColumnLayout { Layout.alignment: Qt.AlignTop } - NInputButton { + NTextInputButton { label: `${Quickshell.env("USER") || "user"}'s profile picture` description: "Your profile picture that appears throughout the interface." text: Settings.data.general.avatarImage @@ -39,18 +39,20 @@ ColumnLayout { buttonTooltip: "Browse for avatar image" onInputEditingFinished: Settings.data.general.avatarImage = text onButtonClicked: { - FilePickerService.open({ - "title": "Select Avatar Image", - "initialPath": Settings.data.general.avatarImage || Quickshell.env("HOME"), - "selectFiles": true, - "scaling": scaling, - "parent": root, - "onSelected": path => Settings.data.general.avatarImage = path - }) + filePicker.open() } } } + NFilePicker { + id: filePicker + pickerType: "file" + title: "Select avatar image" + initialPath: Settings.data.general.avatarImage.substr(0, Settings.data.general.avatarImage.lastIndexOf("/")) || Quickshell.env("HOME") + nameFilters: ["Image files (*.jpg *.jpeg *.png *.gif *.pnm *.bmp *.face)", "All files (*)"] + onAccepted: paths => Settings.data.general.avatarImage = paths[0] + } + NDivider { Layout.fillWidth: true Layout.topMargin: Style.marginXL * scaling diff --git a/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml b/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml index c2976301..f05e8dc7 100644 --- a/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml +++ b/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell import qs.Commons import qs.Services import qs.Widgets @@ -20,24 +21,15 @@ ColumnLayout { spacing: Style.marginS * scaling Layout.fillWidth: true - NInputButton { + NTextInputButton { label: "Output folder" description: "Folder where screen recordings will be saved." - placeholderText: "/home/xxx/Videos" + placeholderText: Quickshell.env("HOME") + "/Videos" text: Settings.data.screenRecorder.directory buttonIcon: "folder-open" buttonTooltip: "Browse for output folder" onInputEditingFinished: Settings.data.screenRecorder.directory = text - onButtonClicked: { - FilePickerService.open({ - "title": "Select Output Folder", - "initialPath": Settings.data.screenRecorder.directory || Quickshell.env("HOME") + "/Videos", - "selectFiles": false, - "scaling": scaling, - "parent": root, - "onSelected": path => Settings.data.screenRecorder.directory = path - }) - } + onButtonClicked: folderPicker.open() } ColumnLayout { @@ -261,4 +253,12 @@ ColumnLayout { Layout.topMargin: Style.marginXL * scaling Layout.bottomMargin: Style.marginXL * scaling } + + NFilePicker { + id: folderPicker + pickerType: "folder" + title: "Select output folder" + initialPath: Settings.data.screenRecorder.directory || Quickshell.env("HOME") + "/Videos" + onAccepted: paths => Settings.data.screenRecorder.directory = paths[0] + } } diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index f2fce98f..13d039cc 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -11,6 +11,8 @@ ColumnLayout { id: root spacing: Style.marginL * scaling + property string specificFolderMonitorName: "" + NHeader { label: "Wallpaper settings" description: "Control how wallpapers are managed and displayed." @@ -18,7 +20,7 @@ ColumnLayout { NToggle { label: "Enable wallpaper management" - description: "Manage wallpapers with Noctalia. (Uncheck if you prefer using another application)." + description: "Manage wallpapers with Noctalia. Uncheck if you prefer using another application." checked: Settings.data.wallpaper.enabled onToggled: checked => Settings.data.wallpaper.enabled = checked Layout.bottomMargin: Style.marginL * scaling @@ -29,7 +31,7 @@ ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true - NInputButton { + NTextInputButton { id: wallpaperPathInput label: "Wallpaper folder" description: "Path to your main wallpaper folder." @@ -37,13 +39,8 @@ ColumnLayout { buttonIcon: "folder-open" buttonTooltip: "Browse for wallpaper folder" Layout.fillWidth: true - - onInputEditingFinished: { - Settings.data.wallpaper.directory = text - } - onButtonClicked: { - openFileManager() - } + onInputEditingFinished: Settings.data.wallpaper.directory = text + onButtonClicked: mainFolderPicker.open() } // Monitor-specific directories @@ -83,17 +80,15 @@ ColumnLayout { font.pointSize: Style.fontSizeM * scaling } - NInputButton { + NTextInputButton { text: WallpaperService.getMonitorDirectory(modelData.name) buttonIcon: "folder-open" buttonTooltip: "Browse for wallpaper folder" Layout.fillWidth: true - - onInputEditingFinished: { - WallpaperService.setMonitorDirectory(modelData.name, text) - } + onInputEditingFinished: WallpaperService.setMonitorDirectory(modelData.name, text) onButtonClicked: { - openMonitorFileManager(modelData.name) + specificFolderMonitorName = modelData.name + monitorFolderPicker.open() } } } @@ -343,26 +338,17 @@ ColumnLayout { Layout.bottomMargin: Style.marginXL * scaling } - // File manager functions - function openFileManager() { - FilePickerService.open({ - "title": "Select Wallpaper Folder", - "initialPath": Settings.data.wallpaper.directory || Quickshell.env("HOME"), - "selectFiles": false, - "scaling": scaling, - "parent": root, - "onSelected": path => Settings.data.wallpaper.directory = path - }) + NFilePicker { + id: mainFolderPicker + pickerType: "folder" + title: "Select wallpaper folder" + onAccepted: paths => Settings.data.wallpaper.directory = paths[0] } - function openMonitorFileManager(monitorName) { - FilePickerService.open({ - "title": "Select Monitor Wallpaper Folder", - "initialPath": WallpaperService.getMonitorDirectory(monitorName), - "selectFiles": false, - "scaling": scaling, - "parent": root, - "onSelected": path => WallpaperService.setMonitorDirectory(monitorName, path) - }) + NFilePicker { + id: monitorFolderPicker + pickerType: "folder" + title: "Select monitor wallpaper folder" + onAccepted: paths => WallpaperService.setMonitorDirectory(specificFolderMonitorName, paths[0]) } } diff --git a/Services/FilePickerService.qml b/Services/FilePickerService.qml deleted file mode 100644 index 94ff4ede..00000000 --- a/Services/FilePickerService.qml +++ /dev/null @@ -1,47 +0,0 @@ -pragma Singleton - -import QtQuick -import Quickshell - -QtObject { - id: root - - // Function to open a file manager dialog - function open(options) { - var component = Qt.createComponent(Qt.resolvedUrl(Quickshell.shellDir + "/Widgets/NFilePicker.qml")) - if (component.status === Component.Ready) { - // Extract directory from file path if it's a file - var initialPath = options.initialPath || Quickshell.env("HOME") - if (options.selectFiles && initialPath !== Quickshell.env("HOME")) { - // If selecting files and path is not home, extract directory - var pathParts = initialPath.split('/') - if (pathParts.length > 1) { - pathParts.pop() // Remove filename - initialPath = pathParts.join('/') || '/' - } - } - - var dialog = component.createObject(options.parent || Overlay.overlay, { - "title": options.title || "Select File/Folder", - "initialPath": initialPath, - "selectFiles": options.selectFiles || false, - "selectFolders": !options.selectFiles || false, - "scaling": options.scaling || 1.0 - }) - if (dialog) { - if (options.onSelected) { - if (options.selectFiles) { - dialog.fileSelected.connect(options.onSelected) - } else { - dialog.folderSelected.connect(options.onSelected) - } - } - dialog.open() - return dialog - } - } else { - console.error("Component error:", component.errorString()) - } - return null - } -} diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index 39459404..4deb3756 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -1,816 +1,197 @@ +import QtCore import QtQuick -import QtQuick.Layouts +import QtQuick.Dialogs import QtQuick.Controls -import Qt.labs.folderlistmodel -import Quickshell -import Quickshell.Io -import qs.Commons import qs.Services -import qs.Widgets -import "../Helpers/FuzzySort.js" as FuzzySort -Popup { +Item { id: root - // Properties - property string title: "File Picker" - property string initialPath: Quickshell.env("HOME") || "/home" - property bool selectFiles: true - property bool selectFolders: true - property var nameFilters: ["*"] - property bool showDirs: true - property real scaling: 1.0 + // Public API Properties + property string initialPath: "" property var selectedPaths: [] - property string currentPath: initialPath - property bool shouldResetSelection: false + property string selectedPath: "" + property bool multipleSelection: false + property string pickerType: "file" // "file" or "folder" + property var nameFilters: ["All files (*)"] // e.g., ["Image files (*.png *.jpg)", "Text files (*.txt)"] + property string title: pickerType === "folder" ? "Select Folder" : "Select File" + property string acceptLabel: "Select" + property string rejectLabel: "Cancel" - // Signals - signal fileSelected(string path) - signal filesSelected(var paths) - signal folderSelected(string path) - signal cancelled + // State properties + property bool isOpen: false - function openFileManager() { - if (!root.currentPath) - root.currentPath = root.initialPath - shouldResetSelection = true - open() - } + // Signals for external connections + signal accepted(var paths) + signal rejected + signal pathSelected(string path) + signal pathsSelected(var paths) + signal beforeOpen + signal afterClose - function getFileIcon(fileName) { - const ext = fileName.split('.').pop().toLowerCase() - const iconMap = { - "txt": 'filepicker-file-text', - "md": 'filepicker-file-text', - "log": 'filepicker-file-text', - "jpg": 'filepicker-photo', - "jpeg": 'filepicker-photo', - "png": 'filepicker-photo', - "gif": 'filepicker-photo', - "bmp": 'filepicker-photo', - "svg": 'filepicker-photo', - "mp4": 'filepicker-video', - "avi": 'filepicker-video', - "mkv": 'filepicker-video', - "mov": 'filepicker-video', - "mp3": 'filepicker-music', - "wav": 'filepicker-music', - "flac": 'filepicker-music', - "ogg": 'filepicker-music', - "zip": 'filepicker-archive', - "tar": 'filepicker-archive', - "gz": 'filepicker-archive', - "rar": 'filepicker-archive', - "7z": 'filepicker-archive', - "pdf": 'filepicker-text', - "doc": 'filepicker-text', - "docx": 'filepicker-text', - "xls": 'filepicker-table', - "xlsx": 'filepicker-table', - "ppt": 'filepicker-presentation', - "pptx": 'filepicker-presentation', - "html": 'filepicker-code', - "htm": 'filepicker-code', - "css": 'filepicker-code', - "js": 'filepicker-code', - "json": 'filepicker-code', - "xml": 'filepicker-code', - "exe": 'filepicker-settings', - "app": 'filepicker-settings', - "deb": 'filepicker-settings', - "rpm": 'filepicker-settings' + // Public functions + function open() { + beforeOpen() + + if (PanelService.openedPanel !== null) { + PanelService.openedPanel.isMasked = true } - return iconMap[ext] || 'filepicker-file' + + isOpen = true + + // Small delay to ensure panel changes happen first + Qt.callLater(function () { + if (pickerType === "folder") { + folderDialog.open() + } else { + fileDialog.open() + } + }) } - function formatFileSize(bytes) { - if (bytes === 0) - return "0 B" - const k = 1024, sizes = ["B", "KB", "MB", "GB", "TB"] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i] - } - - function confirmSelection() { - if (filePickerPanel.currentSelection.length === 0) - return - - root.selectedPaths = filePickerPanel.currentSelection - const path = filePickerPanel.currentSelection[0] - - if (filePickerPanel.currentSelection.length === 1) { - const isDir = folderModel.get(folderModel.indexOf(path), "fileIsDir") - if (root.selectFiles && !root.selectFolders) - root.fileSelected(path) - else if (root.selectFolders && !root.selectFiles) - root.folderSelected(path) - else - isDir ? root.folderSelected(path) : root.fileSelected(path) + function close() { + if (pickerType === "folder") { + folderDialog.close() } else { - root.filesSelected(filePickerPanel.currentSelection) + fileDialog.close() } - root.close() + + handleClose() } - function updateFilteredModel() { - filteredModel.clear() - const searchText = filePickerPanel.filterText.toLowerCase() + function handleClose() { + isOpen = false - for (var i = 0; i < folderModel.count; i++) { - const fileName = folderModel.get(i, "fileName") - const filePath = folderModel.get(i, "filePath") - const fileIsDir = folderModel.get(i, "fileIsDir") - const fileSize = folderModel.get(i, "fileSize") + if (PanelService.openedPanel !== null) { + PanelService.openedPanel.isMasked = false + } - if (root.selectFolders && !root.selectFiles && !fileIsDir) - continue - if (searchText === "" || fileName.toLowerCase().includes(searchText)) { - filteredModel.append({ - "fileName": fileName, - "filePath": filePath, - "fileIsDir": fileIsDir, - "fileSize": fileSize - }) + afterClose() + } + + function reset() { + selectedPaths = [] + selectedPath = "" + } + + // Helper function to set file extensions easily + function setFileExtensions(extensions) { + if (!extensions || extensions.length === 0) { + nameFilters = ["All files (*)"] + return + } + + var filters = [] + for (var i = 0; i < extensions.length; i++) { + var ext = extensions[i] + if (typeof ext === "string") { + // Simple extension like "png" + filters.push(ext.toUpperCase() + " files (*." + ext + ")") + } else if (typeof ext === "object" && ext.label && ext.extensions) { + // Complex filter like {label: "Images", extensions: ["png", "jpg", "jpeg"]} + var filterStr = ext.label + " (" + for (var j = 0; j < ext.extensions.length; j++) { + filterStr += "*." + ext.extensions[j] + if (j < ext.extensions.length - 1) + filterStr += " " + } + filterStr += ")" + filters.push(filterStr) } } + + if (filters.length > 0) { + filters.push("All files (*)") + nameFilters = filters + } } - width: 900 * scaling - height: 700 * scaling - modal: true - closePolicy: Popup.CloseOnEscape - anchors.centerIn: Overlay.overlay - - background: Rectangle { - color: Color.mSurfaceVariant - radius: Style.radiusL * scaling - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) + // Helper function to convert URL to local path + function urlToPath(url) { + var path = url.toString() + // Remove file:// prefix (works for both Windows and Unix) + path = path.replace(/^file:\/\/\//, "/") // Unix + path = path.replace(/^file:\/\//, "") // Windows + // Handle Windows drive letters + if (Qt.platform.os === "windows") { + path = path.replace(/^\/([A-Z]:)/, "$1") + } + return path } - Rectangle { - id: filePickerPanel - anchors.fill: parent - anchors.margins: Style.marginL * scaling - color: Color.transparent - - property string filterText: "" - property var currentSelection: [] - property bool viewMode: true // true = grid, false = list - property string searchText: "" - property bool showSearchBar: false - - focus: true - - Keys.onPressed: event => { - if (event.modifiers & Qt.ControlModifier && event.key === Qt.Key_F) { - filePickerPanel.showSearchBar = !filePickerPanel.showSearchBar - if (filePickerPanel.showSearchBar) - Qt.callLater(() => searchInput.forceActiveFocus()) - event.accepted = true - } else if (event.key === Qt.Key_Escape && filePickerPanel.showSearchBar) { - filePickerPanel.showSearchBar = false - filePickerPanel.searchText = "" - filePickerPanel.filterText = "" - root.updateFilteredModel() - event.accepted = true - } - } - - ColumnLayout { - anchors.fill: parent - spacing: Style.marginM * scaling - - // Header - RowLayout { - Layout.fillWidth: true - spacing: Style.marginM * scaling - - NIcon { - icon: "filepicker-folder" - color: Color.mPrimary - font.pointSize: Style.fontSizeXXL * scaling - } - NText { - text: root.title - font.pointSize: Style.fontSizeXL * scaling - font.weight: Style.fontWeightBold - color: Color.mPrimary - Layout.fillWidth: true - } - NIconButton { - icon: "filepicker-refresh" - tooltipText: "Refresh" - onClicked: folderModel.refresh() - } - NIconButton { - icon: "filepicker-close" - tooltipText: "Close" - onClicked: { - root.cancelled() - root.close() - } - } - } - - NDivider { - Layout.fillWidth: true - } - - // Navigation toolbar - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 45 * scaling - color: Color.mSurfaceVariant - radius: Style.radiusS * scaling - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - - RowLayout { - anchors.fill: parent - anchors.margins: Style.marginS * scaling - spacing: Style.marginS * scaling - - NIconButton { - icon: "filepicker-arrow-up" - tooltipText: "Up" - baseSize: Style.baseWidgetSize * 0.8 - enabled: folderModel.folder.toString() !== "file:///" - onClicked: { - const parentPath = folderModel.parentFolder.toString().replace("file://", "") - folderModel.folder = "file://" + parentPath - root.currentPath = parentPath - } - } - - NIconButton { - icon: "filepicker-home" - tooltipText: "Home" - baseSize: Style.baseWidgetSize * 0.8 - onClicked: { - const homePath = Quickshell.env("HOME") || "/home" - folderModel.folder = "file://" + homePath - root.currentPath = homePath - } - } - - NIconButton { - icon: filePickerPanel.viewMode ? "filepicker-list" : "filepicker-layout-grid" - tooltipText: filePickerPanel.viewMode ? "List View" : "Grid View" - baseSize: Style.baseWidgetSize * 0.8 - onClicked: filePickerPanel.viewMode = !filePickerPanel.viewMode - } - - NIconButton { - icon: filePickerPanel.showSearchBar ? "filepicker-x" : "filepicker-search" - tooltipText: filePickerPanel.showSearchBar ? "Close Search" : "Search" - baseSize: Style.baseWidgetSize * 0.8 - onClicked: { - filePickerPanel.showSearchBar = !filePickerPanel.showSearchBar - if (!filePickerPanel.showSearchBar) { - filePickerPanel.searchText = "" - filePickerPanel.filterText = "" - root.updateFilteredModel() - } - } - } - - NTextInput { - id: locationInput - text: root.currentPath - placeholderText: "Enter path..." - Layout.fillWidth: true - onEditingFinished: { - const newPath = text.trim() - if (newPath !== "" && newPath !== root.currentPath) { - folderModel.folder = "file://" + newPath - root.currentPath = newPath - } else { - text = root.currentPath - } - } - Connections { - target: root - function onCurrentPathChanged() { - if (!locationInput.activeFocus) - locationInput.text = root.currentPath - } - } - } - } - } - - // Search bar - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 45 * scaling - color: Color.mSurfaceVariant - radius: Style.radiusS * scaling - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - visible: filePickerPanel.showSearchBar - - RowLayout { - anchors.fill: parent - anchors.margins: Style.marginS * scaling - spacing: Style.marginS * scaling - - NIcon { - icon: "filepicker-search" - color: Color.mOnSurfaceVariant - font.pointSize: Style.fontSizeS * scaling - } - NTextInput { - id: searchInput - placeholderText: "Search files and folders..." - Layout.fillWidth: true - text: filePickerPanel.searchText - onTextChanged: { - filePickerPanel.searchText = text - filePickerPanel.filterText = text - root.updateFilteredModel() - } - Keys.onEscapePressed: { - filePickerPanel.showSearchBar = false - filePickerPanel.searchText = "" - filePickerPanel.filterText = "" - root.updateFilteredModel() - } - } - NIconButton { - icon: "filepicker-x" - tooltipText: "Clear" - baseSize: Style.baseWidgetSize * 0.6 - visible: filePickerPanel.searchText.length > 0 - onClicked: { - searchInput.text = "" - filePickerPanel.searchText = "" - filePickerPanel.filterText = "" - root.updateFilteredModel() - } - } - } - } - - // File list area - Rectangle { - Layout.fillWidth: true - Layout.fillHeight: true - color: Color.mSurface - radius: Style.radiusM * scaling - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - clip: true - - FolderListModel { - id: folderModel - folder: "file://" + root.currentPath - nameFilters: root.nameFilters - showDirs: root.showDirs - showHidden: true - sortField: FolderListModel.Name - sortReversed: false - onFolderChanged: { - root.currentPath = folder.toString().replace("file://", "") - filePickerPanel.currentSelection = [] - } - onStatusChanged: { - if (status === FolderListModel.Error) { - if (root.currentPath !== Quickshell.env("HOME")) { - folder = "file://" + Quickshell.env("HOME") - root.currentPath = Quickshell.env("HOME") - } - } else if (status === FolderListModel.Ready) { - root.updateFilteredModel() - } - } - } - - ListModel { - id: filteredModel - } - - // Common scroll bar component - Component { - id: scrollBarComponent - ScrollBar { - policy: ScrollBar.AsNeeded - contentItem: Rectangle { - implicitWidth: 6 * scaling - implicitHeight: 100 - radius: Style.radiusM * scaling - color: parent.pressed ? Qt.alpha(Color.mTertiary, 0.8) : parent.hovered ? Qt.alpha(Color.mTertiary, 0.8) : Qt.alpha(Color.mTertiary, 0.8) - opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 1.0 : 0.0 - Behavior on opacity { - NumberAnimation { - duration: Style.animationFast - } - } - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - } - background: Rectangle { - implicitWidth: 6 * scaling - implicitHeight: 100 - color: Color.transparent - opacity: parent.policy === ScrollBar.AlwaysOn || parent.active ? 0.3 : 0.0 - radius: (Style.radiusM * scaling) / 2 - Behavior on opacity { - NumberAnimation { - duration: Style.animationFast - } - } - } - } - } - - // Grid view - GridView { - id: gridView - anchors.fill: parent - anchors.margins: Style.marginM * scaling - model: filteredModel - visible: filePickerPanel.viewMode - clip: true - - property int columns: Math.max(1, Math.floor(width / (120 * scaling))) - property int itemSize: Math.floor((width - leftMargin - rightMargin - (columns * Style.marginS * scaling)) / columns) - - cellWidth: Math.floor((width - leftMargin - rightMargin) / columns) - cellHeight: Math.floor(itemSize * 0.8) + Style.marginXS * scaling + Style.fontSizeS * scaling + Style.marginM * scaling - - leftMargin: Style.marginS * scaling - rightMargin: Style.marginS * scaling - topMargin: Style.marginS * scaling - bottomMargin: Style.marginS * scaling - - ScrollBar.vertical: scrollBarComponent.createObject(gridView, { - "parent": gridView, - "x": gridView.mirrored ? 0 : gridView.width - width, - "y": 0, - "height": gridView.height - }) - - delegate: Rectangle { - id: gridItem - width: gridView.itemSize - height: gridView.cellHeight - color: Color.transparent - radius: Style.radiusM * scaling - - property bool isSelected: filePickerPanel.currentSelection.includes(model.filePath) - - Rectangle { - anchors.fill: parent - color: Color.transparent - radius: parent.radius - border.color: isSelected ? Color.mSecondary : Color.mSurface - border.width: Math.max(1, Style.borderL * scaling) - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - } - - Rectangle { - anchors.fill: parent - color: (mouseArea.containsMouse && !isSelected) ? Color.mTertiary : Color.transparent - radius: parent.radius - border.color: (mouseArea.containsMouse && !isSelected) ? Color.mTertiary : Color.transparent - border.width: Math.max(1, Style.borderS * scaling) - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - Behavior on border.color { - ColorAnimation { - duration: Style.animationFast - } - } - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: Style.marginS * scaling - spacing: Style.marginXS * scaling - - Rectangle { - id: iconContainer - Layout.fillWidth: true - Layout.preferredHeight: Math.round(gridView.itemSize * 0.67) - color: Color.transparent - - property bool isImage: { - if (model.fileIsDir) - return false - const ext = model.fileName.split('.').pop().toLowerCase() - return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'].includes(ext) - } - - Image { - id: thumbnail - anchors.fill: parent - anchors.margins: Style.marginXS * scaling - source: iconContainer.isImage ? "file://" + model.filePath : "" - fillMode: Image.PreserveAspectFit - visible: iconContainer.isImage && status === Image.Ready - smooth: false - cache: true - asynchronous: true - sourceSize.width: 120 * scaling - sourceSize.height: 120 * scaling - onStatusChanged: { - if (status === Image.Error) - visible = false - } - - Rectangle { - anchors.fill: parent - color: Color.mSurfaceVariant - radius: Style.radiusS * scaling - visible: thumbnail.status === Image.Loading - NIcon { - icon: "filepicker-photo" - font.pointSize: Style.fontSizeL * scaling - color: Color.mOnSurfaceVariant - anchors.centerIn: parent - } - } - } - - NIcon { - icon: model.fileIsDir ? "filepicker-folder" : root.getFileIcon(model.fileName) - font.pointSize: Style.fontSizeXXL * 2 * scaling - color: { - if (isSelected) - return Color.mSecondary - else if (mouseArea.containsMouse) - return model.fileIsDir ? Color.mOnTertiary : Color.mOnTertiary - else - return model.fileIsDir ? Color.mPrimary : Color.mOnSurfaceVariant - } - anchors.centerIn: parent - visible: !iconContainer.isImage || thumbnail.status !== Image.Ready - } - - Rectangle { - anchors.top: parent.top - anchors.right: parent.right - anchors.margins: Style.marginS * scaling - width: 24 * scaling - height: 24 * scaling - radius: width / 2 - color: Color.mSecondary - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - visible: isSelected - NIcon { - icon: "filepicker-check" - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSecondary - anchors.centerIn: parent - } - } - } - - NText { - text: model.fileName - color: { - if (isSelected) - return Color.mSecondary - else if (mouseArea.containsMouse) - return Color.mOnTertiary - else - return Color.mOnSurface - } - font.pointSize: Style.fontSizeS * scaling - font.weight: isSelected ? Style.fontWeightBold : Style.fontWeightRegular - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WordWrap - elide: Text.ElideRight - maximumLineCount: 2 - } - } - - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.LeftButton | Qt.RightButton - - onClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - if (model.fileIsDir) { - if (root.selectFolders && !root.selectFiles) { - filePickerPanel.currentSelection = [model.filePath] - } else { - folderModel.folder = "file://" + model.filePath - root.currentPath = model.filePath - } - } else { - if (root.selectFiles) - filePickerPanel.currentSelection = [model.filePath] - } - } - } - - onDoubleClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - if (model.fileIsDir) { - if (root.selectFolders && !root.selectFiles) { - filePickerPanel.currentSelection = [model.filePath] - root.confirmSelection() - } else { - folderModel.folder = "file://" + model.filePath - root.currentPath = model.filePath - } - } else { - if (root.selectFiles) { - filePickerPanel.currentSelection = [model.filePath] - root.confirmSelection() - } - } - } - } - } - } - } - - // List view - ListView { - id: listView - anchors.fill: parent - anchors.margins: Style.marginS * scaling - model: filteredModel - visible: !filePickerPanel.viewMode - clip: true - - ScrollBar.vertical: scrollBarComponent.createObject(listView, { - "parent": listView, - "x": listView.mirrored ? 0 : listView.width - width, - "y": 0, - "height": listView.height - }) - - delegate: Rectangle { - id: listItem - width: listView.width - height: 40 * scaling - color: { - if (filePickerPanel.currentSelection.includes(model.filePath)) - return Color.mSecondary - if (mouseArea.containsMouse) - return Qt.alpha(Color.mOnSurface, 0.1) - return Color.transparent - } - radius: Style.radiusS * scaling - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - - RowLayout { - anchors.fill: parent - anchors.leftMargin: Style.marginM * scaling - anchors.rightMargin: Style.marginM * scaling - spacing: Style.marginM * scaling - - NIcon { - icon: model.fileIsDir ? "filepicker-folder" : root.getFileIcon(model.fileName) - font.pointSize: Style.fontSizeL * scaling - color: model.fileIsDir ? (filePickerPanel.currentSelection.includes(model.filePath) ? Color.mOnSecondary : Color.mPrimary) : Color.mOnSurfaceVariant - } - - NText { - text: model.fileName - color: filePickerPanel.currentSelection.includes(model.filePath) ? Color.mOnSecondary : Color.mOnSurface - font.pointSize: Style.fontSizeM * scaling - font.weight: filePickerPanel.currentSelection.includes(model.filePath) ? Style.fontWeightBold : Style.fontWeightRegular - Layout.fillWidth: true - elide: Text.ElideRight - } - - NText { - text: model.fileIsDir ? "" : root.formatFileSize(model.fileSize) - color: filePickerPanel.currentSelection.includes(model.filePath) ? Color.mOnSecondary : Color.mOnSurfaceVariant - font.pointSize: Style.fontSizeS * scaling - visible: !model.fileIsDir - Layout.preferredWidth: implicitWidth - } - } - - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.LeftButton | Qt.RightButton - - onClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - if (model.fileIsDir) { - if (root.selectFolders && !root.selectFiles) { - filePickerPanel.currentSelection = [model.filePath] - } else { - folderModel.folder = "file://" + model.filePath - root.currentPath = model.filePath - } - } else { - if (root.selectFiles) - filePickerPanel.currentSelection = [model.filePath] - } - } - } - - onDoubleClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - if (model.fileIsDir) { - if (root.selectFolders && !root.selectFiles) { - filePickerPanel.currentSelection = [model.filePath] - root.confirmSelection() - } else { - folderModel.folder = "file://" + model.filePath - root.currentPath = model.filePath - } - } else { - if (root.selectFiles) { - filePickerPanel.currentSelection = [model.filePath] - root.confirmSelection() - } - } - } - } - } - } - } - } - - // Footer - RowLayout { - Layout.fillWidth: true - spacing: Style.marginM * scaling - - NText { - text: { - if (filePickerPanel.searchText.length > 0) { - return "Searching for: \"" + filePickerPanel.searchText + "\" (" + filteredModel.count + " matches)" - } else if (filePickerPanel.currentSelection.length > 0) { - return filePickerPanel.currentSelection.length + " item(s) selected" - } else { - return filteredModel.count + " items" - } - } - color: filePickerPanel.searchText.length > 0 ? Color.mPrimary : Color.mOnSurfaceVariant - font.pointSize: Style.fontSizeS * scaling - Layout.fillWidth: true - } - - NButton { - text: "Cancel" - outlined: true - onClicked: { - root.cancelled() - root.close() - } - } - - NButton { - text: { - if (root.selectFolders && !root.selectFiles) - return "Select Folder" - else if (root.selectFiles && !root.selectFolders) - return "Select File" - else - return "Select" - } - icon: "filepicker-check" - enabled: filePickerPanel.currentSelection.length > 0 - onClicked: root.confirmSelection() - } - } + // Get default folder with proper fallback + function getDefaultFolder() { + if (root.initialPath) { + return "file:///" + root.initialPath.replace(/^\//, "") } - Connections { - target: root - function onShouldResetSelectionChanged() { - if (root.shouldResetSelection) { - filePickerPanel.currentSelection = [] - root.shouldResetSelection = false + // Fallback to home directory + try { + return StandardPaths.writableLocation(StandardPaths.HomeLocation) + } catch (e) { + // Final fallback if StandardPaths fails + return "file:///" + (Qt.platform.os === "windows" ? "C:/Users" : "/home") + } + } + + // FileDialog for file selection (Qt 6.x) + FileDialog { + id: fileDialog + title: root.title + currentFolder: getDefaultFolder() + fileMode: root.multipleSelection ? FileDialog.OpenFiles : FileDialog.OpenFile + nameFilters: root.nameFilters + acceptLabel: root.acceptLabel + rejectLabel: root.rejectLabel + modality: Qt.WindowModal + + onAccepted: { + if (fileMode === FileDialog.OpenFiles) { + var paths = [] + for (var i = 0; i < fileDialog.selectedFiles.length; i++) { + paths.push(urlToPath(fileDialog.selectedFiles[i])) } + root.selectedPaths = paths + root.selectedPath = paths.length > 0 ? paths[0] : "" + root.pathsSelected(paths) + root.accepted(paths) + } else { + var singlePath = urlToPath(fileDialog.selectedFile) + root.selectedPath = singlePath + root.selectedPaths = [singlePath] + root.pathSelected(singlePath) + root.accepted([singlePath]) } + root.handleClose() } - Component.onCompleted: { - if (!root.currentPath) - root.currentPath = root.initialPath - folderModel.folder = "file://" + root.currentPath + onRejected: { + root.rejected() + root.handleClose() + } + } + + // FolderDialog for folder selection (Qt 6.x) + FolderDialog { + id: folderDialog + title: root.title + currentFolder: getDefaultFolder() + acceptLabel: root.acceptLabel + rejectLabel: root.rejectLabel + modality: Qt.WindowModal + + onAccepted: { + var folderPath = urlToPath(folderDialog.selectedFolder) + root.selectedPath = folderPath + root.selectedPaths = [folderPath] + root.pathSelected(folderPath) + root.accepted([folderPath]) + root.handleClose() + } + + onRejected: { + root.rejected() + root.handleClose() } } } diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml index 26ad0b8b..03837a97 100644 --- a/Widgets/NPanel.qml +++ b/Widgets/NPanel.qml @@ -25,6 +25,8 @@ Loader { property bool panelAnchorLeft: false property bool panelAnchorRight: false + property bool isMasked: false + // Properties to support positioning relative to the opener (button) property bool useButtonPosition: false property point buttonPosition: Qt.point(0, 0) @@ -177,12 +179,18 @@ Loader { } visible: true - color: Settings.data.general.dimDesktop ? Qt.alpha(Color.mShadow, dimmingOpacity) : Color.transparent + WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.namespace: "noctalia-panel" WlrLayershell.keyboardFocus: root.panelKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None + mask: root.isMasked ? maskRegion : null + + Region { + id: maskRegion + } + Behavior on color { ColorAnimation { duration: Style.animationSlow @@ -244,7 +252,7 @@ Loader { } scale: root.scaleValue - opacity: root.opacityValue + opacity: root.isMasked ? 0 : root.opacityValue x: isDragged ? manualX : calculatedX y: isDragged ? manualY : calculatedY diff --git a/Widgets/NInputButton.qml b/Widgets/NTextInputButton.qml similarity index 100% rename from Widgets/NInputButton.qml rename to Widgets/NTextInputButton.qml From 5aa935b348fd3c42e504965f58ec842ee7436209 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 12:19:41 -0400 Subject: [PATCH 02/54] FileDialog: also properly hide/restore popups when opening --- .../Bar/BarWidgetSettingsDialog.qml | 100 ++++++++++-------- .../SidePanelToggleSettings.qml | 15 ++- Services/PanelService.qml | 13 +++ Widgets/NFilePicker.qml | 8 ++ 4 files changed, 85 insertions(+), 51 deletions(-) diff --git a/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml b/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml index fe65650f..7f0da45c 100644 --- a/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml +++ b/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml @@ -9,12 +9,14 @@ import "./WidgetSettings" as WidgetSettings // Widget Settings Dialog Component Popup { - id: settingsPopup + id: root property int widgetIndex: -1 property var widgetData: null property string widgetId: "" + property bool isMasked: false + // Center popup in parent x: (parent.width - width) * 0.5 y: (parent.height - height) * 0.5 @@ -23,52 +25,35 @@ Popup { height: content.implicitHeight + padding * 2 padding: Style.marginXL * scaling modal: true - - background: Rectangle { - id: bgRect - color: Color.mSurface - radius: Style.radiusL * scaling - border.color: Color.mPrimary - border.width: Style.borderM * scaling - } - - // Load settings when popup opens with data + onOpened: { + // Mark this popup has opened in the PanelService + PanelService.willOpenPopup(root) + + // Load settings when popup opens with data if (widgetData && widgetId) { loadWidgetSettings() } } - function loadWidgetSettings() { - const widgetSettingsMap = { - "ActiveWindow": "WidgetSettings/ActiveWindowSettings.qml", - "Battery": "WidgetSettings/BatterySettings.qml", - "Brightness": "WidgetSettings/BrightnessSettings.qml", - "Clock": "WidgetSettings/ClockSettings.qml", - "CustomButton": "WidgetSettings/CustomButtonSettings.qml", - "KeyboardLayout": "WidgetSettings/KeyboardLayoutSettings.qml", - "MediaMini": "WidgetSettings/MediaMiniSettings.qml", - "Microphone": "WidgetSettings/MicrophoneSettings.qml", - "NotificationHistory": "WidgetSettings/NotificationHistorySettings.qml", - "Workspace": "WidgetSettings/WorkspaceSettings.qml", - "SidePanelToggle": "WidgetSettings/SidePanelToggleSettings.qml", - "Spacer": "WidgetSettings/SpacerSettings.qml", - "SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml", - "Volume": "WidgetSettings/VolumeSettings.qml" - } - - const source = widgetSettingsMap[widgetId] - if (source) { - // Use setSource to pass properties at creation time - settingsLoader.setSource(source, { - "widgetData": widgetData, - "widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId] - }) - } + onClosed: { + PanelService.willClosePopup(root) } - ColumnLayout { + background: Rectangle { + id: bgRect + + opacity: root.isMasked ? 0 : 1.0 + color: Color.mSurface + radius: Style.radiusL * scaling + border.color: Color.mPrimary + border.width: Math.max(1, Style.borderM * scaling) + } + + contentItem: ColumnLayout { id: content + + opacity: root.isMasked ? 0 : 1.0 width: parent.width spacing: Style.marginM * scaling @@ -77,7 +62,7 @@ Popup { Layout.fillWidth: true NText { - text: `${settingsPopup.widgetId} Settings` + text: `${root.widgetId} Settings` font.pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mPrimary @@ -86,7 +71,7 @@ Popup { NIconButton { icon: "close" - onClicked: settingsPopup.close() + onClicked: root.close() } } @@ -117,7 +102,7 @@ Popup { NButton { text: "Cancel" outlined: true - onClicked: settingsPopup.close() + onClicked: root.close() } NButton { @@ -126,11 +111,40 @@ Popup { onClicked: { if (settingsLoader.item && settingsLoader.item.saveSettings) { var newSettings = settingsLoader.item.saveSettings() - root.updateWidgetSettings(sectionId, settingsPopup.widgetIndex, newSettings) - settingsPopup.close() + root.updateWidgetSettings(sectionId, root.widgetIndex, newSettings) + root.close() } } } } } + + function loadWidgetSettings() { + const widgetSettingsMap = { + "ActiveWindow": "WidgetSettings/ActiveWindowSettings.qml", + "Battery": "WidgetSettings/BatterySettings.qml", + "Brightness": "WidgetSettings/BrightnessSettings.qml", + "Clock": "WidgetSettings/ClockSettings.qml", + "CustomButton": "WidgetSettings/CustomButtonSettings.qml", + "KeyboardLayout": "WidgetSettings/KeyboardLayoutSettings.qml", + "MediaMini": "WidgetSettings/MediaMiniSettings.qml", + "Microphone": "WidgetSettings/MicrophoneSettings.qml", + "NotificationHistory": "WidgetSettings/NotificationHistorySettings.qml", + "Workspace": "WidgetSettings/WorkspaceSettings.qml", + "SidePanelToggle": "WidgetSettings/SidePanelToggleSettings.qml", + "Spacer": "WidgetSettings/SpacerSettings.qml", + "SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml", + "Volume": "WidgetSettings/VolumeSettings.qml" + } + + const source = widgetSettingsMap[widgetId] + if (source) { + // Use setSource to pass properties at creation time + settingsLoader.setSource(source, { + "widgetData": widgetData, + "widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId] + }) + } + } + } diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml index df48506c..caae851b 100644 --- a/Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml +++ b/Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml @@ -38,13 +38,6 @@ ColumnLayout { } } - NFilePicker { - id: filePicker - title: "Select a custom icon" - onFileSelected: function (filePath) { - valueCustomIconPath = "file://" + filePath - } - } RowLayout { spacing: Style.marginM * scaling @@ -85,9 +78,15 @@ ColumnLayout { NIconPicker { id: iconPicker initialIcon: valueIcon - onIconSelected: function (iconName) { + onIconSelected: iconName => { valueIcon = iconName valueCustomIconPath = "" } } + + NFilePicker { + id: filePicker + title: "Select a custom icon" + onAccepted: paths => valueCustomIconPath = paths[0] + } } \ No newline at end of file diff --git a/Services/PanelService.qml b/Services/PanelService.qml index f1df973f..caefab3b 100644 --- a/Services/PanelService.qml +++ b/Services/PanelService.qml @@ -14,6 +14,9 @@ Singleton { property var openedPanel: null readonly property bool hasOpenedPanel: (openedPanel !== null) + // Currently opened popup + property var openedPopup: null + property var registeredPanels: ({}) signal willOpen @@ -56,4 +59,14 @@ Singleton { openedPanel = null } } + + // Popups + function willOpenPopup(popup) { + openedPopup = popup + } + function willClosePopup(popup) { + if (openedPopup && openedPopup === popup) { + openedPopup = null + } + } } diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index 4deb3756..2f701b21 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -37,6 +37,10 @@ Item { PanelService.openedPanel.isMasked = true } + if (PanelService.openedPopup !== null) { + PanelService.openedPopup.isMasked = true + } + isOpen = true // Small delay to ensure panel changes happen first @@ -66,6 +70,10 @@ Item { PanelService.openedPanel.isMasked = false } + if (PanelService.openedPopup !== null) { + PanelService.openedPopup.isMasked = false + } + afterClose() } From 64001152effce7abd445600c23101447279345e9 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 13:32:00 -0400 Subject: [PATCH 03/54] BarWidgetSettings: fix --- .../Bar/BarWidgetSettingsDialog.qml | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml b/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml index 7f0da45c..812f223f 100644 --- a/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml +++ b/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml @@ -9,7 +9,8 @@ import "./WidgetSettings" as WidgetSettings // Widget Settings Dialog Component Popup { - id: root + // Don't replace by root! + id: widgetSettings property int widgetIndex: -1 property var widgetData: null @@ -28,7 +29,7 @@ Popup { onOpened: { // Mark this popup has opened in the PanelService - PanelService.willOpenPopup(root) + PanelService.willOpenPopup(widgetSettings) // Load settings when popup opens with data if (widgetData && widgetId) { @@ -37,13 +38,13 @@ Popup { } onClosed: { - PanelService.willClosePopup(root) + PanelService.willClosePopup(widgetSettings) } background: Rectangle { id: bgRect - opacity: root.isMasked ? 0 : 1.0 + opacity: widgetSettings.isMasked ? 0 : 1.0 color: Color.mSurface radius: Style.radiusL * scaling border.color: Color.mPrimary @@ -53,7 +54,7 @@ Popup { contentItem: ColumnLayout { id: content - opacity: root.isMasked ? 0 : 1.0 + opacity: widgetSettings.isMasked ? 0 : 1.0 width: parent.width spacing: Style.marginM * scaling @@ -62,7 +63,7 @@ Popup { Layout.fillWidth: true NText { - text: `${root.widgetId} Settings` + text: `${widgetSettings.widgetId} Settings` font.pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mPrimary @@ -71,7 +72,7 @@ Popup { NIconButton { icon: "close" - onClicked: root.close() + onClicked: widgetSettings.close() } } @@ -102,7 +103,7 @@ Popup { NButton { text: "Cancel" outlined: true - onClicked: root.close() + onClicked: widgetSettings.close() } NButton { @@ -111,8 +112,8 @@ Popup { onClicked: { if (settingsLoader.item && settingsLoader.item.saveSettings) { var newSettings = settingsLoader.item.saveSettings() - root.updateWidgetSettings(sectionId, root.widgetIndex, newSettings) - root.close() + root.updateWidgetSettings(sectionId, widgetSettings.widgetIndex, newSettings) + widgetSettings.close() } } } From c20773d60b0c109a38d84c4c48ed94bc1978087f Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 13:40:38 -0400 Subject: [PATCH 04/54] Prevent NPanel dragging when popup are open. --- Modules/Bar/Widgets/SidePanelToggle.qml | 8 +++++--- .../SettingsPanel/Bar/BarWidgetSettingsDialog.qml | 5 ++--- .../Bar/WidgetSettings/SidePanelToggleSettings.qml | 9 ++++----- Modules/SettingsPanel/SettingsPanel.qml | 2 +- Modules/WallpaperSelector/WallpaperSelector.qml | 3 ++- Widgets/NColorPickerDialog.qml | 13 ++++++++++--- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/Modules/Bar/Widgets/SidePanelToggle.qml b/Modules/Bar/Widgets/SidePanelToggle.qml index f2336f69..91b53406 100644 --- a/Modules/Bar/Widgets/SidePanelToggle.qml +++ b/Modules/Bar/Widgets/SidePanelToggle.qml @@ -52,9 +52,11 @@ NIconButton { width: root.width * 0.8 height: width source: { - if (customIconPath !== "") return customIconPath; - if (useDistroLogo) return DistroLogoService.osLogo; - return ""; + if (customIconPath !== "") + return customIconPath + if (useDistroLogo) + return DistroLogoService.osLogo + return "" } visible: source !== "" smooth: true diff --git a/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml b/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml index 812f223f..74b34888 100644 --- a/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml +++ b/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml @@ -26,7 +26,7 @@ Popup { height: content.implicitHeight + padding * 2 padding: Style.marginXL * scaling modal: true - + onOpened: { // Mark this popup has opened in the PanelService PanelService.willOpenPopup(widgetSettings) @@ -120,7 +120,7 @@ Popup { } } - function loadWidgetSettings() { + function loadWidgetSettings() { const widgetSettingsMap = { "ActiveWindow": "WidgetSettings/ActiveWindowSettings.qml", "Battery": "WidgetSettings/BatterySettings.qml", @@ -147,5 +147,4 @@ Popup { }) } } - } diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml index caae851b..ee31b3ee 100644 --- a/Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml +++ b/Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml @@ -38,7 +38,6 @@ ColumnLayout { } } - RowLayout { spacing: Style.marginM * scaling @@ -79,9 +78,9 @@ ColumnLayout { id: iconPicker initialIcon: valueIcon onIconSelected: iconName => { - valueIcon = iconName - valueCustomIconPath = "" - } + valueIcon = iconName + valueCustomIconPath = "" + } } NFilePicker { @@ -89,4 +88,4 @@ ColumnLayout { title: "Select a custom icon" onAccepted: paths => valueCustomIconPath = paths[0] } -} \ No newline at end of file +} diff --git a/Modules/SettingsPanel/SettingsPanel.qml b/Modules/SettingsPanel/SettingsPanel.qml index 5993f902..81772ff9 100644 --- a/Modules/SettingsPanel/SettingsPanel.qml +++ b/Modules/SettingsPanel/SettingsPanel.qml @@ -21,7 +21,7 @@ NPanel { panelKeyboardFocus: true - draggable: true + draggable: (PanelService.openedPopup === null) // Tabs enumeration, order is NOT relevant enum Tab { diff --git a/Modules/WallpaperSelector/WallpaperSelector.qml b/Modules/WallpaperSelector/WallpaperSelector.qml index 0781c11b..4828df14 100644 --- a/Modules/WallpaperSelector/WallpaperSelector.qml +++ b/Modules/WallpaperSelector/WallpaperSelector.qml @@ -18,7 +18,8 @@ NPanel { panelAnchorHorizontalCenter: true panelAnchorVerticalCenter: true panelKeyboardFocus: true - draggable: true + + draggable: (PanelService.openedPopup === null) panelContent: Rectangle { id: wallpaperPanel diff --git a/Widgets/NColorPickerDialog.qml b/Widgets/NColorPickerDialog.qml index 6cf6965f..836fb4ab 100644 --- a/Widgets/NColorPickerDialog.qml +++ b/Widgets/NColorPickerDialog.qml @@ -26,7 +26,14 @@ Popup { y: (parent.height - height) * 0.5 modal: true - clip: true + + onOpened: { + PanelService.willOpenPopup(root) + } + + onClosed: { + PanelService.willClosePopup(root) + } function rgbToHsv(r, g, b) { r /= 255 @@ -110,9 +117,9 @@ Popup { border.width: Math.max(1, Style.borderM * scaling) } - NScrollView { + contentItem: NScrollView { id: scrollView - anchors.fill: parent + width: parent.width verticalPolicy: ScrollBar.AlwaysOff horizontalPolicy: ScrollBar.AlwaysOff From be1c975f4d4451ad22c150fd839340ada3824df7 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 13:46:25 -0400 Subject: [PATCH 05/54] Prevent even more dragging when popup are open. --- Widgets/NComboBox.qml | 8 ++++++++ Widgets/NIconPicker.qml | 5 +++++ Widgets/NSearchableComboBox.qml | 8 ++++++++ 3 files changed, 21 insertions(+) diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml index 9c9b5bd2..f6ac29ba 100644 --- a/Widgets/NComboBox.qml +++ b/Widgets/NComboBox.qml @@ -89,6 +89,14 @@ RowLayout { implicitHeight: Math.min(root.popupHeight, contentItem.implicitHeight + Style.marginM * scaling * 2) padding: Style.marginM * scaling + onOpened: { + PanelService.willOpenPopup(root) + } + + onClosed: { + PanelService.willClosePopup(root) + } + contentItem: ListView { property var comboBoxRoot: root clip: true diff --git a/Widgets/NIconPicker.qml b/Widgets/NIconPicker.qml index bc6af0d8..c0ffaa49 100644 --- a/Widgets/NIconPicker.qml +++ b/Widgets/NIconPicker.qml @@ -41,6 +41,11 @@ Popup { selectedIcon = initialIcon query = initialIcon searchInput.forceActiveFocus() + PanelService.willOpenPopup(root) + } + + onClosed: { + PanelService.willClosePopup(root) } background: Rectangle { diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml index 9093f4a5..9ecb4a6e 100644 --- a/Widgets/NSearchableComboBox.qml +++ b/Widgets/NSearchableComboBox.qml @@ -153,6 +153,14 @@ RowLayout { height: root.popupHeight + 60 * scaling padding: Style.marginM * scaling + onOpened: { + PanelService.willOpenPopup(root) + } + + onClosed: { + PanelService.willClosePopup(root) + } + contentItem: ColumnLayout { spacing: Style.marginS * scaling From e0d577cbda7276baae3c6328188d966c7ca32357 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 13:47:51 -0400 Subject: [PATCH 06/54] Prevent even more dragging. --- Modules/SidePanel/Cards/MediaCard.qml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Modules/SidePanel/Cards/MediaCard.qml b/Modules/SidePanel/Cards/MediaCard.qml index f8945df1..2a9a9213 100644 --- a/Modules/SidePanel/Cards/MediaCard.qml +++ b/Modules/SidePanel/Cards/MediaCard.qml @@ -99,6 +99,14 @@ NBox { implicitHeight: Math.min(160 * scaling, contentItem.implicitHeight + Style.marginM * scaling) padding: Style.marginS * scaling + onOpened: { + PanelService.willOpenPopup(root) + } + + onClosed: { + PanelService.willClosePopup(root) + } + contentItem: ListView { clip: true implicitHeight: contentHeight From f896b41c6bb1eaaece64fb59346424ba80383ee7 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 13:49:03 -0400 Subject: [PATCH 07/54] Dock: removed onCountChanged as it is unecessary and was producing warnings. --- Modules/Dock/Dock.qml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 11f633f5..bb4fded2 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -34,14 +34,6 @@ Variants { } } - // Also listen to model changes (for ObjectModel) - Connections { - target: ToplevelManager ? ToplevelManager.toplevels : null - function onCountChanged() { - updateDockApps() - } - } - // Update dock apps when pinned apps change Connections { target: Settings.data.dock From 03da290c546bb04883adba72bc90c75b3a59da44 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 13:59:19 -0400 Subject: [PATCH 08/54] Notifications History: restored original panel width, changed title to: "Notifications" --- Modules/Notification/NotificationHistoryPanel.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/Notification/NotificationHistoryPanel.qml b/Modules/Notification/NotificationHistoryPanel.qml index 1fe59c93..35f92d0a 100644 --- a/Modules/Notification/NotificationHistoryPanel.qml +++ b/Modules/Notification/NotificationHistoryPanel.qml @@ -12,7 +12,7 @@ import qs.Widgets NPanel { id: root - preferredWidth: 360 + preferredWidth: 380 preferredHeight: 480 panelKeyboardFocus: true @@ -37,7 +37,7 @@ NPanel { } NText { - text: "Notification history" + text: "Notifications" font.pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface From 6fbaf46ed9fa6189f8bdb98e00365dc76191df49 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 14:58:34 -0400 Subject: [PATCH 09/54] AppIcons => ThemeIcons --- Commons/{AppIcons.qml => ThemeIcons.qml} | 0 Modules/Bar/Widgets/ActiveWindow.qml | 4 ++-- Modules/Bar/Widgets/Taskbar.qml | 2 +- Modules/Dock/Dock.qml | 2 +- Modules/Launcher/Launcher.qml | 2 +- Services/NotificationService.qml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename Commons/{AppIcons.qml => ThemeIcons.qml} (100%) diff --git a/Commons/AppIcons.qml b/Commons/ThemeIcons.qml similarity index 100% rename from Commons/AppIcons.qml rename to Commons/ThemeIcons.qml diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 5e6eb2f8..fc25a9f4 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -86,7 +86,7 @@ Item { try { const idValue = focusedWindow.appId const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue) - const iconResult = AppIcons.iconForAppId(normalizedId.toLowerCase()) + const iconResult = ThemeIcons.iconForAppId(normalizedId.toLowerCase()) if (iconResult && iconResult !== "") { return iconResult } @@ -102,7 +102,7 @@ Item { if (activeToplevel.appId) { const idValue2 = activeToplevel.appId const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2) - const iconResult2 = AppIcons.iconForAppId(normalizedId2.toLowerCase()) + const iconResult2 = ThemeIcons.iconForAppId(normalizedId2.toLowerCase()) if (iconResult2 && iconResult2 !== "") { return iconResult2 } diff --git a/Modules/Bar/Widgets/Taskbar.qml b/Modules/Bar/Widgets/Taskbar.qml index 71c11f67..63e7326d 100644 --- a/Modules/Bar/Widgets/Taskbar.qml +++ b/Modules/Bar/Widgets/Taskbar.qml @@ -68,7 +68,7 @@ Rectangle { anchors.centerIn: parent width: parent.width height: parent.height - source: AppIcons.iconForAppId(taskbarItem.modelData.appId) + source: ThemeIcons.iconForAppId(taskbarItem.modelData.appId) smooth: true asynchronous: true } diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index bb4fded2..52f21800 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -347,7 +347,7 @@ Variants { function getAppIcon(appData): string { if (!appData || !appData.appId) return "" - return AppIcons.iconForAppId(appData.appId?.toLowerCase()) + return ThemeIcons.iconForAppId(appData.appId?.toLowerCase()) } RowLayout { diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index 8789ee25..ba1855e3 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -397,7 +397,7 @@ NPanel { sourceComponent: Component { IconImage { anchors.fill: parent - source: modelData.icon ? AppIcons.iconFromName(modelData.icon, "application-x-executable") : "" + source: modelData.icon ? ThemeIcons.iconFromName(modelData.icon, "application-x-executable") : "" visible: modelData.icon && source !== "" asynchronous: true } diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml index be537f22..8f0999dd 100644 --- a/Services/NotificationService.qml +++ b/Services/NotificationService.qml @@ -304,7 +304,7 @@ Singleton { return "" if (icon.startsWith("/") || icon.startsWith("file://")) return icon - return AppIcons.iconFromName(icon) + return ThemeIcons.iconFromName(icon) } function stripTags(text) { From 4a4cd2055363238778ca909ee4c2f74bfed87a2a Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 16:01:15 -0400 Subject: [PATCH 10/54] ActiveWindow: Fix #338 --- Services/CompositorService.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/CompositorService.qml b/Services/CompositorService.qml index 8fc7c935..ca4ccb3e 100644 --- a/Services/CompositorService.qml +++ b/Services/CompositorService.qml @@ -114,9 +114,9 @@ Singleton { // Get window title for focused window function getFocusedWindowTitle() { if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.length) { - return windows[focusedWindowIndex].title || "(Unnamed window)" + return windows[focusedWindowIndex].title || "Unnamed window" } - return "(No active window)" + return "" } // Generic workspace switching From 773912320f7198d69884dfef964958ee3eeef994 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 22 Sep 2025 22:19:43 +0200 Subject: [PATCH 11/54] LockScreen: fix expanding password --- Modules/LockScreen/LockScreen.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index a9ac0235..cabad9f0 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -364,6 +364,7 @@ Loader { Rectangle { id: terminalBackground anchors.fill: parent + clip: true radius: Style.radiusM * scaling color: Qt.alpha(Color.mSurface, 0.9) border.color: Color.mPrimary @@ -565,6 +566,11 @@ Loader { font.family: Settings.data.ui.fontFixed font.pointSize: Style.fontSizeL * scaling visible: passwordInput.activeFocus && !lockContext.unlockInProgress + Layout.fillWidth: true + wrapMode: Text.NoWrap + maximumLineCount: 1 + elide: Text.ElideRight + clip: true SequentialAnimation { id: typingEffect @@ -643,6 +649,7 @@ Loader { RowLayout { Layout.alignment: Qt.AlignRight Layout.bottomMargin: -10 * scaling + Layout.fillWidth: true Rectangle { Layout.preferredWidth: 120 * scaling Layout.preferredHeight: 40 * scaling From 51fb5b9f4abd119621f618d067b63261c1cefd1b Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 22 Sep 2025 22:23:39 +0200 Subject: [PATCH 12/54] ActiveWindow: hide ActiveWindow if there is no actual window --- Modules/Bar/Widgets/ActiveWindow.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index fc25a9f4..18cba41d 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -31,6 +31,7 @@ Item { } readonly property string windowTitle: CompositorService.getFocusedWindowTitle() + readonly property bool hasActiveWindow: windowTitle !== "" && windowTitle !== "(No active window)" readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon @@ -52,7 +53,7 @@ Item { implicitHeight: (barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling) implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * 0.8 * scaling) : (horizontalLayout.implicitWidth + Style.marginM * 2 * scaling) - visible: windowTitle !== "" + visible: hasActiveWindow function calculatedVerticalHeight() { // Use standard widget height like other widgets From 17308083fea34b768c5780b41e8b379c88a4b382 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 22 Sep 2025 22:25:01 +0200 Subject: [PATCH 13/54] Revert "ActiveWindow: hide ActiveWindow if there is no actual window" This reverts commit 51fb5b9f4abd119621f618d067b63261c1cefd1b. --- Modules/Bar/Widgets/ActiveWindow.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 18cba41d..fc25a9f4 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -31,7 +31,6 @@ Item { } readonly property string windowTitle: CompositorService.getFocusedWindowTitle() - readonly property bool hasActiveWindow: windowTitle !== "" && windowTitle !== "(No active window)" readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon @@ -53,7 +52,7 @@ Item { implicitHeight: (barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling) implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * 0.8 * scaling) : (horizontalLayout.implicitWidth + Style.marginM * 2 * scaling) - visible: hasActiveWindow + visible: windowTitle !== "" function calculatedVerticalHeight() { // Use standard widget height like other widgets From 4d0777ab934147c39ee875b0abe4117601e138a9 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 22 Sep 2025 22:27:20 +0200 Subject: [PATCH 14/54] Let people use scrollwheel to switch between workspaces (fixes #290) --- Modules/Bar/Widgets/Workspace.qml | 66 +++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/Modules/Bar/Widgets/Workspace.qml b/Modules/Bar/Widgets/Workspace.qml index 628b17e9..1721c30d 100644 --- a/Modules/Bar/Widgets/Workspace.qml +++ b/Modules/Bar/Widgets/Workspace.qml @@ -56,6 +56,10 @@ Item { property int horizontalPadding: Math.round(Style.marginS * scaling) property int spacingBetweenPills: Math.round(Style.marginXS * scaling) + // Wheel scroll handling + property int wheelAccumulatedDelta: 0 + property bool wheelCooldown: false + signal workspaceChanged(int workspaceId, color accentColor) implicitWidth: isVertical ? Math.round(Style.barHeight * scaling) : computeWidth() @@ -95,6 +99,28 @@ Item { return Math.round(total) } + function getFocusedLocalIndex() { + for (var i = 0; i < localWorkspaces.count; i++) { + if (localWorkspaces.get(i).isFocused === true) + return i + } + return -1 + } + + function switchByOffset(offset) { + if (localWorkspaces.count === 0) + return + var current = getFocusedLocalIndex() + if (current < 0) + current = 0 + var next = (current + offset) % localWorkspaces.count + if (next < 0) + next = localWorkspaces.count - 1 + const ws = localWorkspaces.get(next) + if (ws && ws.idx !== undefined) + CompositorService.switchToWorkspace(ws.idx) + } + Component.onCompleted: { refreshWorkspaces() } @@ -185,6 +211,46 @@ Item { anchors.verticalCenter: parent.verticalCenter } + // Debounce timer for wheel interactions + Timer { + id: wheelDebounce + interval: 150 + repeat: false + onTriggered: { + root.wheelCooldown = false + root.wheelAccumulatedDelta = 0 + } + } + + // Scroll to switch workspaces + WheelHandler { + id: wheelHandler + target: root + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: function (event) { + if (root.wheelCooldown) + return + // Prefer vertical delta, fall back to horizontal if needed + var dy = event.angleDelta.y + var dx = event.angleDelta.x + var useDy = Math.abs(dy) >= Math.abs(dx) + var delta = useDy ? dy : dx + // One notch is typically 120 + root.wheelAccumulatedDelta += delta + var step = 120 + if (Math.abs(root.wheelAccumulatedDelta) >= step) { + var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1 + // For vertical layout, natural mapping: wheel up -> previous, down -> next (already handled by sign) + // For horizontal layout, same mapping using vertical wheel + root.switchByOffset(direction) + root.wheelCooldown = true + wheelDebounce.restart() + root.wheelAccumulatedDelta = 0 + event.accepted = true + } + } + } + // Horizontal layout for top/bottom bars Row { id: pillRow From 6031c97e1ad8f0722355923c4fdbae1458924327 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 22 Sep 2025 22:47:16 +0200 Subject: [PATCH 15/54] ScreenRecorder: add toast for record stop/start/error --- Services/ScreenRecorderService.qml | 53 +++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/Services/ScreenRecorderService.qml b/Services/ScreenRecorderService.qml index 8a5512bd..e4d13543 100644 --- a/Services/ScreenRecorderService.qml +++ b/Services/ScreenRecorderService.qml @@ -12,6 +12,8 @@ Singleton { readonly property var settings: Settings.data.screenRecorder property bool isRecording: false property bool isPending: false + // True only if the recorder actually started capturing at least once + property bool hasActiveRecording: false property string outputPath: "" property bool isAvailable: ProgramCheckerService.gpuScreenRecorderAvailable @@ -36,7 +38,16 @@ Singleton { return } isPending = true + hasActiveRecording = false + // First, ensure xdg-desktop-portal and a compositor portal are running + portalCheckProcess.exec({ + "command": ["sh", "-c", // require core portal AND one of the backends + "pidof xdg-desktop-portal >/dev/null 2>&1 && (pidof xdg-desktop-portal-wlr >/dev/null 2>&1 || pidof xdg-desktop-portal-hyprland >/dev/null 2>&1 || pidof xdg-desktop-portal-gnome >/dev/null 2>&1 || pidof xdg-desktop-portal-kde >/dev/null 2>&1)"] + }) + } + + function launchRecorder() { var filename = Time.getFormattedTimestamp() + ".mp4" var videoDir = settings.directory if (videoDir && !videoDir.endsWith("/")) { @@ -56,7 +67,7 @@ Singleton { notify-send "gpu-screen-recorder not installed!" -u critical fi` - // Use Process instead of execDetached so we can monitor it + // Use Process instead of execDetached so we can monitor it and read stderr recorderProcess.exec({ "command": ["sh", "-c", command] }) @@ -71,12 +82,15 @@ Singleton { return } + ToastService.showNotice("Stopping recording…", outputPath, 2000) + Quickshell.execDetached(["sh", "-c", "pkill -SIGINT -f 'gpu-screen-recorder' || pkill -SIGINT -f 'com.dec05eba.gpu_screen_recorder'"]) isRecording = false isPending = false pendingTimer.running = false monitorTimer.running = false + hasActiveRecording = false // Just in case, force kill after 3 seconds killTimer.running = true @@ -85,15 +99,50 @@ Singleton { // Process to run and monitor gpu-screen-recorder Process { id: recorderProcess + stdout: StdioCollector {} + stderr: StdioCollector {} onExited: function (exitCode, exitStatus) { if (isPending) { // Process ended while we were pending - likely cancelled or error isPending = false pendingTimer.running = false + // If it failed to start, show a clear error toast with stderr + if (exitCode !== 0) { + const err = String(stderr.text || "").trim() + if (err.length > 0) + ToastService.showError("Failed to start recording", err, 7000) + else + ToastService.showError("Failed to start recording", "gpu-screen-recorder exited unexpectedly.", 7000) + } } else if (isRecording) { // Process ended normally while recording isRecording = false monitorTimer.running = false + // Consider successful save if exitCode == 0 + if (exitCode === 0) { + ToastService.showNotice("Recording saved", outputPath, 5000) + } else { + const err2 = String(stderr.text || "").trim() + if (err2.length > 0) + ToastService.showError("Recording failed", err2, 7000) + else + ToastService.showError("Recording failed", "The recorder exited with an error.", 7000) + } + } + } + } + + // Pre-flight check for xdg-desktop-portal + Process { + id: portalCheckProcess + onExited: function (exitCode, exitStatus) { + if (exitCode === 0) { + // Portals available, proceed to launch + launchRecorder() + } else { + isPending = false + hasActiveRecording = false + ToastService.showError("Desktop portals not running", "Start xdg-desktop-portal and a compositor portal (wlr/hyprland/gnome/kde).", 8000) } } } @@ -108,7 +157,9 @@ Singleton { // Process is still running after 2 seconds - assume recording started successfully isPending = false isRecording = true + hasActiveRecording = true monitorTimer.running = true + ToastService.showNotice("Recording started", outputPath, 4000) } else if (isPending) { // Process not running anymore - was cancelled or failed isPending = false From 490200b3b8d9066765652fd5268219b1b98455a8 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 22 Sep 2025 22:50:58 +0200 Subject: [PATCH 16/54] ActiveWindow: properly hide when no window is available --- Modules/Bar/Widgets/ActiveWindow.qml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index fc25a9f4..547b0588 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -31,6 +31,10 @@ Item { } readonly property string windowTitle: CompositorService.getFocusedWindowTitle() + readonly property bool hasActiveWindow: { + const t = (windowTitle || "").trim() + return t.length > 0 && t !== "(No active window)" + } readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon @@ -52,7 +56,7 @@ Item { implicitHeight: (barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling) implicitWidth: (barPosition === "left" || barPosition === "right") ? Math.round(Style.capsuleHeight * 0.8 * scaling) : (horizontalLayout.implicitWidth + Style.marginM * 2 * scaling) - visible: windowTitle !== "" + visible: hasActiveWindow function calculatedVerticalHeight() { // Use standard widget height like other widgets @@ -159,7 +163,7 @@ Item { Layout.preferredWidth: Style.capsuleHeight * 0.75 * scaling Layout.preferredHeight: Style.capsuleHeight * 0.75 * scaling Layout.alignment: Qt.AlignVCenter - visible: windowTitle !== "" && showIcon + visible: hasActiveWindow && showIcon IconImage { id: windowIcon @@ -217,7 +221,7 @@ Item { anchors.centerIn: parent width: parent.width - Style.marginXS * scaling * 2 height: parent.height - Style.marginXS * scaling * 2 - visible: barPosition === "left" || barPosition === "right" + visible: (barPosition === "left" || barPosition === "right") && hasActiveWindow // Window icon Item { From 4c9d40865fd263d3cb5b77ac9e9895d68361e4a3 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 22 Sep 2025 23:20:59 +0200 Subject: [PATCH 17/54] NText: add elide (ltr & rtl) --- Widgets/NText.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Widgets/NText.qml b/Widgets/NText.qml index c15198d1..82d533a5 100644 --- a/Widgets/NText.qml +++ b/Widgets/NText.qml @@ -1,4 +1,5 @@ import QtQuick +import QtQuick.Layouts import qs.Commons import qs.Services import qs.Widgets @@ -14,4 +15,6 @@ Text { color: Color.mOnSurface renderType: Text.QtRendering verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + wrapMode: Text.NoWrap } From 21d331c232ba5ae8565396dc9973e2f63f2ab61d Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 17:37:34 -0400 Subject: [PATCH 18/54] ActiveWindow: more cleanup --- Modules/Bar/Widgets/ActiveWindow.qml | 5 +---- Services/CompositorService.qml | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 547b0588..933d75f4 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -31,10 +31,7 @@ Item { } readonly property string windowTitle: CompositorService.getFocusedWindowTitle() - readonly property bool hasActiveWindow: { - const t = (windowTitle || "").trim() - return t.length > 0 && t !== "(No active window)" - } + readonly property bool hasActiveWindow: windowTitle !== "" readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon diff --git a/Services/CompositorService.qml b/Services/CompositorService.qml index ca4ccb3e..d2064350 100644 --- a/Services/CompositorService.qml +++ b/Services/CompositorService.qml @@ -114,7 +114,7 @@ Singleton { // Get window title for focused window function getFocusedWindowTitle() { if (focusedWindowIndex >= 0 && focusedWindowIndex < windows.length) { - return windows[focusedWindowIndex].title || "Unnamed window" + return windows[focusedWindowIndex].title || "" } return "" } From 6a8c3c721a382d429ba12a34a2e818d14178aecb Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 17:49:05 -0400 Subject: [PATCH 19/54] TablerIcons at root of Commons/ --- Commons/Icons.qml | 1 - Commons/{IconsSets => }/TablerIcons.qml | 0 2 files changed, 1 deletion(-) rename Commons/{IconsSets => }/TablerIcons.qml (100%) diff --git a/Commons/Icons.qml b/Commons/Icons.qml index 93ed5d96..70f0934e 100644 --- a/Commons/Icons.qml +++ b/Commons/Icons.qml @@ -4,7 +4,6 @@ import QtQuick import QtQuick.Controls import Quickshell import qs.Commons -import qs.Commons.IconsSets Singleton { id: root diff --git a/Commons/IconsSets/TablerIcons.qml b/Commons/TablerIcons.qml similarity index 100% rename from Commons/IconsSets/TablerIcons.qml rename to Commons/TablerIcons.qml From 489ce76d2a2524624c9fc92579e9e7a4fd1366d0 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Mon, 22 Sep 2025 23:56:18 +0200 Subject: [PATCH 20/54] Notification: layout changes --- Modules/Dock/Dock.qml | 28 +++++++++++++++++++------- Modules/Dock/DockMenu.qml | 42 +++++++++++++++++++++------------------ 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 52f21800..730516b6 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -454,7 +454,13 @@ Variants { id: contextMenu scaling: root.scaling onHoveredChanged: menuHovered = hovered - onRequestClose: contextMenu.hide() + onRequestClose: { + contextMenu.hide() + // Restart hide timer after menu action if auto-hide is enabled + if (autoHide && !dockHovered && !anyAppHovered && !peekHovered) { + hideTimer.restart() + } + } onAppClosed: root.updateDockApps // Force immediate dock update when app is closed onVisibleChanged: { if (visible) { @@ -493,11 +499,23 @@ Variants { } onClicked: function (mouse) { - // Close any existing context menu first - if (mouse.button !== Qt.RightButton || root.currentContextMenu !== contextMenu) { + if (mouse.button === Qt.RightButton) { + // If right-clicking on the same app with an open context menu, close it + if (root.currentContextMenu === contextMenu && contextMenu.visible) { + root.closeAllContextMenus() + return + } + // Close any other existing context menu first root.closeAllContextMenus() + // Hide tooltip when showing context menu + appTooltip.hide() + contextMenu.show(appButton, modelData.toplevel || modelData) + return } + // Close any existing context menu for non-right-click actions + root.closeAllContextMenus() + // Check if toplevel is still valid (not a stale reference) const isValidToplevel = modelData?.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(modelData.toplevel) @@ -512,10 +530,6 @@ Variants { // Pinned app not running - launch it Quickshell.execDetached(["gtk-launch", modelData.appId]) } - } else if (mouse.button === Qt.RightButton) { - // Hide tooltip when showing context menu - appTooltip.hide() - contextMenu.show(appButton, modelData.toplevel || modelData) } } } diff --git a/Modules/Dock/DockMenu.qml b/Modules/Dock/DockMenu.qml index f4b78ae5..5b8cebd9 100644 --- a/Modules/Dock/DockMenu.qml +++ b/Modules/Dock/DockMenu.qml @@ -19,7 +19,7 @@ PopupWindow { signal requestClose - implicitWidth: 160 * scaling + implicitWidth: 140 * scaling implicitHeight: contextMenuColumn.implicitHeight + (Style.marginM * scaling * 2) color: Color.transparent visible: false @@ -75,7 +75,14 @@ PopupWindow { id: menuMouseArea anchors.fill: parent hoverEnabled: true - onClicked: root.hide() // Close when clicking on the background (outside menu content) + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: function (mouse) { + if (mouse.button === Qt.RightButton) { + root.hide() // Close on right-click + } else { + root.hide() // Close when clicking on the background (outside menu content) + } + } } Shortcut { @@ -105,11 +112,11 @@ PopupWindow { anchors.margins: Style.marginM * scaling spacing: 0 - // Activate/Focus item + // Focus item Rectangle { width: parent.width height: 32 * scaling - color: activateMouseArea.containsMouse ? Qt.alpha(Color.mSecondary, 0.2) : Color.transparent + color: activateMouseArea.containsMouse ? Color.mTertiary : Color.transparent radius: Style.radiusXS * scaling Row { @@ -121,20 +128,14 @@ PopupWindow { NIcon { icon: "eye" font.pointSize: Style.fontSizeL * scaling - color: Color.mOnSurface + color: activateMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface anchors.verticalCenter: parent.verticalCenter } NText { - text: { - if (!root.toplevel) - return "Activate" - // Check if this toplevel is active by comparing with ToplevelManager.activeToplevel - const isActive = ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === root.toplevel - return isActive ? "Focus" : "Activate" - } + text: "Focus" font.pointSize: Style.fontSizeS * scaling - color: Color.mOnSurface + color: activateMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface anchors.verticalCenter: parent.verticalCenter } } @@ -150,6 +151,7 @@ PopupWindow { root.toplevel.activate() } root.hide() + root.requestClose() } } } @@ -158,7 +160,7 @@ PopupWindow { Rectangle { width: parent.width height: 32 * scaling - color: pinMouseArea.containsMouse ? Qt.alpha(Color.mTertiary, 0.2) : Color.transparent + color: pinMouseArea.containsMouse ? Color.mTertiary : Color.transparent radius: Style.radiusXS * scaling Row { @@ -174,7 +176,7 @@ PopupWindow { return root.isAppPinned(root.toplevel.appId) ? "pinned-off" : "pin" } font.pointSize: Style.fontSizeL * scaling - color: Color.mOnSurface + color: pinMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface anchors.verticalCenter: parent.verticalCenter } @@ -185,7 +187,7 @@ PopupWindow { return root.isAppPinned(root.toplevel.appId) ? "Unpin" : "Pin" } font.pointSize: Style.fontSizeS * scaling - color: Color.mOnSurface + color: pinMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface anchors.verticalCenter: parent.verticalCenter } } @@ -201,6 +203,7 @@ PopupWindow { root.toggleAppPin(root.toplevel.appId) } root.hide() + root.requestClose() } } } @@ -209,7 +212,7 @@ PopupWindow { Rectangle { width: parent.width height: 32 * scaling - color: closeMouseArea.containsMouse ? Qt.alpha(Color.mPrimary, 0.2) : Color.transparent + color: closeMouseArea.containsMouse ? Color.mTertiary : Color.transparent radius: Style.radiusXS * scaling Row { @@ -221,14 +224,14 @@ PopupWindow { NIcon { icon: "x" font.pointSize: Style.fontSizeL * scaling - color: closeMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurface + color: closeMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface anchors.verticalCenter: parent.verticalCenter } NText { text: "Close" font.pointSize: Style.fontSizeS * scaling - color: closeMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurface + color: closeMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface anchors.verticalCenter: parent.verticalCenter } } @@ -253,6 +256,7 @@ PopupWindow { Logger.warn("DockMenu", "Cannot close app - invalid toplevel reference") } root.hide() + root.requestClose() } } } From 202516aee37664266533564f234de59d2da55aa7 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Tue, 23 Sep 2025 00:01:31 +0200 Subject: [PATCH 21/54] Dock: fix pinned app grouping --- Modules/Dock/Dock.qml | 46 +++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 730516b6..be19085b 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -100,19 +100,28 @@ Variants { const runningApps = ToplevelManager ? (ToplevelManager.toplevels.values || []) : [] const pinnedApps = Settings.data.dock.pinnedApps || [] const combined = [] + const processedAppIds = new Set() - // First, add pinned apps (both running and non-running) in their pinned order + // Strategy: Maintain app positions as much as possible + // 1. First pass: Add all running apps (both pinned and non-pinned) in their current order + runningApps.forEach(toplevel => { + if (toplevel && toplevel.appId) { + const isPinned = pinnedApps.includes(toplevel.appId) + const appType = isPinned ? "pinned-running" : "running" + + combined.push({ + "type": appType, + "toplevel": toplevel, + "appId": toplevel.appId, + "title": toplevel.title + }) + processedAppIds.add(toplevel.appId) + } + }) + + // 2. Second pass: Add non-running pinned apps at the end pinnedApps.forEach(pinnedAppId => { - const runningApp = runningApps.find(toplevel => toplevel && toplevel.appId === pinnedAppId) - if (runningApp) { - // Pinned app that is currently running - combined.push({ - "type": "pinned-running", - "toplevel": runningApp, - "appId": runningApp.appId, - "title": runningApp.title - }) - } else { + if (!processedAppIds.has(pinnedAppId)) { // Pinned app that is not running combined.push({ "type": "pinned", @@ -123,21 +132,6 @@ Variants { } }) - // Then, add running apps that are not pinned - runningApps.forEach(toplevel => { - if (toplevel && toplevel.appId) { - const isPinned = pinnedApps.includes(toplevel.appId) - if (!isPinned) { - combined.push({ - "type": "running", - "toplevel": toplevel, - "appId": toplevel.appId, - "title": toplevel.title - }) - } - } - }) - dockApps = combined } From 609f1e9655920cb56e70b0d67c69134355551be5 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Tue, 23 Sep 2025 00:20:06 +0200 Subject: [PATCH 22/54] Bar/: refactor layout --- .../BluetoothPanel/BluetoothDevicesList.qml | 0 .../BluetoothPanel/BluetoothPanel.qml | 0 Modules/{ => Bar}/PowerPanel/PowerPanel.qml | 0 Modules/{ => Bar}/WiFiPanel/WiFiPanel.qml | 0 Modules/Dock/Dock.qml | 8 +++++- shell.qml | 28 +++++++++++++------ 6 files changed, 26 insertions(+), 10 deletions(-) rename Modules/{ => Bar}/BluetoothPanel/BluetoothDevicesList.qml (100%) rename Modules/{ => Bar}/BluetoothPanel/BluetoothPanel.qml (100%) rename Modules/{ => Bar}/PowerPanel/PowerPanel.qml (100%) rename Modules/{ => Bar}/WiFiPanel/WiFiPanel.qml (100%) diff --git a/Modules/BluetoothPanel/BluetoothDevicesList.qml b/Modules/Bar/BluetoothPanel/BluetoothDevicesList.qml similarity index 100% rename from Modules/BluetoothPanel/BluetoothDevicesList.qml rename to Modules/Bar/BluetoothPanel/BluetoothDevicesList.qml diff --git a/Modules/BluetoothPanel/BluetoothPanel.qml b/Modules/Bar/BluetoothPanel/BluetoothPanel.qml similarity index 100% rename from Modules/BluetoothPanel/BluetoothPanel.qml rename to Modules/Bar/BluetoothPanel/BluetoothPanel.qml diff --git a/Modules/PowerPanel/PowerPanel.qml b/Modules/Bar/PowerPanel/PowerPanel.qml similarity index 100% rename from Modules/PowerPanel/PowerPanel.qml rename to Modules/Bar/PowerPanel/PowerPanel.qml diff --git a/Modules/WiFiPanel/WiFiPanel.qml b/Modules/Bar/WiFiPanel/WiFiPanel.qml similarity index 100% rename from Modules/WiFiPanel/WiFiPanel.qml rename to Modules/Bar/WiFiPanel/WiFiPanel.qml diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index be19085b..f6135c37 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -108,7 +108,7 @@ Variants { if (toplevel && toplevel.appId) { const isPinned = pinnedApps.includes(toplevel.appId) const appType = isPinned ? "pinned-running" : "running" - + combined.push({ "type": appType, "toplevel": toplevel, @@ -461,6 +461,12 @@ Variants { root.currentContextMenu = contextMenu } else if (root.currentContextMenu === contextMenu) { root.currentContextMenu = null + // Reset menu hover state when menu becomes invisible + menuHovered = false + // Restart hide timer if conditions are met + if (autoHide && !dockHovered && !anyAppHovered && !peekHovered) { + hideTimer.restart() + } } } } diff --git a/shell.qml b/shell.qml index 22f4f521..86ca9acc 100644 --- a/shell.qml +++ b/shell.qml @@ -7,30 +7,40 @@ */ // Disable reload popup add this as a new row: //pragma Env QS_NO_RELOAD_POPUP=1 + +// Qt & Quickshell Core import QtQuick import Quickshell import Quickshell.Io import Quickshell.Services.Pipewire import Quickshell.Widgets + +// Commons & Services import qs.Commons -import qs.Modules.Launcher +import qs.Services +import qs.Widgets + +// Core Modules import qs.Modules.Background -import qs.Modules.Bar -import qs.Modules.Bar.Extras -import qs.Modules.BluetoothPanel -import qs.Modules.Calendar import qs.Modules.Dock import qs.Modules.IPC import qs.Modules.LockScreen + +// Bar & Bar Components +import qs.Modules.Bar +import qs.Modules.Bar.Extras +import qs.Modules.Bar.BluetoothPanel +import qs.Modules.Bar.PowerPanel +import qs.Modules.Bar.WiFiPanel + +// Panels & UI Components +import qs.Modules.Calendar +import qs.Modules.Launcher import qs.Modules.Notification import qs.Modules.SettingsPanel -import qs.Modules.PowerPanel import qs.Modules.SidePanel import qs.Modules.Toast -import qs.Modules.WiFiPanel import qs.Modules.WallpaperSelector -import qs.Services -import qs.Widgets ShellRoot { id: shellRoot From 39b52eb17eba2d2a37c3eb4d3eb409727c1aac54 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Tue, 23 Sep 2025 00:21:43 +0200 Subject: [PATCH 23/54] Bar/: remove Panel suffix --- .../{BluetoothPanel => Bluetooth}/BluetoothDevicesList.qml | 0 .../Bar/{BluetoothPanel => Bluetooth}/BluetoothPanel.qml | 0 Modules/Bar/{PowerPanel => Power}/PowerPanel.qml | 0 Modules/Bar/{WiFiPanel => WiFi}/WiFiPanel.qml | 0 shell.qml | 6 +++--- 5 files changed, 3 insertions(+), 3 deletions(-) rename Modules/Bar/{BluetoothPanel => Bluetooth}/BluetoothDevicesList.qml (100%) rename Modules/Bar/{BluetoothPanel => Bluetooth}/BluetoothPanel.qml (100%) rename Modules/Bar/{PowerPanel => Power}/PowerPanel.qml (100%) rename Modules/Bar/{WiFiPanel => WiFi}/WiFiPanel.qml (100%) diff --git a/Modules/Bar/BluetoothPanel/BluetoothDevicesList.qml b/Modules/Bar/Bluetooth/BluetoothDevicesList.qml similarity index 100% rename from Modules/Bar/BluetoothPanel/BluetoothDevicesList.qml rename to Modules/Bar/Bluetooth/BluetoothDevicesList.qml diff --git a/Modules/Bar/BluetoothPanel/BluetoothPanel.qml b/Modules/Bar/Bluetooth/BluetoothPanel.qml similarity index 100% rename from Modules/Bar/BluetoothPanel/BluetoothPanel.qml rename to Modules/Bar/Bluetooth/BluetoothPanel.qml diff --git a/Modules/Bar/PowerPanel/PowerPanel.qml b/Modules/Bar/Power/PowerPanel.qml similarity index 100% rename from Modules/Bar/PowerPanel/PowerPanel.qml rename to Modules/Bar/Power/PowerPanel.qml diff --git a/Modules/Bar/WiFiPanel/WiFiPanel.qml b/Modules/Bar/WiFi/WiFiPanel.qml similarity index 100% rename from Modules/Bar/WiFiPanel/WiFiPanel.qml rename to Modules/Bar/WiFi/WiFiPanel.qml diff --git a/shell.qml b/shell.qml index 86ca9acc..dfc3673f 100644 --- a/shell.qml +++ b/shell.qml @@ -29,9 +29,9 @@ import qs.Modules.LockScreen // Bar & Bar Components import qs.Modules.Bar import qs.Modules.Bar.Extras -import qs.Modules.Bar.BluetoothPanel -import qs.Modules.Bar.PowerPanel -import qs.Modules.Bar.WiFiPanel +import qs.Modules.Bar.Bluetooth +import qs.Modules.Bar.Power +import qs.Modules.Bar.WiFi // Panels & UI Components import qs.Modules.Calendar From 9a7fb4a219d55a536aa786ce0f68a2d3541db26d Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Tue, 23 Sep 2025 00:24:22 +0200 Subject: [PATCH 24/54] Bar/: add Calendar folder --- Modules/{ => Bar}/Calendar/Calendar.qml | 0 shell.qml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename Modules/{ => Bar}/Calendar/Calendar.qml (100%) diff --git a/Modules/Calendar/Calendar.qml b/Modules/Bar/Calendar/Calendar.qml similarity index 100% rename from Modules/Calendar/Calendar.qml rename to Modules/Bar/Calendar/Calendar.qml diff --git a/shell.qml b/shell.qml index dfc3673f..e021743b 100644 --- a/shell.qml +++ b/shell.qml @@ -30,11 +30,11 @@ import qs.Modules.LockScreen import qs.Modules.Bar import qs.Modules.Bar.Extras import qs.Modules.Bar.Bluetooth +import qs.Modules.Bar.Calendar import qs.Modules.Bar.Power import qs.Modules.Bar.WiFi // Panels & UI Components -import qs.Modules.Calendar import qs.Modules.Launcher import qs.Modules.Notification import qs.Modules.SettingsPanel From b8f4401878ef3119cf0976779b4f05c17fd6cb99 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 19:11:10 -0400 Subject: [PATCH 25/54] First pass --- Modules/Background/Background.qml | 1 - .../{Calendar.qml => CalendarPanel.qml} | 0 Modules/Bar/Widgets/Brightness.qml | 4 +-- Modules/Bar/Widgets/CustomButton.qml | 2 +- Modules/Bar/Widgets/Microphone.qml | 2 +- Modules/Bar/Widgets/NightLight.qml | 2 +- Modules/Bar/Widgets/SidePanelToggle.qml | 2 +- Modules/Bar/Widgets/Volume.qml | 2 +- Modules/Bar/Widgets/WallpaperSelector.qml | 2 +- .../Cards/MediaCard.qml | 0 .../Cards/PowerProfilesCard.qml | 0 .../Cards/ProfileCard.qml | 8 +++--- .../Cards/SystemMonitorCard.qml | 0 .../Cards/UtilitiesCard.qml | 8 +++--- .../Cards/WeatherCard.qml | 0 .../ControlCenterPanel.qml} | 2 +- .../Bar/BarSectionEditor.qml | 0 .../Bar/BarWidgetSettingsDialog.qml | 0 .../WidgetSettings/ActiveWindowSettings.qml | 0 .../Bar/WidgetSettings/BatterySettings.qml | 0 .../Bar/WidgetSettings/BrightnessSettings.qml | 0 .../Bar/WidgetSettings/ClockSettings.qml | 0 .../WidgetSettings/CustomButtonSettings.qml | 0 .../WidgetSettings/KeyboardLayoutSettings.qml | 0 .../Bar/WidgetSettings/MediaMiniSettings.qml | 0 .../Bar/WidgetSettings/MicrophoneSettings.qml | 0 .../NotificationHistorySettings.qml | 0 .../SidePanelToggleSettings.qml | 0 .../Bar/WidgetSettings/SpacerSettings.qml | 0 .../WidgetSettings/SystemMonitorSettings.qml | 0 .../Bar/WidgetSettings/VolumeSettings.qml | 0 .../Bar/WidgetSettings/WorkspaceSettings.qml | 0 .../SettingsPanel.qml | 2 +- .../Tabs/AboutTab.qml | 0 .../Tabs/AudioTab.qml | 0 .../Tabs/BarTab.qml | 2 +- .../Tabs/ColorSchemeTab.qml | 0 .../Tabs/DisplayTab.qml | 0 .../Tabs/DockTab.qml | 0 .../Tabs/GeneralTab.qml | 0 .../Tabs/HooksTab.qml | 0 .../Tabs/LauncherTab.qml | 0 .../Tabs/LocationTab.qml | 0 .../Tabs/NetworkTab.qml | 0 .../Tabs/NotificationsTab.qml | 0 .../Tabs/ScreenRecorderTab.qml | 0 .../Tabs/WallpaperTab.qml | 0 .../WallpaperPanel.qml} | 0 .../IPCManager.qml => Services/IPCService.qml | 5 ++-- shell.qml | 25 ++++++++++--------- 50 files changed, 35 insertions(+), 34 deletions(-) rename Modules/Bar/Calendar/{Calendar.qml => CalendarPanel.qml} (100%) rename Modules/{SidePanel => ControlCenter}/Cards/MediaCard.qml (100%) rename Modules/{SidePanel => ControlCenter}/Cards/PowerProfilesCard.qml (100%) rename Modules/{SidePanel => ControlCenter}/Cards/ProfileCard.qml (94%) rename Modules/{SidePanel => ControlCenter}/Cards/SystemMonitorCard.qml (100%) rename Modules/{SidePanel => ControlCenter}/Cards/UtilitiesCard.qml (90%) rename Modules/{SidePanel => ControlCenter}/Cards/WeatherCard.qml (100%) rename Modules/{SidePanel/SidePanel.qml => ControlCenter/ControlCenterPanel.qml} (98%) rename Modules/{SettingsPanel => Settings}/Bar/BarSectionEditor.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/BarWidgetSettingsDialog.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/ActiveWindowSettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/BatterySettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/BrightnessSettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/ClockSettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/CustomButtonSettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/KeyboardLayoutSettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/MediaMiniSettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/MicrophoneSettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/NotificationHistorySettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/SidePanelToggleSettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/SpacerSettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/SystemMonitorSettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/VolumeSettings.qml (100%) rename Modules/{SettingsPanel => Settings}/Bar/WidgetSettings/WorkspaceSettings.qml (100%) rename Modules/{SettingsPanel => Settings}/SettingsPanel.qml (99%) rename Modules/{SettingsPanel => Settings}/Tabs/AboutTab.qml (100%) rename Modules/{SettingsPanel => Settings}/Tabs/AudioTab.qml (100%) rename Modules/{SettingsPanel => Settings}/Tabs/BarTab.qml (99%) rename Modules/{SettingsPanel => Settings}/Tabs/ColorSchemeTab.qml (100%) rename Modules/{SettingsPanel => Settings}/Tabs/DisplayTab.qml (100%) rename Modules/{SettingsPanel => Settings}/Tabs/DockTab.qml (100%) rename Modules/{SettingsPanel => Settings}/Tabs/GeneralTab.qml (100%) rename Modules/{SettingsPanel => Settings}/Tabs/HooksTab.qml (100%) rename Modules/{SettingsPanel => Settings}/Tabs/LauncherTab.qml (100%) rename Modules/{SettingsPanel => Settings}/Tabs/LocationTab.qml (100%) rename Modules/{SettingsPanel => Settings}/Tabs/NetworkTab.qml (100%) rename Modules/{SettingsPanel => Settings}/Tabs/NotificationsTab.qml (100%) rename Modules/{SettingsPanel => Settings}/Tabs/ScreenRecorderTab.qml (100%) rename Modules/{SettingsPanel => Settings}/Tabs/WallpaperTab.qml (100%) rename Modules/{WallpaperSelector/WallpaperSelector.qml => Wallpaper/WallpaperPanel.qml} (100%) rename Modules/IPC/IPCManager.qml => Services/IPCService.qml (95%) diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 7f588e73..4dd653b2 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -3,7 +3,6 @@ import Quickshell import Quickshell.Wayland import qs.Commons import qs.Services -import qs.Modules.SettingsPanel import qs.Widgets Variants { diff --git a/Modules/Bar/Calendar/Calendar.qml b/Modules/Bar/Calendar/CalendarPanel.qml similarity index 100% rename from Modules/Bar/Calendar/Calendar.qml rename to Modules/Bar/Calendar/CalendarPanel.qml diff --git a/Modules/Bar/Widgets/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml index e81ef351..652660c0 100644 --- a/Modules/Bar/Widgets/Brightness.qml +++ b/Modules/Bar/Widgets/Brightness.qml @@ -1,10 +1,10 @@ import QtQuick import Quickshell import qs.Commons -import qs.Modules.SettingsPanel +import qs.Modules.Bar.Extras +import qs.Modules.Settings import qs.Services import qs.Widgets -import qs.Modules.Bar.Extras Item { id: root diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index a2343238..3973b4cd 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -5,7 +5,7 @@ import Quickshell.Io import qs.Commons import qs.Services import qs.Widgets -import qs.Modules.SettingsPanel +import qs.Modules.Settings import qs.Modules.Bar.Extras Item { diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml index 6e1eda5c..41e5dbff 100644 --- a/Modules/Bar/Widgets/Microphone.qml +++ b/Modules/Bar/Widgets/Microphone.qml @@ -3,7 +3,7 @@ import Quickshell import Quickshell.Io import Quickshell.Services.Pipewire import qs.Commons -import qs.Modules.SettingsPanel +import qs.Modules.Settings import qs.Services import qs.Widgets import qs.Modules.Bar.Extras diff --git a/Modules/Bar/Widgets/NightLight.qml b/Modules/Bar/Widgets/NightLight.qml index 840224c2..b1801dd5 100644 --- a/Modules/Bar/Widgets/NightLight.qml +++ b/Modules/Bar/Widgets/NightLight.qml @@ -4,7 +4,7 @@ import QtQuick.Controls import Quickshell import Quickshell.Wayland import qs.Commons -import qs.Modules.SettingsPanel +import qs.Modules.Settings import qs.Services import qs.Widgets diff --git a/Modules/Bar/Widgets/SidePanelToggle.qml b/Modules/Bar/Widgets/SidePanelToggle.qml index 91b53406..c1d348f7 100644 --- a/Modules/Bar/Widgets/SidePanelToggle.qml +++ b/Modules/Bar/Widgets/SidePanelToggle.qml @@ -43,7 +43,7 @@ NIconButton { colorBgHover: useDistroLogo ? Color.mSurfaceVariant : Color.mTertiary colorBorder: Color.transparent colorBorderHover: useDistroLogo ? Color.mTertiary : Color.transparent - onClicked: PanelService.getPanel("sidePanel")?.toggle(this) + onClicked: PanelService.getPanel("controlCenterPanel")?.toggle(this) onRightClicked: PanelService.getPanel("settingsPanel")?.toggle() IconImage { diff --git a/Modules/Bar/Widgets/Volume.qml b/Modules/Bar/Widgets/Volume.qml index e39a23f1..03ac13c9 100644 --- a/Modules/Bar/Widgets/Volume.qml +++ b/Modules/Bar/Widgets/Volume.qml @@ -3,7 +3,7 @@ import Quickshell import Quickshell.Io import Quickshell.Services.Pipewire import qs.Commons -import qs.Modules.SettingsPanel +import qs.Modules.Settings import qs.Services import qs.Widgets import qs.Modules.Bar.Extras diff --git a/Modules/Bar/Widgets/WallpaperSelector.qml b/Modules/Bar/Widgets/WallpaperSelector.qml index 8743be2d..05163d2f 100644 --- a/Modules/Bar/Widgets/WallpaperSelector.qml +++ b/Modules/Bar/Widgets/WallpaperSelector.qml @@ -19,5 +19,5 @@ NIconButton { colorFg: Color.mOnSurface colorBorder: Color.transparent colorBorderHover: Color.transparent - onClicked: PanelService.getPanel("wallpaperSelector")?.toggle(this) + onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this) } diff --git a/Modules/SidePanel/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml similarity index 100% rename from Modules/SidePanel/Cards/MediaCard.qml rename to Modules/ControlCenter/Cards/MediaCard.qml diff --git a/Modules/SidePanel/Cards/PowerProfilesCard.qml b/Modules/ControlCenter/Cards/PowerProfilesCard.qml similarity index 100% rename from Modules/SidePanel/Cards/PowerProfilesCard.qml rename to Modules/ControlCenter/Cards/PowerProfilesCard.qml diff --git a/Modules/SidePanel/Cards/ProfileCard.qml b/Modules/ControlCenter/Cards/ProfileCard.qml similarity index 94% rename from Modules/SidePanel/Cards/ProfileCard.qml rename to Modules/ControlCenter/Cards/ProfileCard.qml index b4771315..efff07b3 100644 --- a/Modules/SidePanel/Cards/ProfileCard.qml +++ b/Modules/ControlCenter/Cards/ProfileCard.qml @@ -4,8 +4,8 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Widgets -import qs.Modules.SettingsPanel -import qs.Modules.SidePanel +import qs.Modules.Settings +import qs.Modules.ControlCenter import qs.Commons import qs.Services import qs.Widgets @@ -69,7 +69,7 @@ NBox { tooltipText: "Power panel" onClicked: { powerPanel.open() - sidePanel.close() + controlCenterPanel.close() } } @@ -78,7 +78,7 @@ NBox { icon: "close" tooltipText: "Close side panel" onClicked: { - sidePanel.close() + controlCenterPanel.close() } } } diff --git a/Modules/SidePanel/Cards/SystemMonitorCard.qml b/Modules/ControlCenter/Cards/SystemMonitorCard.qml similarity index 100% rename from Modules/SidePanel/Cards/SystemMonitorCard.qml rename to Modules/ControlCenter/Cards/SystemMonitorCard.qml diff --git a/Modules/SidePanel/Cards/UtilitiesCard.qml b/Modules/ControlCenter/Cards/UtilitiesCard.qml similarity index 90% rename from Modules/SidePanel/Cards/UtilitiesCard.qml rename to Modules/ControlCenter/Cards/UtilitiesCard.qml index ad0dde03..9bfae591 100644 --- a/Modules/SidePanel/Cards/UtilitiesCard.qml +++ b/Modules/ControlCenter/Cards/UtilitiesCard.qml @@ -3,7 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.Commons -import qs.Modules.SettingsPanel +import qs.Modules.Settings import qs.Services import qs.Widgets @@ -33,8 +33,8 @@ NBox { ScreenRecorderService.toggleRecording() // If we were not recording and we just initiated a start, close the panel if (!ScreenRecorderService.isRecording) { - var panel = PanelService.getPanel("sidePanel") - panel && panel.close() + var panel = PanelService.getPanel("controlCenterPanel") + panel?.close() } } } @@ -55,7 +55,7 @@ NBox { visible: Settings.data.wallpaper.enabled icon: "wallpaper-selector" tooltipText: "Left click: Open wallpaper selector.\nRight click: Set random wallpaper." - onClicked: PanelService.getPanel("wallpaperSelector")?.toggle(this) + onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this) onRightClicked: WallpaperService.setRandomWallpaper() } diff --git a/Modules/SidePanel/Cards/WeatherCard.qml b/Modules/ControlCenter/Cards/WeatherCard.qml similarity index 100% rename from Modules/SidePanel/Cards/WeatherCard.qml rename to Modules/ControlCenter/Cards/WeatherCard.qml diff --git a/Modules/SidePanel/SidePanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml similarity index 98% rename from Modules/SidePanel/SidePanel.qml rename to Modules/ControlCenter/ControlCenterPanel.qml index bfd4e850..de1e7753 100644 --- a/Modules/SidePanel/SidePanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell -import qs.Modules.SidePanel.Cards +import qs.Modules.ControlCenter.Cards import qs.Commons import qs.Services import qs.Widgets diff --git a/Modules/SettingsPanel/Bar/BarSectionEditor.qml b/Modules/Settings/Bar/BarSectionEditor.qml similarity index 100% rename from Modules/SettingsPanel/Bar/BarSectionEditor.qml rename to Modules/Settings/Bar/BarSectionEditor.qml diff --git a/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml similarity index 100% rename from Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml rename to Modules/Settings/Bar/BarWidgetSettingsDialog.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/ActiveWindowSettings.qml rename to Modules/Settings/Bar/WidgetSettings/ActiveWindowSettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/BatterySettings.qml b/Modules/Settings/Bar/WidgetSettings/BatterySettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/BatterySettings.qml rename to Modules/Settings/Bar/WidgetSettings/BatterySettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/BrightnessSettings.qml b/Modules/Settings/Bar/WidgetSettings/BrightnessSettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/BrightnessSettings.qml rename to Modules/Settings/Bar/WidgetSettings/BrightnessSettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/ClockSettings.qml b/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/ClockSettings.qml rename to Modules/Settings/Bar/WidgetSettings/ClockSettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/CustomButtonSettings.qml b/Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/CustomButtonSettings.qml rename to Modules/Settings/Bar/WidgetSettings/CustomButtonSettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/KeyboardLayoutSettings.qml b/Modules/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/KeyboardLayoutSettings.qml rename to Modules/Settings/Bar/WidgetSettings/KeyboardLayoutSettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/MediaMiniSettings.qml b/Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/MediaMiniSettings.qml rename to Modules/Settings/Bar/WidgetSettings/MediaMiniSettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/MicrophoneSettings.qml b/Modules/Settings/Bar/WidgetSettings/MicrophoneSettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/MicrophoneSettings.qml rename to Modules/Settings/Bar/WidgetSettings/MicrophoneSettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/NotificationHistorySettings.qml b/Modules/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/NotificationHistorySettings.qml rename to Modules/Settings/Bar/WidgetSettings/NotificationHistorySettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml b/Modules/Settings/Bar/WidgetSettings/SidePanelToggleSettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml rename to Modules/Settings/Bar/WidgetSettings/SidePanelToggleSettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/SpacerSettings.qml b/Modules/Settings/Bar/WidgetSettings/SpacerSettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/SpacerSettings.qml rename to Modules/Settings/Bar/WidgetSettings/SpacerSettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/SystemMonitorSettings.qml b/Modules/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/SystemMonitorSettings.qml rename to Modules/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/VolumeSettings.qml b/Modules/Settings/Bar/WidgetSettings/VolumeSettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/VolumeSettings.qml rename to Modules/Settings/Bar/WidgetSettings/VolumeSettings.qml diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/WorkspaceSettings.qml b/Modules/Settings/Bar/WidgetSettings/WorkspaceSettings.qml similarity index 100% rename from Modules/SettingsPanel/Bar/WidgetSettings/WorkspaceSettings.qml rename to Modules/Settings/Bar/WidgetSettings/WorkspaceSettings.qml diff --git a/Modules/SettingsPanel/SettingsPanel.qml b/Modules/Settings/SettingsPanel.qml similarity index 99% rename from Modules/SettingsPanel/SettingsPanel.qml rename to Modules/Settings/SettingsPanel.qml index 81772ff9..c60762d8 100644 --- a/Modules/SettingsPanel/SettingsPanel.qml +++ b/Modules/Settings/SettingsPanel.qml @@ -3,7 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Wayland -import qs.Modules.SettingsPanel.Tabs as Tabs +import qs.Modules.Settings.Tabs as Tabs import qs.Commons import qs.Services import qs.Widgets diff --git a/Modules/SettingsPanel/Tabs/AboutTab.qml b/Modules/Settings/Tabs/AboutTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/AboutTab.qml rename to Modules/Settings/Tabs/AboutTab.qml diff --git a/Modules/SettingsPanel/Tabs/AudioTab.qml b/Modules/Settings/Tabs/AudioTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/AudioTab.qml rename to Modules/Settings/Tabs/AudioTab.qml diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/Settings/Tabs/BarTab.qml similarity index 99% rename from Modules/SettingsPanel/Tabs/BarTab.qml rename to Modules/Settings/Tabs/BarTab.qml index c332092c..3ad30376 100644 --- a/Modules/SettingsPanel/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.SettingsPanel.Bar +import qs.Modules.Settings.Bar ColumnLayout { id: root diff --git a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/ColorSchemeTab.qml rename to Modules/Settings/Tabs/ColorSchemeTab.qml diff --git a/Modules/SettingsPanel/Tabs/DisplayTab.qml b/Modules/Settings/Tabs/DisplayTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/DisplayTab.qml rename to Modules/Settings/Tabs/DisplayTab.qml diff --git a/Modules/SettingsPanel/Tabs/DockTab.qml b/Modules/Settings/Tabs/DockTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/DockTab.qml rename to Modules/Settings/Tabs/DockTab.qml diff --git a/Modules/SettingsPanel/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/GeneralTab.qml rename to Modules/Settings/Tabs/GeneralTab.qml diff --git a/Modules/SettingsPanel/Tabs/HooksTab.qml b/Modules/Settings/Tabs/HooksTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/HooksTab.qml rename to Modules/Settings/Tabs/HooksTab.qml diff --git a/Modules/SettingsPanel/Tabs/LauncherTab.qml b/Modules/Settings/Tabs/LauncherTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/LauncherTab.qml rename to Modules/Settings/Tabs/LauncherTab.qml diff --git a/Modules/SettingsPanel/Tabs/LocationTab.qml b/Modules/Settings/Tabs/LocationTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/LocationTab.qml rename to Modules/Settings/Tabs/LocationTab.qml diff --git a/Modules/SettingsPanel/Tabs/NetworkTab.qml b/Modules/Settings/Tabs/NetworkTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/NetworkTab.qml rename to Modules/Settings/Tabs/NetworkTab.qml diff --git a/Modules/SettingsPanel/Tabs/NotificationsTab.qml b/Modules/Settings/Tabs/NotificationsTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/NotificationsTab.qml rename to Modules/Settings/Tabs/NotificationsTab.qml diff --git a/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml b/Modules/Settings/Tabs/ScreenRecorderTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml rename to Modules/Settings/Tabs/ScreenRecorderTab.qml diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/Settings/Tabs/WallpaperTab.qml similarity index 100% rename from Modules/SettingsPanel/Tabs/WallpaperTab.qml rename to Modules/Settings/Tabs/WallpaperTab.qml diff --git a/Modules/WallpaperSelector/WallpaperSelector.qml b/Modules/Wallpaper/WallpaperPanel.qml similarity index 100% rename from Modules/WallpaperSelector/WallpaperSelector.qml rename to Modules/Wallpaper/WallpaperPanel.qml diff --git a/Modules/IPC/IPCManager.qml b/Services/IPCService.qml similarity index 95% rename from Modules/IPC/IPCManager.qml rename to Services/IPCService.qml index ba644c75..380222b0 100644 --- a/Modules/IPC/IPCManager.qml +++ b/Services/IPCService.qml @@ -117,10 +117,11 @@ Item { } IpcHandler { + // TODO: upgrade when breaking changes target: "sidePanel" function toggle() { // Will attempt to open the panel next to the bar button if any. - sidePanel.toggle(BarService.lookupWidget("SidePanelToggle")) + controlCenterPanel.toggle(BarService.lookupWidget("SidePanelToggle")) } } @@ -129,7 +130,7 @@ Item { target: "wallpaper" function toggle() { if (Settings.data.wallpaper.enabled) { - wallpaperSelector.toggle() + wallpaperPanel.toggle() } } diff --git a/shell.qml b/shell.qml index e021743b..18071540 100644 --- a/shell.qml +++ b/shell.qml @@ -23,7 +23,6 @@ import qs.Widgets // Core Modules import qs.Modules.Background import qs.Modules.Dock -import qs.Modules.IPC import qs.Modules.LockScreen // Bar & Bar Components @@ -35,12 +34,12 @@ import qs.Modules.Bar.Power import qs.Modules.Bar.WiFi // Panels & UI Components +import qs.Modules.ControlCenter import qs.Modules.Launcher import qs.Modules.Notification -import qs.Modules.SettingsPanel -import qs.Modules.SidePanel +import qs.Modules.Settings import qs.Modules.Toast -import qs.Modules.WallpaperSelector +import qs.Modules.Wallpaper ShellRoot { id: shellRoot @@ -61,7 +60,9 @@ ShellRoot { ToastOverlay {} - IPCManager {} + // IPCService is treated as a service + // but it's actually an Item that needs to exists in the shell. + IPCService {} // ------------------------------ // All the NPanels @@ -70,12 +71,12 @@ ShellRoot { objectName: "launcherPanel" } - SidePanel { - id: sidePanel - objectName: "sidePanel" + ControlCenterPanel { + id: controlCenterPanel + objectName: "controlCenterPanel" } - Calendar { + CalendarPanel { id: calendarPanel objectName: "calendarPanel" } @@ -105,9 +106,9 @@ ShellRoot { objectName: "bluetoothPanel" } - WallpaperSelector { - id: wallpaperSelector - objectName: "wallpaperSelector" + WallpaperPanel { + id: wallpaperPanel + objectName: "wallpaperPanel" } Component.onCompleted: { From def778dbf1fba372388c1bc2ffe070a9bb2f23b4 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 19:39:52 -0400 Subject: [PATCH 26/54] Settings: Log before splicing or you will log the wrong widget.id --- Commons/Settings.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 1339f952..f481aa92 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -401,8 +401,8 @@ Singleton { for (var i = widgets.length - 1; i >= 0; i--) { var widget = widgets[i] if (!BarWidgetRegistry.hasWidget(widget.id)) { - widgets.splice(i, 1) Logger.warn(`Settings`, `Deleted invalid widget ${widget.id}`) + widgets.splice(i, 1) } } } From 50ef79677e08c5ff0b9b621ebd5140b78a3515a9 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 19:51:57 -0400 Subject: [PATCH 27/54] Updating bar widgets ids --- Commons/Settings.qml | 20 ++++++++++---- ...{SidePanelToggle.qml => ControlCenter.qml} | 0 .../{DarkModeToggle.qml => DarkMode.qml} | 0 ...corderIndicator.qml => ScreenRecorder.qml} | 0 .../{PowerToggle.qml => SessionMenu.qml} | 0 Services/BarWidgetRegistry.qml | 26 +++++++++---------- 6 files changed, 28 insertions(+), 18 deletions(-) rename Modules/Bar/Widgets/{SidePanelToggle.qml => ControlCenter.qml} (100%) rename Modules/Bar/Widgets/{DarkModeToggle.qml => DarkMode.qml} (100%) rename Modules/Bar/Widgets/{ScreenRecorderIndicator.qml => ScreenRecorder.qml} (100%) rename Modules/Bar/Widgets/{PowerToggle.qml => SessionMenu.qml} (100%) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index f481aa92..65428b6a 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -379,15 +379,25 @@ Singleton { const sections = ["left", "center", "right"] // ----------------- - // 1st. check our settings are not super old, when we only had the widget type as a plain string + // 1st. convert old widget id to new id for (var s = 0; s < sections.length; s++) { const sectionName = sections[s] for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) { var widget = adapter.bar.widgets[sectionName][i] - if (typeof widget === "string") { - adapter.bar.widgets[sectionName][i] = { - "id": widget - } + + switch (widget.id) { + case "DarkModeToggle": + widget.id = "DarkMode" + break; + case "PowerToggle": + widget.id = "SessionMenu" + break; + case "ScreenRecorderIndicator": + widget.id = "ScreenRecorder" + break; + case "SidePanelToggle": + widget.id = "ControlCenter" + break; } } } diff --git a/Modules/Bar/Widgets/SidePanelToggle.qml b/Modules/Bar/Widgets/ControlCenter.qml similarity index 100% rename from Modules/Bar/Widgets/SidePanelToggle.qml rename to Modules/Bar/Widgets/ControlCenter.qml diff --git a/Modules/Bar/Widgets/DarkModeToggle.qml b/Modules/Bar/Widgets/DarkMode.qml similarity index 100% rename from Modules/Bar/Widgets/DarkModeToggle.qml rename to Modules/Bar/Widgets/DarkMode.qml diff --git a/Modules/Bar/Widgets/ScreenRecorderIndicator.qml b/Modules/Bar/Widgets/ScreenRecorder.qml similarity index 100% rename from Modules/Bar/Widgets/ScreenRecorderIndicator.qml rename to Modules/Bar/Widgets/ScreenRecorder.qml diff --git a/Modules/Bar/Widgets/PowerToggle.qml b/Modules/Bar/Widgets/SessionMenu.qml similarity index 100% rename from Modules/Bar/Widgets/PowerToggle.qml rename to Modules/Bar/Widgets/SessionMenu.qml diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index d4594468..ba34b8cc 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -16,7 +16,7 @@ Singleton { "Brightness": brightnessComponent, "Clock": clockComponent, "CustomButton": customButtonComponent, - "DarkModeToggle": darkModeToggle, + "DarkMode": darkMode, "KeepAwake": keepAwakeComponent, "KeyboardLayout": keyboardLayoutComponent, "MediaMini": mediaMiniComponent, @@ -24,9 +24,9 @@ Singleton { "NightLight": nightLightComponent, "NotificationHistory": notificationHistoryComponent, "PowerProfile": powerProfileComponent, - "PowerToggle": powerToggleComponent, - "ScreenRecorderIndicator": screenRecorderIndicatorComponent, - "SidePanelToggle": sidePanelToggleComponent, + "SessionMenu": sessionMenuComponent, + "ScreenRecorder": screenRecorderComponent, + "ControlCenter": controlCenterComponent, "Spacer": spacerComponent, "SystemMonitor": systemMonitorComponent, "Taskbar": taskbarComponent, @@ -100,7 +100,7 @@ Singleton { "showVisualizer": false, "visualizerType": "linear" }, - "SidePanelToggle": { + "ControlCenter": { "allowUserSettings": true, "useDistroLogo": false, "icon": "noctalia", @@ -135,8 +135,8 @@ Singleton { property Component customButtonComponent: Component { CustomButton {} } - property Component darkModeToggle: Component { - DarkModeToggle {} + property Component darkMode: Component { + DarkMode{} } property Component keyboardLayoutComponent: Component { KeyboardLayout {} @@ -159,14 +159,14 @@ Singleton { property Component powerProfileComponent: Component { PowerProfile {} } - property Component powerToggleComponent: Component { - PowerToggle {} + property Component sessionMenuComponent: Component { + SessionMenu {} } - property Component screenRecorderIndicatorComponent: Component { - ScreenRecorderIndicator {} + property Component screenRecorderComponent: Component { + ScreenRecorder {} } - property Component sidePanelToggleComponent: Component { - SidePanelToggle {} + property Component controlCenterComponent: Component { + ControlCenter {} } property Component spacerComponent: Component { Spacer {} From 50ea3e9a8bc0f6f0f470d3a90bf803f047e6917d Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 20:09:12 -0400 Subject: [PATCH 28/54] More renaming --- Assets/settings-default.json | 4 +-- Commons/Settings.qml | 28 +++++++++---------- Modules/Bar/Widgets/SessionMenu.qml | 4 +-- Modules/ControlCenter/Cards/ProfileCard.qml | 4 +-- .../SessionMenu.qml} | 2 +- .../Settings/Bar/BarWidgetSettingsDialog.qml | 6 ++-- Services/BarWidgetRegistry.qml | 2 +- Services/IPCService.qml | 22 +++++++++++++-- shell.qml | 11 ++++---- 9 files changed, 50 insertions(+), 33 deletions(-) rename Modules/{Bar/Power/PowerPanel.qml => SessionMenu/SessionMenu.qml} (99%) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 37d85a50..920bb69a 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -28,7 +28,7 @@ ], "right": [ { - "id": "ScreenRecorderIndicator" + "id": "ScreenRecorder" }, { "id": "Tray" @@ -58,7 +58,7 @@ "id": "Clock" }, { - "id": "SidePanelToggle" + "id": "ControlCenter" } ] } diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 65428b6a..04359571 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -142,7 +142,7 @@ Singleton { "id": "Workspace" }] property list right: [{ - "id": "ScreenRecorderIndicator" + "id": "ScreenRecorder" }, { "id": "Tray" }, { @@ -162,7 +162,7 @@ Singleton { }, { "id": "Clock" }, { - "id": "SidePanelToggle" + "id": "ControlCenter" }] } } @@ -386,18 +386,18 @@ Singleton { var widget = adapter.bar.widgets[sectionName][i] switch (widget.id) { - case "DarkModeToggle": - widget.id = "DarkMode" - break; - case "PowerToggle": - widget.id = "SessionMenu" - break; - case "ScreenRecorderIndicator": - widget.id = "ScreenRecorder" - break; - case "SidePanelToggle": - widget.id = "ControlCenter" - break; + case "DarkModeToggle": + widget.id = "DarkMode" + break + case "PowerToggle": + widget.id = "SessionMenu" + break + case "ScreenRecorderIndicator": + widget.id = "ScreenRecorder" + break + case "SidePanelToggle": + widget.id = "ControlCenter" + break } } } diff --git a/Modules/Bar/Widgets/SessionMenu.qml b/Modules/Bar/Widgets/SessionMenu.qml index 9df6c452..38ee93dd 100644 --- a/Modules/Bar/Widgets/SessionMenu.qml +++ b/Modules/Bar/Widgets/SessionMenu.qml @@ -14,10 +14,10 @@ NIconButton { compact: (Settings.data.bar.density === "compact") baseSize: Style.capsuleHeight icon: "power" - tooltipText: "Power panel" + tooltipText: "Session menu" colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) colorFg: Color.mError colorBorder: Color.transparent colorBorderHover: Color.transparent - onClicked: PanelService.getPanel("powerPanel")?.toggle() + onClicked: PanelService.getPanel("sessionMenuPanel")?.toggle() } diff --git a/Modules/ControlCenter/Cards/ProfileCard.qml b/Modules/ControlCenter/Cards/ProfileCard.qml index efff07b3..d7aa4a4e 100644 --- a/Modules/ControlCenter/Cards/ProfileCard.qml +++ b/Modules/ControlCenter/Cards/ProfileCard.qml @@ -66,9 +66,9 @@ NBox { NIconButton { id: powerButton icon: "power" - tooltipText: "Power panel" + tooltipText: "Session Menu" onClicked: { - powerPanel.open() + sessionMenuPanel.open() controlCenterPanel.close() } } diff --git a/Modules/Bar/Power/PowerPanel.qml b/Modules/SessionMenu/SessionMenu.qml similarity index 99% rename from Modules/Bar/Power/PowerPanel.qml rename to Modules/SessionMenu/SessionMenu.qml index df2381ce..c3de9a59 100644 --- a/Modules/Bar/Power/PowerPanel.qml +++ b/Modules/SessionMenu/SessionMenu.qml @@ -263,7 +263,7 @@ NPanel { Layout.preferredHeight: Style.baseWidgetSize * 0.8 * scaling NText { - text: timerActive ? `${pendingAction.charAt(0).toUpperCase() + pendingAction.slice(1)} in ${Math.ceil(timeRemaining / 1000)} seconds...` : "Power panel" + text: timerActive ? `${pendingAction.charAt(0).toUpperCase() + pendingAction.slice(1)} in ${Math.ceil(timeRemaining / 1000)} seconds...` : "Session Menu" font.weight: Style.fontWeightBold font.pointSize: Style.fontSizeL * scaling color: timerActive ? Color.mPrimary : Color.mOnSurface diff --git a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml index 74b34888..86344997 100644 --- a/Modules/Settings/Bar/BarWidgetSettingsDialog.qml +++ b/Modules/Settings/Bar/BarWidgetSettingsDialog.qml @@ -126,16 +126,16 @@ Popup { "Battery": "WidgetSettings/BatterySettings.qml", "Brightness": "WidgetSettings/BrightnessSettings.qml", "Clock": "WidgetSettings/ClockSettings.qml", + "ControlCenter": "WidgetSettings/ControlCenterSettings.qml", "CustomButton": "WidgetSettings/CustomButtonSettings.qml", "KeyboardLayout": "WidgetSettings/KeyboardLayoutSettings.qml", "MediaMini": "WidgetSettings/MediaMiniSettings.qml", "Microphone": "WidgetSettings/MicrophoneSettings.qml", "NotificationHistory": "WidgetSettings/NotificationHistorySettings.qml", - "Workspace": "WidgetSettings/WorkspaceSettings.qml", - "SidePanelToggle": "WidgetSettings/SidePanelToggleSettings.qml", "Spacer": "WidgetSettings/SpacerSettings.qml", "SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml", - "Volume": "WidgetSettings/VolumeSettings.qml" + "Volume": "WidgetSettings/VolumeSettings.qml", + "Workspace": "WidgetSettings/WorkspaceSettings.qml" } const source = widgetSettingsMap[widgetId] diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index ba34b8cc..074f362d 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -136,7 +136,7 @@ Singleton { CustomButton {} } property Component darkMode: Component { - DarkMode{} + DarkMode {} } property Component keyboardLayoutComponent: Component { KeyboardLayout {} diff --git a/Services/IPCService.qml b/Services/IPCService.qml index 380222b0..5031bbbc 100644 --- a/Services/IPCService.qml +++ b/Services/IPCService.qml @@ -109,19 +109,35 @@ Item { } } + // TODO: delete in next major release IpcHandler { target: "powerPanel" function toggle() { - powerPanel.toggle() + sessionMenuPanel.toggle() + ToastService.showWarning("IPC", "PowerPanel has been renamed to SessionMenu, this IPC call will be deprecated soon. Please use \"ipc call sessionMenu toggle\" instead.", 8000) + } + } + IpcHandler { + target: "sessionMenu" + function toggle() { + sessionMenuPanel.toggle() } } + // TODO: delete in next major release IpcHandler { - // TODO: upgrade when breaking changes target: "sidePanel" function toggle() { // Will attempt to open the panel next to the bar button if any. - controlCenterPanel.toggle(BarService.lookupWidget("SidePanelToggle")) + controlCenterPanel.toggle(BarService.lookupWidget("ControlCenter")) + ToastService.showWarning("IPC", "SidePanel has been renamed to ControlCenter, this IPC call will be deprecated soon. Please use \"ipc call controlCenter toggle\" instead.", 8000) + } + } + IpcHandler { + target: "controlCenter" + function toggle() { + // Will attempt to open the panel next to the bar button if any. + controlCenterPanel.toggle(BarService.lookupWidget("ControlCenter")) } } diff --git a/shell.qml b/shell.qml index 18071540..5d5f9240 100644 --- a/shell.qml +++ b/shell.qml @@ -24,13 +24,14 @@ import qs.Widgets import qs.Modules.Background import qs.Modules.Dock import qs.Modules.LockScreen +import qs.Modules.SessionMenu // Bar & Bar Components import qs.Modules.Bar import qs.Modules.Bar.Extras import qs.Modules.Bar.Bluetooth import qs.Modules.Bar.Calendar -import qs.Modules.Bar.Power + import qs.Modules.Bar.WiFi // Panels & UI Components @@ -60,7 +61,7 @@ ShellRoot { ToastOverlay {} - // IPCService is treated as a service + // IPCService is treated as a service // but it's actually an Item that needs to exists in the shell. IPCService {} @@ -91,9 +92,9 @@ ShellRoot { objectName: "notificationHistoryPanel" } - PowerPanel { - id: powerPanel - objectName: "powerPanel" + SessionMenu { + id: sessionMenuPanel + objectName: "sessionMenuPanel" } WiFiPanel { From a395156556c0dcf1f752bd9899f545de34d1267f Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 20:14:42 -0400 Subject: [PATCH 29/54] ControlCenterSettings fix --- ...SidePanelToggleSettings.qml => ControlCenterSettings.qml} | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) rename Modules/Settings/Bar/WidgetSettings/{SidePanelToggleSettings.qml => ControlCenterSettings.qml} (95%) diff --git a/Modules/Settings/Bar/WidgetSettings/SidePanelToggleSettings.qml b/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml similarity index 95% rename from Modules/Settings/Bar/WidgetSettings/SidePanelToggleSettings.qml rename to Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml index ee31b3ee..9c6ab6a9 100644 --- a/Modules/Settings/Bar/WidgetSettings/SidePanelToggleSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml @@ -57,10 +57,13 @@ ColumnLayout { NIcon { Layout.alignment: Qt.AlignVCenter icon: valueIcon - font.pointSize: Style.fontSizeXL * scaling + font.pointSize: Style.fontSizeXXL * 1.5 * scaling visible: valueIcon !== "" && valueCustomIconPath === "" } + } + RowLayout { + spacing: Style.marginM * scaling NButton { enabled: !valueUseDistroLogo text: "Browse Library" From aec170d7f81f53589ba34bc462ef7f5614252fe6 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 20:16:39 -0400 Subject: [PATCH 30/54] Fix a few hardcoded margin by proper Style.xxx --- Modules/LockScreen/LockScreen.qml | 6 +++--- Modules/Settings/Tabs/LocationTab.qml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index cabad9f0..84b58c98 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -766,7 +766,7 @@ Loader { Rectangle { anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.top - anchors.bottomMargin: 12 * scaling + anchors.bottomMargin: Style.marginM * scaling radius: Style.radiusM * scaling color: Color.mSurface border.color: Color.mOutline @@ -817,7 +817,7 @@ Loader { Rectangle { anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.top - anchors.bottomMargin: 12 * scaling + anchors.bottomMargin: Style.marginM * scaling radius: Style.radiusM * scaling color: Color.mSurface border.color: Color.mOutline @@ -869,7 +869,7 @@ Loader { Rectangle { anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.top - anchors.bottomMargin: 12 * scaling + anchors.bottomMargin: Style.marginM * scaling radius: Style.radiusM * scaling color: Color.mSurface border.color: Color.mOutline diff --git a/Modules/Settings/Tabs/LocationTab.qml b/Modules/Settings/Tabs/LocationTab.qml index 2696abab..d0cc8d3a 100644 --- a/Modules/Settings/Tabs/LocationTab.qml +++ b/Modules/Settings/Tabs/LocationTab.qml @@ -48,7 +48,7 @@ ColumnLayout { verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignRight Layout.alignment: Qt.AlignBottom - Layout.bottomMargin: 12 * scaling + Layout.bottomMargin: Style.marginM * scaling } } From c4764c0e5b6c6618506929a70f302851e494ecbb Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 20:23:00 -0400 Subject: [PATCH 31/54] ScreenRecorder: disable toast when recording starts --- Services/ScreenRecorderService.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Services/ScreenRecorderService.qml b/Services/ScreenRecorderService.qml index e4d13543..e45a3785 100644 --- a/Services/ScreenRecorderService.qml +++ b/Services/ScreenRecorderService.qml @@ -159,7 +159,8 @@ Singleton { isRecording = true hasActiveRecording = true monitorTimer.running = true - ToastService.showNotice("Recording started", outputPath, 4000) + // Don't show a toast when recording starts to avoid having the toast in every video. + //ToastService.showNotice("Recording started", outputPath, 4000) } else if (isPending) { // Process not running anymore - was cancelled or failed isPending = false From 9b8c0b9cf0693bd0e96579bb43ad97979634c58a Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 20:53:59 -0400 Subject: [PATCH 32/54] ListView replaced by proper NListView --- Modules/ControlCenter/Cards/MediaCard.qml | 25 +- .../Bar/WidgetSettings/ClockSettings.qml | 2 +- Widgets/NComboBox.qml | 33 +- Widgets/NDateTimeTokens.qml | 655 +++++++++--------- Widgets/NSearchableComboBox.qml | 6 +- 5 files changed, 356 insertions(+), 365 deletions(-) diff --git a/Modules/ControlCenter/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml index 2a9a9213..3c87aa62 100644 --- a/Modules/ControlCenter/Cards/MediaCard.qml +++ b/Modules/ControlCenter/Cards/MediaCard.qml @@ -96,7 +96,7 @@ NBox { x: playerSelector.width * 0.5 y: playerSelector.height * 0.75 width: playerSelector.width * 0.5 - implicitHeight: Math.min(160 * scaling, contentItem.implicitHeight + Style.marginM * scaling) + implicitHeight: Math.min(160 * scaling, contentItem.implicitHeight + Style.marginM * 2 * scaling) padding: Style.marginS * scaling onOpened: { @@ -107,38 +107,37 @@ NBox { PanelService.willClosePopup(root) } - contentItem: ListView { - clip: true + contentItem: NListView { implicitHeight: contentHeight model: playerSelector.popup.visible ? playerSelector.delegateModel : null currentIndex: playerSelector.highlightedIndex - ScrollIndicator.vertical: ScrollIndicator {} + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded } background: Rectangle { color: Color.mSurface border.color: Color.mOutline border.width: Math.max(1, Style.borderS * scaling) - radius: Style.radiusXS * scaling + radius: Style.radiusS * scaling } } delegate: ItemDelegate { width: playerSelector.width + highlighted: playerSelector.highlightedIndex === index + background: Rectangle { + width: popup.width - Style.marginS * scaling * 2 + color: highlighted ? Color.mTertiary : Color.transparent + radius: Style.radiusXS * scaling + } contentItem: NText { text: modelData.identity font.pointSize: Style.fontSizeS * scaling - color: highlighted ? Color.mSurface : Color.mOnSurface + color: highlighted ? Color.mOnTertiary : Color.mOnSurface verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } - highlighted: playerSelector.highlightedIndex === index - - background: Rectangle { - width: popup.width - Style.marginS * scaling * 2 - color: highlighted ? Color.mSecondary : Color.transparent - radius: Style.radiusXS * scaling - } } onActivated: { diff --git a/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml b/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml index 689612e2..60e2794c 100644 --- a/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ClockSettings.qml @@ -84,7 +84,7 @@ ColumnLayout { NHeader { label: "Clock display" - description: "Arrange your clock's layout. Click a token below to add it to the selected field." + description: "Customize your clock's display by adding tokens from the list below. To use the 12-hour format, you must include the 'AP' token." } RowLayout { diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml index f6ac29ba..635876a0 100644 --- a/Widgets/NComboBox.qml +++ b/Widgets/NComboBox.qml @@ -85,7 +85,7 @@ RowLayout { popup: Popup { y: combo.height - width: combo.width + implicitWidth: combo.width - Style.marginM * scaling implicitHeight: Math.min(root.popupHeight, contentItem.implicitHeight + Style.marginM * scaling * 2) padding: Style.marginM * scaling @@ -97,12 +97,11 @@ RowLayout { PanelService.willClosePopup(root) } - contentItem: ListView { - property var comboBoxRoot: root - clip: true - implicitHeight: contentHeight + contentItem: NListView { model: combo.popup.visible ? root.model : null - ScrollIndicator.vertical: ScrollIndicator {} + implicitHeight: contentHeight + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded delegate: ItemDelegate { width: combo.width @@ -116,17 +115,15 @@ RowLayout { } onClicked: { - ListView.view.comboBoxRoot.selected(ListView.view.comboBoxRoot.model.get(index).key) + root.selected(root.model.get(index).key) combo.currentIndex = index combo.popup.close() } - contentItem: NText { - text: name - font.pointSize: Style.fontSizeM * scaling - color: highlighted ? Color.mSurface : Color.mOnSurface - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight + background: Rectangle { + width: combo.width - Style.marginM * scaling * 3 + color: highlighted ? Color.mTertiary : Color.transparent + radius: Style.radiusS * scaling Behavior on color { ColorAnimation { duration: Style.animationFast @@ -134,10 +131,12 @@ RowLayout { } } - background: Rectangle { - width: combo.width - Style.marginM * scaling * 3 - color: highlighted ? Color.mTertiary : Color.transparent - radius: Style.radiusS * scaling + contentItem: NText { + text: name + font.pointSize: Style.fontSizeM * scaling + color: highlighted ? Color.mOnTertiary : Color.mOnSurface + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight Behavior on color { ColorAnimation { duration: Style.animationFast diff --git a/Widgets/NDateTimeTokens.qml b/Widgets/NDateTimeTokens.qml index b4897390..904f3c29 100644 --- a/Widgets/NDateTimeTokens.qml +++ b/Widgets/NDateTimeTokens.qml @@ -21,335 +21,312 @@ Rectangle { anchors.margins: Style.marginS * scaling spacing: Style.marginS * scaling - // Scrollable list of tokens - NScrollView { + NListView { + id: tokensList Layout.fillWidth: true Layout.fillHeight: true - clip: true - horizontalPolicy: ScrollBar.AlwaysOff - verticalPolicy: ScrollBar.AsNeeded + model: ListModel { - ListView { - id: tokensList - model: ListModel { + // Common format combinations + ListElement { + category: "Common" + token: "h:mm AP" + description: "12-hour time with minutes" + example: "2:30 PM" + } + ListElement { + category: "Common" + token: "HH:mm" + description: "24-hour time with minutes" + example: "14:30" + } + ListElement { + category: "Common" + token: "HH:mm:ss" + description: "24-hour time with seconds" + example: "14:30:45" + } + ListElement { + category: "Common" + token: "ddd MMM d" + description: "Weekday, month and day" + example: "Mon Dec 25" + } + ListElement { + category: "Common" + token: "yyyy-MM-dd" + description: "ISO date format" + example: "2023-12-25" + } + ListElement { + category: "Common" + token: "MM/dd/yyyy" + description: "US date format" + example: "12/25/2023" + } + ListElement { + category: "Common" + token: "dd.MM.yyyy" + description: "European date format" + example: "25.12.2023" + } + ListElement { + category: "Common" + token: "ddd, MMM dd" + description: "Weekday with date" + example: "Fri, Dec 12" + } - // Common format combinations - ListElement { - category: "Common" - token: "h:mm AP" - description: "12-hour time with minutes" - example: "2:30 PM" - } - ListElement { - category: "Common" - token: "HH:mm" - description: "24-hour time with minutes" - example: "14:30" - } - ListElement { - category: "Common" - token: "HH:mm:ss" - description: "24-hour time with seconds" - example: "14:30:45" - } - ListElement { - category: "Common" - token: "ddd MMM d" - description: "Weekday, month and day" - example: "Mon Dec 25" - } - ListElement { - category: "Common" - token: "yyyy-MM-dd" - description: "ISO date format" - example: "2023-12-25" - } - ListElement { - category: "Common" - token: "MM/dd/yyyy" - description: "US date format" - example: "12/25/2023" - } - ListElement { - category: "Common" - token: "dd.MM.yyyy" - description: "European date format" - example: "25.12.2023" - } - ListElement { - category: "Common" - token: "ddd, MMM dd" - description: "Weekday with date" - example: "Fri, Dec 12" - } + // Hour tokens + // ListElement { + // category: "Hour" + // token: "h" + // description: "Hour without leading zero (12-hour when used with AP/ap, otherwise 24-hour)" + // example: "2 (needs AP/ap for 12hr)" + // } + // ListElement { + // category: "Hour" + // token: "hh" + // description: "Hour with leading zero (12-hour when used with AP/ap, otherwise 24-hour)" + // example: "02 (needs AP/ap for 12hr)" + // } + // ListElement { + // category: "Hour" + // token: "h AP" + // description: "12-hour format with AM/PM" + // example: "2 PM" + // } + // ListElement { + // category: "Hour" + // token: "hh AP" + // description: "12-hour format with leading zero and AM/PM" + // example: "02 PM" + // } + ListElement { + category: "Hour" + token: "H" + description: "Hour without leading zero (0-23) - 24-hour format" + example: "14" + } + ListElement { + category: "Hour" + token: "HH" + description: "Hour with leading zero (00-23) - 24-hour format" + example: "14" + } - // Hour tokens - // ListElement { - // category: "Hour" - // token: "h" - // description: "Hour without leading zero (12-hour when used with AP/ap, otherwise 24-hour)" - // example: "2 (needs AP/ap for 12hr)" - // } - // ListElement { - // category: "Hour" - // token: "hh" - // description: "Hour with leading zero (12-hour when used with AP/ap, otherwise 24-hour)" - // example: "02 (needs AP/ap for 12hr)" - // } - // ListElement { - // category: "Hour" - // token: "h AP" - // description: "12-hour format with AM/PM" - // example: "2 PM" - // } - // ListElement { - // category: "Hour" - // token: "hh AP" - // description: "12-hour format with leading zero and AM/PM" - // example: "02 PM" - // } - ListElement { - category: "Hour" - token: "H" - description: "Hour without leading zero (0-23) - 24-hour format" - example: "14" - } - ListElement { - category: "Hour" - token: "HH" - description: "Hour with leading zero (00-23) - 24-hour format" - example: "14" - } + // Minute tokens + ListElement { + category: "Minute" + token: "m" + description: "Minute without leading zero (0-59)" + example: "30" + } + ListElement { + category: "Minute" + token: "mm" + description: "Minute with leading zero (00-59)" + example: "30" + } - // Minute tokens - ListElement { - category: "Minute" - token: "m" - description: "Minute without leading zero (0-59)" - example: "30" - } - ListElement { - category: "Minute" - token: "mm" - description: "Minute with leading zero (00-59)" - example: "30" - } + // Second tokens + ListElement { + category: "Second" + token: "s" + description: "Second without leading zero (0-59)" + example: "45" + } + ListElement { + category: "Second" + token: "ss" + description: "Second with leading zero (00-59)" + example: "45" + } - // Second tokens - ListElement { - category: "Second" - token: "s" - description: "Second without leading zero (0-59)" - example: "45" - } - ListElement { - category: "Second" - token: "ss" - description: "Second with leading zero (00-59)" - example: "45" - } + // AM/PM tokens + ListElement { + category: "AM/PM" + token: "AP" + description: "AM/PM in uppercase" + example: "PM" + } + ListElement { + category: "AM/PM" + token: "ap" + description: "am/pm in lowercase" + example: "pm" + } - // AM/PM tokens - ListElement { - category: "AM/PM" - token: "AP" - description: "AM/PM in uppercase" - example: "PM" - } - ListElement { - category: "AM/PM" - token: "ap" - description: "am/pm in lowercase" - example: "pm" - } + // Timezone tokens + ListElement { + category: "Timezone" + token: "t" + description: "Timezone abbreviation" + example: "UTC" + } - // Timezone tokens - ListElement { - category: "Timezone" - token: "t" - description: "Timezone abbreviation" - example: "UTC" - } + // Year tokens + ListElement { + category: "Year" + token: "yy" + description: "Year as two-digit number (00-99)" + example: "23" + } + ListElement { + category: "Year" + token: "yyyy" + description: "Year as four-digit number" + example: "2023" + } - // Year tokens - ListElement { - category: "Year" - token: "yy" - description: "Year as two-digit number (00-99)" - example: "23" - } - ListElement { - category: "Year" - token: "yyyy" - description: "Year as four-digit number" - example: "2023" - } + // Month tokens + ListElement { + category: "Month" + token: "M" + description: "Month as number without leading zero (1-12)" + example: "12" + } + ListElement { + category: "Month" + token: "MM" + description: "Month as number with leading zero (01-12)" + example: "12" + } + ListElement { + category: "Month" + token: "MMM" + description: "Abbreviated month name" + example: "Dec" + } + ListElement { + category: "Month" + token: "MMMM" + description: "Full month name" + example: "December" + } - // Month tokens - ListElement { - category: "Month" - token: "M" - description: "Month as number without leading zero (1-12)" - example: "12" - } - ListElement { - category: "Month" - token: "MM" - description: "Month as number with leading zero (01-12)" - example: "12" - } - ListElement { - category: "Month" - token: "MMM" - description: "Abbreviated month name" - example: "Dec" - } - ListElement { - category: "Month" - token: "MMMM" - description: "Full month name" - example: "December" - } + // Day tokens + ListElement { + category: "Day" + token: "d" + description: "Day without leading zero (1-31)" + example: "25" + } + ListElement { + category: "Day" + token: "dd" + description: "Day with leading zero (01-31)" + example: "25" + } + ListElement { + category: "Day" + token: "ddd" + description: "Abbreviated day name" + example: "Mon" + } + ListElement { + category: "Day" + token: "dddd" + description: "Full day name" + example: "Monday" + } + } - // Day tokens - ListElement { - category: "Day" - token: "d" - description: "Day without leading zero (1-31)" - example: "25" + delegate: Rectangle { + id: tokenDelegate + width: tokensList.width + height: layout.implicitHeight + Style.marginS * scaling + radius: Style.radiusS * scaling + color: { + if (tokenMouseArea.containsMouse) { + return Qt.alpha(Color.mPrimary, 0.1) } - ListElement { - category: "Day" - token: "dd" - description: "Day with leading zero (01-31)" - example: "25" - } - ListElement { - category: "Day" - token: "ddd" - description: "Abbreviated day name" - example: "Mon" - } - ListElement { - category: "Day" - token: "dddd" - description: "Full day name" - example: "Monday" + return index % 2 === 0 ? Color.mSurfaceVariant : Qt.alpha(Color.mSurfaceVariant, 0.6) + } + + // Mouse area for the entire delegate + MouseArea { + id: tokenMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onClicked: { + // Emit the signal with the token + root.tokenClicked(model.token) + + // Visual feedback + clickAnimation.start() } } - delegate: Rectangle { - id: tokenDelegate - width: tokensList.width - height: layout.implicitHeight + Style.marginS * scaling - radius: Style.radiusS * scaling - color: { - if (tokenMouseArea.containsMouse) { - return Qt.alpha(Color.mPrimary, 0.1) - } - return index % 2 === 0 ? Color.mSurfaceVariant : Qt.alpha(Color.mSurfaceVariant, 0.6) + // Click animation + SequentialAnimation { + id: clickAnimation + PropertyAnimation { + target: tokenDelegate + property: "color" + to: Qt.alpha(Color.mPrimary, 0.3) + duration: 100 } - - // Mouse area for the entire delegate - MouseArea { - id: tokenMouseArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - - onClicked: { - // Emit the signal with the token - root.tokenClicked(model.token) - - // Visual feedback - clickAnimation.start() - } + PropertyAnimation { + target: tokenDelegate + property: "color" + to: tokenMouseArea.containsMouse ? Qt.alpha(Color.mPrimary, 0.1) : (index % 2 === 0 ? Color.mSurface : Color.mSurfaceVariant) + duration: 200 } + } - // Click animation - SequentialAnimation { - id: clickAnimation - PropertyAnimation { - target: tokenDelegate - property: "color" - to: Qt.alpha(Color.mPrimary, 0.3) - duration: 100 - } - PropertyAnimation { - target: tokenDelegate - property: "color" - to: tokenMouseArea.containsMouse ? Qt.alpha(Color.mPrimary, 0.1) : (index % 2 === 0 ? Color.mSurface : Color.mSurfaceVariant) - duration: 200 - } - } + RowLayout { + id: layout + anchors.fill: parent + anchors.margins: Style.marginXS * scaling + spacing: Style.marginM * scaling - RowLayout { - id: layout - anchors.fill: parent - anchors.margins: Style.marginXS * scaling - spacing: Style.marginM * scaling + // Category badge + Rectangle { + Layout.alignment: Qt.AlignVCenter + width: 70 * scaling + height: 22 * scaling + color: getCategoryColor(model.category)[0] + radius: Style.radiusS * scaling + opacity: tokenMouseArea.containsMouse ? 0.9 : 1.0 - // Category badge - Rectangle { - Layout.alignment: Qt.AlignVCenter - width: 70 * scaling - height: 22 * scaling - color: getCategoryColor(model.category)[0] - radius: Style.radiusS * scaling - opacity: tokenMouseArea.containsMouse ? 0.9 : 1.0 - - Behavior on opacity { - NumberAnimation { - duration: Style.animationFast - } - } - - NText { - anchors.centerIn: parent - text: model.category - color: getCategoryColor(model.category)[1] - font.pointSize: Style.fontSizeXS * scaling + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast } } - // Token - Made more prominent and clickable - Rectangle { - id: tokenButton - Layout.alignment: Qt.AlignVCenter // Added this line - width: 100 * scaling - height: 22 * scaling - color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurface - radius: Style.radiusS * scaling - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - - NText { - anchors.centerIn: parent - text: model.token - color: tokenMouseArea.containsMouse ? Color.mOnPrimary : Color.mSurface - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightBold - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - } - } - - // Description NText { - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter // Added this line - text: model.description - color: tokenMouseArea.containsMouse ? Color.mOnSurface : Color.mOnSurfaceVariant + anchors.centerIn: parent + text: model.category + color: getCategoryColor(model.category)[1] + font.pointSize: Style.fontSizeXS * scaling + } + } + + // Token - Made more prominent and clickable + Rectangle { + id: tokenButton + Layout.alignment: Qt.AlignVCenter // Added this line + width: 100 * scaling + height: 22 * scaling + color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurface + radius: Style.radiusS * scaling + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NText { + anchors.centerIn: parent + text: model.token + color: tokenMouseArea.containsMouse ? Color.mOnPrimary : Color.mSurface font.pointSize: Style.fontSizeS * scaling - wrapMode: Text.WordWrap + font.weight: Style.fontWeightBold Behavior on color { ColorAnimation { @@ -357,41 +334,57 @@ Rectangle { } } } + } - // Live example - Rectangle { - Layout.alignment: Qt.AlignVCenter // Added this line - width: 90 * scaling - height: 22 * scaling - color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant - radius: Style.radiusS * scaling - border.color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) + // Description + NText { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter // Added this line + text: model.description + color: tokenMouseArea.containsMouse ? Color.mOnSurface : Color.mOnSurfaceVariant + font.pointSize: Style.fontSizeS * scaling + wrapMode: Text.WordWrap + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + // Live example + Rectangle { + Layout.alignment: Qt.AlignVCenter // Added this line + width: 90 * scaling + height: 22 * scaling + color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurfaceVariant + radius: Style.radiusS * scaling + border.color: tokenMouseArea.containsMouse ? Color.mPrimary : Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + } + } + + NText { + anchors.centerIn: parent + text: Qt.formatDateTime(root.sampleDate, model.token) + color: tokenMouseArea.containsMouse ? Color.mOnPrimary : Color.mSurfaceVariant + font.pointSize: Style.fontSizeS * scaling Behavior on color { ColorAnimation { duration: Style.animationFast } } - - Behavior on border.color { - ColorAnimation { - duration: Style.animationFast - } - } - - NText { - anchors.centerIn: parent - text: Qt.formatDateTime(root.sampleDate, model.token) - color: tokenMouseArea.containsMouse ? Color.mOnPrimary : Color.mSurfaceVariant - font.pointSize: Style.fontSizeS * scaling - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - } } } } diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml index 9ecb4a6e..27fb2a0d 100644 --- a/Widgets/NSearchableComboBox.qml +++ b/Widgets/NSearchableComboBox.qml @@ -174,13 +174,13 @@ RowLayout { fontSize: Style.fontSizeS * scaling } - ListView { + NListView { id: listView Layout.fillWidth: true Layout.fillHeight: true - clip: true model: combo.popup.visible ? filteredModel : null - ScrollIndicator.vertical: ScrollIndicator {} + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded delegate: ItemDelegate { width: listView.width From d8539c08145f2e1635819345c2fce54671bf1443 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 20:56:00 -0400 Subject: [PATCH 33/54] Removed filepicker icons aliases --- Commons/TablerIcons.qml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/Commons/TablerIcons.qml b/Commons/TablerIcons.qml index adb79ce6..d4eab0fd 100644 --- a/Commons/TablerIcons.qml +++ b/Commons/TablerIcons.qml @@ -119,28 +119,6 @@ Singleton { "bt-device-watch": "device-watch", "bt-device-speaker": "device-speaker", "bt-device-tv": "device-tv", - "filepicker-folder": "folder", - "filepicker-refresh": "refresh", - "filepicker-close": "x", - "filepicker-arrow-left": "arrow-left", - "filepicker-arrow-up": "arrow-up", - "filepicker-home": "home", - "filepicker-layout-grid": "layout-grid", - "filepicker-list": "list", - "filepicker-search": "search", - "filepicker-x": "x", - "filepicker-photo": "photo", - "filepicker-check": "check", - "filepicker-file-text": "file-text", - "filepicker-video": "video", - "filepicker-music": "music", - "filepicker-archive": "archive", - "filepicker-table": "table", - "filepicker-presentation": "presentation", - "filepicker-code": "code", - "filepicker-settings": "settings", - "filepicker-file": "file", - "filepicker-text": "file-text", "noctalia": "noctalia" } From 9f31c61a189c08114d56fd27c9e864091c3c1483 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 21:00:51 -0400 Subject: [PATCH 34/54] Bar section editor: added missing tooltips: --- Modules/Settings/Bar/BarSectionEditor.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/Settings/Bar/BarSectionEditor.qml b/Modules/Settings/Bar/BarSectionEditor.qml index aa006544..777c47d6 100644 --- a/Modules/Settings/Bar/BarSectionEditor.qml +++ b/Modules/Settings/Bar/BarSectionEditor.qml @@ -180,6 +180,7 @@ NBox { active: BarWidgetRegistry.widgetHasUserSettings(modelData.id) sourceComponent: NIconButton { icon: "settings" + tooltipText: "Widget settings" baseSize: miniButtonSize colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight) colorBg: Color.mOnSurface @@ -220,6 +221,7 @@ NBox { NIconButton { icon: "close" + tooltipText: "Remove widget" baseSize: miniButtonSize colorBorder: Qt.alpha(Color.mOutline, Style.opacityLight) colorBg: Color.mOnSurface From 4de2b7f5a8295720819674b684e0653516469b49 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Tue, 23 Sep 2025 03:02:44 +0200 Subject: [PATCH 35/54] LockScreen: fix cursor --- Modules/LockScreen/LockScreen.qml | 232 ++++++++++++++++++++++++------ 1 file changed, 191 insertions(+), 41 deletions(-) diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 84b58c98..62ccc05f 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -559,53 +559,60 @@ Loader { } } - NText { - id: asterisksText - text: "*".repeat(passwordInput.text.length) - color: Color.mOnSurface - font.family: Settings.data.ui.fontFixed - font.pointSize: Style.fontSizeL * scaling - visible: passwordInput.activeFocus && !lockContext.unlockInProgress + // Container for asterisks and cursor to control positioning + Item { Layout.fillWidth: true - wrapMode: Text.NoWrap - maximumLineCount: 1 - elide: Text.ElideRight - clip: true + Layout.preferredHeight: asterisksText.implicitHeight - SequentialAnimation { - id: typingEffect - NumberAnimation { - target: passwordInput - property: "scale" - to: 1.01 - duration: 50 - } - NumberAnimation { - target: passwordInput - property: "scale" - to: 1.0 - duration: 50 + NText { + id: asterisksText + text: "*".repeat(passwordInput.text.length) + color: Color.mOnSurface + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeL * scaling + visible: passwordInput.activeFocus && !lockContext.unlockInProgress + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + wrapMode: Text.NoWrap + maximumLineCount: 1 + elide: Text.ElideRight + + SequentialAnimation { + id: typingEffect + NumberAnimation { + target: passwordInput + property: "scale" + to: 1.01 + duration: 50 + } + NumberAnimation { + target: passwordInput + property: "scale" + to: 1.0 + duration: 50 + } } } - } - Rectangle { - width: 8 * scaling - height: 20 * scaling - color: Color.mPrimary - visible: passwordInput.activeFocus - Layout.leftMargin: -Style.marginS * scaling - Layout.alignment: Qt.AlignVCenter + Rectangle { + width: 8 * scaling + height: 20 * scaling + color: Color.mPrimary + visible: passwordInput.activeFocus + anchors.left: asterisksText.right + anchors.leftMargin: Style.marginXS * scaling + anchors.verticalCenter: parent.verticalCenter - SequentialAnimation on opacity { - loops: Animation.Infinite - NumberAnimation { - to: 1.0 - duration: 500 - } - NumberAnimation { - to: 0.0 - duration: 500 + SequentialAnimation on opacity { + loops: Animation.Infinite + NumberAnimation { + to: 1.0 + duration: 500 + } + NumberAnimation { + to: 0.0 + duration: 500 + } } } } @@ -738,6 +745,149 @@ Loader { } } + // ALARMING Easter Egg for long passwords + Item { + id: easterEggContainer + anchors.fill: parent + z: 1000 + + property bool easterEggTriggered: false + + // Monitor password length + Connections { + target: passwordInput + function onTextChanged() { + if (passwordInput.text.length >= 25) { + easterEggContainer.easterEggTriggered = true + } + } + function onActiveFocusChanged() { + if (!passwordInput.activeFocus) { + easterEggContainer.easterEggTriggered = false + } + } + } + + // Also reset when authentication starts + Connections { + target: lockContext + function onUnlockInProgressChanged() { + if (lockContext.unlockInProgress) { + easterEggContainer.easterEggTriggered = false + } + } + } + + // Scattered warning messages (game-style pop-ups) + Repeater { + model: easterEggContainer.easterEggTriggered && passwordInput.activeFocus && !lockContext.unlockInProgress ? 12 : 0 + + NText { + property var messages: ["BREACH DETECTED", "SECURITY ALERT", "SYSTEM COMPROMISED", "ANOMALY DETECTED", "FIREWALL BREACH", "DEFENSE FAILING", "16 // 16 // 16", "THE ATLAS SEES ALL", "SIMULATION DETECTED", "WAKE UP", "16 16 16 16 16", "KZZT... 16... KZZT", "ERROR ERROR ERROR", "THEY'RE WATCHING", "16 MINUTES REMAIN"] + + property real baseX: Math.random() * (parent.width - 300) + property real baseY: Math.random() * (parent.height - 80) + + text: messages[index % messages.length] + color: Color.mError + font.family: Settings.data.ui.fontFixed + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + + x: baseX + y: baseY + + // Better random positioning avoiding center terminal + Component.onCompleted: { + var centerX = parent.width / 2 + var centerY = parent.height / 2 + var avoidRadius = 350 * scaling + + // If too close to center, push to random edge zones + var distanceFromCenter = Math.sqrt((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY)) + if (distanceFromCenter < avoidRadius) { + // Pick a random edge zone + var zone = Math.floor(Math.random() * 4) + switch (zone) { + case 0: + // Top + x = Math.random() * parent.width + y = Math.random() * 100 * scaling + break + case 1: + // Right + x = parent.width - (50 + Math.random() * 200) * scaling + y = Math.random() * parent.height + break + case 2: + // Bottom + x = Math.random() * parent.width + y = parent.height - (50 + Math.random() * 100) * scaling + break + case 3: + // Left + x = Math.random() * 200 * scaling + y = Math.random() * parent.height + break + } + } + + // Add some random drift to make positioning more varied + x += (Math.random() - 0.5) * 100 * scaling + y += (Math.random() - 0.5) * 50 * scaling + + // Ensure we stay within bounds + x = Math.max(20 * scaling, Math.min(parent.width - 280 * scaling, x)) + y = Math.max(20 * scaling, Math.min(parent.height - 60 * scaling, y)) + } + + // Simple pop-in animation + SequentialAnimation on scale { + loops: Animation.Infinite + PauseAnimation { + duration: index * 400 + Math.random() * 1000 + } + NumberAnimation { + from: 0 + to: 1.2 + duration: 300 + easing.type: Easing.OutBack + } + NumberAnimation { + to: 1.0 + duration: 200 + } + PauseAnimation { + duration: 2000 + Math.random() * 3000 + } + NumberAnimation { + to: 0 + duration: 300 + } + PauseAnimation { + duration: 800 + Math.random() * 1200 + } + } + + // Gentle blinking effect + SequentialAnimation on opacity { + loops: Animation.Infinite + PauseAnimation { + duration: index * 200 + } + NumberAnimation { + to: 0.6 + duration: 400 + Math.random() * 300 + } + NumberAnimation { + to: 1.0 + duration: 300 + Math.random() * 200 + } + } + } + } + } + // Power buttons at bottom right RowLayout { anchors.right: parent.right From d5a862d90429374a513f7d41e327b5a0e430e618 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Tue, 23 Sep 2025 03:08:32 +0200 Subject: [PATCH 36/54] shell: remove reload popup, except for error --- shell.qml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/shell.qml b/shell.qml index 5d5f9240..2b4b0b4e 100644 --- a/shell.qml +++ b/shell.qml @@ -116,4 +116,12 @@ ShellRoot { // Save a ref. to our lockScreen so we can access it easily PanelService.lockScreen = lockScreen } + + Connections { + function onReloadCompleted() { + Quickshell.inhibitReloadPopup(); + } + + target: Quickshell + } } From 3da0e529c62241f08967115957e325d49b0eeb6d Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 21:09:45 -0400 Subject: [PATCH 37/54] Shell: cleanup --- shell.qml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/shell.qml b/shell.qml index 2b4b0b4e..d801b401 100644 --- a/shell.qml +++ b/shell.qml @@ -6,8 +6,6 @@ * but proper credit must be given to the original author. */ -// Disable reload popup add this as a new row: //pragma Env QS_NO_RELOAD_POPUP=1 - // Qt & Quickshell Core import QtQuick import Quickshell @@ -118,10 +116,9 @@ ShellRoot { } Connections { + target: Quickshell function onReloadCompleted() { Quickshell.inhibitReloadPopup(); } - - target: Quickshell } } From 8f8f6c23ea3594fdf402cd558541480108d85401 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 21:33:38 -0400 Subject: [PATCH 38/54] Bar Editor: added ability to move widget to other sections with right clicking context menu. --- Modules/Settings/Bar/BarSectionEditor.qml | 46 +++++++++ Modules/Settings/Tabs/BarTab.qml | 22 +++++ Widgets/NContextMenu.qml | 113 ++++++++++++++++++++++ shell.qml | 2 +- 4 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 Widgets/NContextMenu.qml diff --git a/Modules/Settings/Bar/BarSectionEditor.qml b/Modules/Settings/Bar/BarSectionEditor.qml index 777c47d6..2652eff3 100644 --- a/Modules/Settings/Bar/BarSectionEditor.qml +++ b/Modules/Settings/Bar/BarSectionEditor.qml @@ -20,6 +20,7 @@ NBox { signal removeWidget(string section, int index) signal reorderWidget(string section, int fromIndex, int toIndex) signal updateWidgetSettings(string section, int index, var settings) + signal moveWidget(string fromSection, int index, string toSection) signal dragPotentialStarted signal dragPotentialEnded @@ -125,6 +126,7 @@ NBox { Repeater { model: widgetModel + delegate: Rectangle { id: widgetItem required property int index @@ -158,6 +160,50 @@ NBox { } } + // Context menu for moving widget to other sections + NContextMenu { + id: contextMenu + parent: Overlay.overlay + model: [{ + "label": "Move to Left", + "action": "left", + "icon": "arrow-left", + "visible": root.sectionId !== "left" + }, { + "label": "Move to Center", + "action": "center", + "icon": "arrows-horizontal", + "visible": root.sectionId !== "center" + }, { + "label": "Move to Right", + "action": "right", + "icon": "arrow-right", + "visible": root.sectionId !== "right" + }] + + onTriggered: action => root.moveWidget(root.sectionId, index, action) + } + + // Update the MouseArea to use the new context menu + MouseArea { + id: contextMouseArea + anchors.fill: parent + acceptedButtons: Qt.RightButton + z: -1 // Below the buttons but above background + + onClicked: mouse => { + if (mouse.button === Qt.RightButton) { + // 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) + } + } + } + } RowLayout { id: widgetContent anchors.centerIn: parent diff --git a/Modules/Settings/Tabs/BarTab.qml b/Modules/Settings/Tabs/BarTab.qml index 3ad30376..2049959c 100644 --- a/Modules/Settings/Tabs/BarTab.qml +++ b/Modules/Settings/Tabs/BarTab.qml @@ -219,6 +219,7 @@ ColumnLayout { onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index) onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection) onDragPotentialStarted: root.handleDragStart() onDragPotentialEnded: root.handleDragEnd() } @@ -233,6 +234,7 @@ ColumnLayout { onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index) onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection) onDragPotentialStarted: root.handleDragStart() onDragPotentialEnded: root.handleDragEnd() } @@ -247,6 +249,7 @@ ColumnLayout { onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index) onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) + onMoveWidget: (fromSection, index, toSection) => _moveWidgetBetweenSections(fromSection, index, toSection) onDragPotentialStarted: root.handleDragStart() onDragPotentialEnded: root.handleDragEnd() } @@ -341,6 +344,25 @@ ColumnLayout { //Logger.log("BarTab", `Updated widget settings for ${settings.id} in ${section} section`) } + function _moveWidgetBetweenSections(fromSection, index, toSection) { + // Get the widget from the source section + if (index >= 0 && index < Settings.data.bar.widgets[fromSection].length) { + var widget = Settings.data.bar.widgets[fromSection][index] + + // Remove from source section + var sourceArray = Settings.data.bar.widgets[fromSection].slice() + sourceArray.splice(index, 1) + Settings.data.bar.widgets[fromSection] = sourceArray + + // Add to target section + var targetArray = Settings.data.bar.widgets[toSection].slice() + targetArray.push(widget) + Settings.data.bar.widgets[toSection] = targetArray + + //Logger.log("BarTab", `Moved widget ${widget.id} from ${fromSection} to ${toSection}`) + } + } + // Base list model for all combo boxes ListModel { id: availableWidgets diff --git a/Widgets/NContextMenu.qml b/Widgets/NContextMenu.qml new file mode 100644 index 00000000..a1b7562c --- /dev/null +++ b/Widgets/NContextMenu.qml @@ -0,0 +1,113 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Services + +Popup { + id: root + + property alias model: listView.model + property real itemHeight: 36 * scaling + property real itemPadding: Style.marginM * scaling + + signal triggered(string action) + + width: 180 * scaling + padding: Style.marginS * scaling + + onOpened: PanelService.willOpenPopup(root) + onClosed: PanelService.willClosePopup(root) + + background: Rectangle { + color: Color.mSurfaceVariant + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + radius: Style.radiusM * scaling + } + + contentItem: ListView { + id: listView + implicitHeight: contentHeight + spacing: Style.marginXXS * scaling + interactive: contentHeight > root.height + clip: true + + delegate: ItemDelegate { + id: menuItem + width: listView.width + height: modelData.visible !== false ? root.itemHeight : 0 + visible: modelData.visible !== false + opacity: modelData.enabled !== false ? 1.0 : 0.5 + enabled: modelData.enabled !== false + + // Store reference to the popup + property var popup: root + + background: Rectangle { + color: menuItem.hovered && menuItem.enabled ? Color.mTertiary : Color.transparent + radius: Style.radiusS * scaling + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + contentItem: RowLayout { + spacing: Style.marginS * scaling + + // Optional icon + NIcon { + visible: modelData.icon !== undefined + icon: modelData.icon || "" + font.pointSize: Style.fontSizeM * scaling + color: menuItem.hovered && menuItem.enabled ? Color.mOnTertiary : Color.mOnSurface + Layout.leftMargin: root.itemPadding + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + NText { + text: modelData.label || modelData.text || "" + font.pointSize: Style.fontSizeM * scaling + color: menuItem.hovered && menuItem.enabled ? Color.mOnTertiary : Color.mOnSurface + verticalAlignment: Text.AlignVCenter + Layout.fillWidth: true + Layout.leftMargin: modelData.icon === undefined ? root.itemPadding : 0 + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + + onClicked: { + if (enabled) { + popup.triggered(modelData.action || modelData.key || index.toString()) + popup.close() + } + } + } + } + + // Helper function to open at mouse position + function openAt(x, y) { + root.x = x + root.y = y + root.open() + } + + // Helper function to open at item + function openAtItem(item, mouseX, mouseY) { + var pos = item.mapToItem(root.parent, mouseX || 0, mouseY || 0) + openAt(pos.x, pos.y) + } +} diff --git a/shell.qml b/shell.qml index d801b401..e7f53d16 100644 --- a/shell.qml +++ b/shell.qml @@ -118,7 +118,7 @@ ShellRoot { Connections { target: Quickshell function onReloadCompleted() { - Quickshell.inhibitReloadPopup(); + Quickshell.inhibitReloadPopup() } } } From d745be9c96c554c6aa09768064a318a0369990be Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 21:45:22 -0400 Subject: [PATCH 39/54] Bar section editor: better icons for move across sections --- Modules/Settings/Bar/BarSectionEditor.qml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Modules/Settings/Bar/BarSectionEditor.qml b/Modules/Settings/Bar/BarSectionEditor.qml index 2652eff3..39dc62e3 100644 --- a/Modules/Settings/Bar/BarSectionEditor.qml +++ b/Modules/Settings/Bar/BarSectionEditor.qml @@ -164,20 +164,21 @@ NBox { NContextMenu { id: contextMenu parent: Overlay.overlay + width: 240 * scaling model: [{ - "label": "Move to Left", + "label": "Move to left section", "action": "left", - "icon": "arrow-left", + "icon": "arrow-bar-to-left", "visible": root.sectionId !== "left" }, { - "label": "Move to Center", + "label": "Move to center section", "action": "center", - "icon": "arrows-horizontal", + "icon": "layout-columns", "visible": root.sectionId !== "center" }, { - "label": "Move to Right", + "label": "Move to right section", "action": "right", - "icon": "arrow-right", + "icon": "arrow-bar-to-right", "visible": root.sectionId !== "right" }] From 807e7394fea4460650a741fbf866c95b47ea8138 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 22:07:29 -0400 Subject: [PATCH 40/54] Cava + Visualizer: Should not depend on mpris. Its by design. --- Modules/Bar/Widgets/MediaMini.qml | 6 ++--- Modules/ControlCenter/Cards/MediaCard.qml | 6 ++--- Modules/LockScreen/LockScreen.qml | 31 +++-------------------- Services/CavaService.qml | 1 - 4 files changed, 9 insertions(+), 35 deletions(-) diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 6d55d481..e8b3fd90 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -100,7 +100,7 @@ Item { Loader { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter - active: showVisualizer && visualizerType == "linear" && MediaService.isPlaying + active: showVisualizer && visualizerType == "linear" z: 0 sourceComponent: LinearSpectrum { @@ -115,7 +115,7 @@ Item { Loader { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter - active: showVisualizer && visualizerType == "mirrored" && MediaService.isPlaying + active: showVisualizer && visualizerType == "mirrored" z: 0 sourceComponent: MirroredSpectrum { @@ -130,7 +130,7 @@ Item { Loader { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter - active: showVisualizer && visualizerType == "wave" && MediaService.isPlaying + active: showVisualizer && visualizerType == "wave" z: 0 sourceComponent: WaveSpectrum { diff --git a/Modules/ControlCenter/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml index 3c87aa62..49307d93 100644 --- a/Modules/ControlCenter/Cards/MediaCard.qml +++ b/Modules/ControlCenter/Cards/MediaCard.qml @@ -331,7 +331,7 @@ NBox { } Loader { - active: Settings.data.audio.visualizerType == "linear" && MediaService.isPlaying + active: Settings.data.audio.visualizerType == "linear" Layout.alignment: Qt.AlignHCenter sourceComponent: LinearSpectrum { @@ -344,7 +344,7 @@ NBox { } Loader { - active: Settings.data.audio.visualizerType == "mirrored" && MediaService.isPlaying + active: Settings.data.audio.visualizerType == "mirrored" Layout.alignment: Qt.AlignHCenter sourceComponent: MirroredSpectrum { @@ -357,7 +357,7 @@ NBox { } Loader { - active: Settings.data.audio.visualizerType == "wave" && MediaService.isPlaying + active: Settings.data.audio.visualizerType == "wave" Layout.alignment: Qt.AlignHCenter sourceComponent: WaveSpectrum { diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 62ccc05f..16219f92 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -199,7 +199,7 @@ Loader { z: 10 Loader { - active: MediaService.isPlaying && Settings.data.audio.visualizerType == "linear" + active: Settings.data.audio.visualizerType == "linear" anchors.centerIn: parent width: 160 * scaling height: 160 * scaling @@ -228,7 +228,7 @@ Loader { } Loader { - active: MediaService.isPlaying && Settings.data.audio.visualizerType == "mirrored" + active: Settings.data.audio.visualizerType == "mirrored" anchors.centerIn: parent width: 160 * scaling height: 160 * scaling @@ -258,7 +258,7 @@ Loader { } Loader { - active: MediaService.isPlaying && Settings.data.audio.visualizerType == "wave" + active: Settings.data.audio.visualizerType == "wave" anchors.centerIn: parent width: 160 * scaling height: 160 * scaling @@ -305,31 +305,6 @@ Loader { } } - Rectangle { - anchors.centerIn: parent - width: parent.width + 24 * scaling - height: parent.height + 24 * scaling - radius: width * 0.5 - color: Color.transparent - border.color: Qt.alpha(Color.mPrimary, 0.3) - border.width: Math.max(1, Style.borderM * scaling) - z: -1 - visible: !MediaService.isPlaying - SequentialAnimation on scale { - loops: Animation.Infinite - NumberAnimation { - to: 1.1 - duration: 1500 - easing.type: Easing.InOutQuad - } - NumberAnimation { - to: 1.0 - duration: 1500 - easing.type: Easing.InOutQuad - } - } - } - NImageCircled { anchors.centerIn: parent width: 100 * scaling diff --git a/Services/CavaService.qml b/Services/CavaService.qml index d31d3e92..30495da3 100644 --- a/Services/CavaService.qml +++ b/Services/CavaService.qml @@ -37,7 +37,6 @@ Singleton { Process { id: process stdinEnabled: true - running: MediaService.isPlaying command: ["cava", "-p", "/dev/stdin"] onExited: { stdinEnabled = true From 39e58acadeee63e11ca152332413da3bb797dbcb Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 22:34:35 -0400 Subject: [PATCH 41/54] MediaCard: Using the new NContextMenu --- Modules/ControlCenter/Cards/MediaCard.qml | 147 +++++++++------------- 1 file changed, 60 insertions(+), 87 deletions(-) diff --git a/Modules/ControlCenter/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml index 49307d93..f5fa1889 100644 --- a/Modules/ControlCenter/Cards/MediaCard.qml +++ b/Modules/ControlCenter/Cards/MediaCard.qml @@ -32,11 +32,11 @@ NBox { color: Color.mPrimary Layout.alignment: Qt.AlignHCenter } - NText { - text: "No media player detected" - color: Color.mOnSurfaceVariant - Layout.alignment: Qt.AlignHCenter - } + // NText { + // text: "No media player detected" + // color: Color.mOnSurfaceVariant + // Layout.alignment: Qt.AlignHCenter + // } Item { Layout.fillWidth: true @@ -51,98 +51,71 @@ NBox { visible: MediaService.currentPlayer && MediaService.canPlay spacing: Style.marginM * scaling - // Player selector - ComboBox { - id: playerSelector + // Player selector using NContextMenu + Rectangle { + id: playerSelectorButton Layout.fillWidth: true - Layout.preferredHeight: Style.barHeight * 0.83 * scaling + Layout.preferredHeight: Style.barHeight * scaling visible: MediaService.getAvailablePlayers().length > 1 - model: MediaService.getAvailablePlayers() - textRole: "identity" - currentIndex: MediaService.selectedPlayerIndex + radius: Style.radiusM * scaling + color: Color.transparent - background: Rectangle { - visible: false - // implicitWidth: 120 * scaling - // implicitHeight: 30 * scaling - color: Color.transparent - border.color: playerSelector.activeFocus ? Color.mSecondary : Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - radius: Style.radiusM * scaling - } + property var currentPlayer: MediaService.getAvailablePlayers()[MediaService.selectedPlayerIndex] - contentItem: NText { - visible: false - leftPadding: Style.marginM * scaling - rightPadding: playerSelector.indicator.width + playerSelector.spacing - text: playerSelector.displayText - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurface - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - } - - indicator: NIcon { - x: playerSelector.width - width - y: playerSelector.topPadding + (playerSelector.availableHeight - height) / 2 - icon: "caret-down" - font.pointSize: Style.fontSizeXXL * scaling - color: Color.mOnSurface - horizontalAlignment: Text.AlignRight - } - - popup: Popup { - id: popup - x: playerSelector.width * 0.5 - y: playerSelector.height * 0.75 - width: playerSelector.width * 0.5 - implicitHeight: Math.min(160 * scaling, contentItem.implicitHeight + Style.marginM * 2 * scaling) - padding: Style.marginS * scaling - - onOpened: { - PanelService.willOpenPopup(root) + RowLayout { + anchors.fill: parent + spacing: Style.marginS * scaling + + NIcon { + icon: "caret-down" + font.pointSize: Style.fontSizeXXL * scaling + color: Color.mOnSurfaceVariant } - onClosed: { - PanelService.willClosePopup(root) - } - - contentItem: NListView { - implicitHeight: contentHeight - model: playerSelector.popup.visible ? playerSelector.delegateModel : null - currentIndex: playerSelector.highlightedIndex - horizontalPolicy: ScrollBar.AlwaysOff - verticalPolicy: ScrollBar.AsNeeded - } - - background: Rectangle { - color: Color.mSurface - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - radius: Style.radiusS * scaling + NText { + text: playerSelectorButton.currentPlayer ? playerSelectorButton.currentPlayer.identity : "" + font.pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + Layout.fillWidth: true } } - delegate: ItemDelegate { - width: playerSelector.width - highlighted: playerSelector.highlightedIndex === index - background: Rectangle { - width: popup.width - Style.marginS * scaling * 2 - color: highlighted ? Color.mTertiary : Color.transparent - radius: Style.radiusXS * scaling - } - contentItem: NText { - text: modelData.identity - font.pointSize: Style.fontSizeS * scaling - color: highlighted ? Color.mOnTertiary : Color.mOnSurface - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight + MouseArea { + id: playerSelectorMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onClicked: { + // Create menu items from available players + var menuItems = [] + var players = MediaService.getAvailablePlayers() + for (var i = 0; i < players.length; i++) { + menuItems.push({ + label: players[i].identity, + action: i.toString(), + icon: "disc", + enabled: true, + visible: true + }) + } + playerContextMenu.model = menuItems + playerContextMenu.openAtItem(playerSelectorButton, playerSelectorButton.width - playerContextMenu.width, playerSelectorButton.height) } } - onActivated: { - MediaService.selectedPlayerIndex = currentIndex - MediaService.updateCurrentPlayer() + NContextMenu { + id: playerContextMenu + parent: root + width: 200 * scaling + + onTriggered: function(action) { + var index = parseInt(action) + if (!isNaN(index)) { + MediaService.selectedPlayerIndex = index + MediaService.updateCurrentPlayer() + } + } } } @@ -200,7 +173,7 @@ NBox { NText { visible: MediaService.trackArtist !== "" text: MediaService.trackArtist - color: Color.mOnSurface + color: Color.mPrimary font.pointSize: Style.fontSizeXS * scaling elide: Text.ElideRight Layout.fillWidth: true @@ -369,4 +342,4 @@ NBox { } } } -} +} \ No newline at end of file From 26fe3114a645f48fff784237b3e730d72c0d0cba Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 22:39:47 -0400 Subject: [PATCH 42/54] Settings: updated comments --- Commons/Settings.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 04359571..14245f97 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -53,6 +53,7 @@ Singleton { // This should only be activated once when the settings structure has changed // Then it should be commented out again, regular users don't need to generate // default settings on every start + // TODO: automate this someday! //generateDefaultSettings() // Patch-in the local default, resolved to user's home From 120ed36deb41bd12b8659d6095ff9d238a1eb127 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 22 Sep 2025 22:41:24 -0400 Subject: [PATCH 43/54] Cava: always active --- Services/CavaService.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Services/CavaService.qml b/Services/CavaService.qml index 30495da3..cdc0c15b 100644 --- a/Services/CavaService.qml +++ b/Services/CavaService.qml @@ -37,6 +37,7 @@ Singleton { Process { id: process stdinEnabled: true + running: true // Yes, cava should run at all times! Even if no mpris is available. command: ["cava", "-p", "/dev/stdin"] onExited: { stdinEnabled = true From 640a4339db26a6e017d67fa3f5d389595615fcdd Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Tue, 23 Sep 2025 08:37:16 -0400 Subject: [PATCH 44/54] Cava: Now only runs when a visualizer is in sight. --- Services/BarService.qml | 24 ++++++++++++++++++++++++ Services/BarWidgetRegistry.qml | 4 ++-- Services/CavaService.qml | 9 ++++++--- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Services/BarService.qml b/Services/BarService.qml index 9fc6749b..96c5f2f1 100644 --- a/Services/BarService.qml +++ b/Services/BarService.qml @@ -1,11 +1,32 @@ pragma Singleton +import QtQuick import Quickshell import qs.Commons Singleton { id: root + property bool hasAudioVisualizer: false + + // Simple timer that run once when the widget structure has changed + // and determine if any MediaMini widget has the visualizer on + Timer { + id: timerCheckVisualizer + interval: 100 + repeat: false + onTriggered: { + hasAudioVisualizer = false + const widgets = getAllWidgetInstances("MediaMini") + for(var i=0; i Date: Tue, 23 Sep 2025 09:25:44 -0400 Subject: [PATCH 45/54] Autoformatting --- Modules/ControlCenter/Cards/MediaCard.qml | 20 ++++++++++---------- Services/BarService.qml | 2 +- Services/CavaService.qml | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Modules/ControlCenter/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml index f5fa1889..5e4ad4f8 100644 --- a/Modules/ControlCenter/Cards/MediaCard.qml +++ b/Modules/ControlCenter/Cards/MediaCard.qml @@ -32,12 +32,12 @@ NBox { color: Color.mPrimary Layout.alignment: Qt.AlignHCenter } + // NText { // text: "No media player detected" // color: Color.mOnSurfaceVariant // Layout.alignment: Qt.AlignHCenter // } - Item { Layout.fillWidth: true Layout.fillHeight: true @@ -65,7 +65,7 @@ NBox { RowLayout { anchors.fill: parent spacing: Style.marginS * scaling - + NIcon { icon: "caret-down" font.pointSize: Style.fontSizeXXL * scaling @@ -92,12 +92,12 @@ NBox { var players = MediaService.getAvailablePlayers() for (var i = 0; i < players.length; i++) { menuItems.push({ - label: players[i].identity, - action: i.toString(), - icon: "disc", - enabled: true, - visible: true - }) + "label": players[i].identity, + "action": i.toString(), + "icon": "disc", + "enabled": true, + "visible": true + }) } playerContextMenu.model = menuItems playerContextMenu.openAtItem(playerSelectorButton, playerSelectorButton.width - playerContextMenu.width, playerSelectorButton.height) @@ -109,7 +109,7 @@ NBox { parent: root width: 200 * scaling - onTriggered: function(action) { + onTriggered: function (action) { var index = parseInt(action) if (!isNaN(index)) { MediaService.selectedPlayerIndex = index @@ -342,4 +342,4 @@ NBox { } } } -} \ No newline at end of file +} diff --git a/Services/BarService.qml b/Services/BarService.qml index 96c5f2f1..a459dee0 100644 --- a/Services/BarService.qml +++ b/Services/BarService.qml @@ -18,7 +18,7 @@ Singleton { onTriggered: { hasAudioVisualizer = false const widgets = getAllWidgetInstances("MediaMini") - for(var i=0; i Date: Tue, 23 Sep 2025 10:22:59 -0400 Subject: [PATCH 46/54] DockMenu: minor UI tweaks. --- Commons/TablerIcons.qml | 3 +++ Modules/Dock/DockMenu.qml | 21 ++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Commons/TablerIcons.qml b/Commons/TablerIcons.qml index d4eab0fd..75d8f0c6 100644 --- a/Commons/TablerIcons.qml +++ b/Commons/TablerIcons.qml @@ -56,6 +56,9 @@ Singleton { "keep-awake-on": "mug", "keep-awake-off": "mug-off", "disc": "disc-filled", + "eye": "eye", + "pin": "pin", + "unpin": "pinned-off", "image": "photo", "dark-mode": "contrast-filled", "camera-video": "video", diff --git a/Modules/Dock/DockMenu.qml b/Modules/Dock/DockMenu.qml index 5b8cebd9..ff4cfa78 100644 --- a/Modules/Dock/DockMenu.qml +++ b/Modules/Dock/DockMenu.qml @@ -93,7 +93,7 @@ PopupWindow { Rectangle { anchors.fill: parent - color: Color.mSurface + color: Color.mSurfaceVariant radius: Style.radiusS * scaling border.color: Color.mOutline border.width: Math.max(1, Style.borderS * scaling) @@ -128,14 +128,14 @@ PopupWindow { NIcon { icon: "eye" font.pointSize: Style.fontSizeL * scaling - color: activateMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface + color: activateMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant anchors.verticalCenter: parent.verticalCenter } NText { text: "Focus" font.pointSize: Style.fontSizeS * scaling - color: activateMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface + color: activateMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant anchors.verticalCenter: parent.verticalCenter } } @@ -150,7 +150,6 @@ PopupWindow { if (root.toplevel?.activate) { root.toplevel.activate() } - root.hide() root.requestClose() } } @@ -173,10 +172,10 @@ PopupWindow { icon: { if (!root.toplevel) return "pin" - return root.isAppPinned(root.toplevel.appId) ? "pinned-off" : "pin" + return root.isAppPinned(root.toplevel.appId) ? "unpin" : "pin" } font.pointSize: Style.fontSizeL * scaling - color: pinMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface + color: pinMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant anchors.verticalCenter: parent.verticalCenter } @@ -187,7 +186,7 @@ PopupWindow { return root.isAppPinned(root.toplevel.appId) ? "Unpin" : "Pin" } font.pointSize: Style.fontSizeS * scaling - color: pinMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface + color: pinMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant anchors.verticalCenter: parent.verticalCenter } } @@ -202,7 +201,7 @@ PopupWindow { if (root.toplevel?.appId) { root.toggleAppPin(root.toplevel.appId) } - root.hide() + //root.hide() root.requestClose() } } @@ -222,16 +221,16 @@ PopupWindow { spacing: Style.marginS * scaling NIcon { - icon: "x" + icon: "close" font.pointSize: Style.fontSizeL * scaling - color: closeMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface + color: closeMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant anchors.verticalCenter: parent.verticalCenter } NText { text: "Close" font.pointSize: Style.fontSizeS * scaling - color: closeMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurface + color: closeMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant anchors.verticalCenter: parent.verticalCenter } } From 1ca84bf0525aaf6637d62a3040e7b8e9a56a07b0 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Tue, 23 Sep 2025 17:53:24 +0200 Subject: [PATCH 47/54] OSD: Implement Volume & Brightness OSD --- Assets/settings-default.json | 3 +- Commons/Settings.qml | 1 + Modules/ControlCenter/Cards/MediaCard.qml | 20 +- Modules/OSD/BrightnessOSD.qml | 219 ++++++++++++++++++++++ Modules/OSD/VolumeOSD.qml | 217 +++++++++++++++++++++ Modules/Settings/Tabs/GeneralTab.qml | 7 + shell.qml | 14 ++ 7 files changed, 470 insertions(+), 11 deletions(-) create mode 100644 Modules/OSD/BrightnessOSD.qml create mode 100644 Modules/OSD/VolumeOSD.qml diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 920bb69a..d040ed1b 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -70,7 +70,8 @@ "forceBlackScreenCorners": false, "radiusRatio": 1, "screenRadiusRatio": 1, - "animationSpeed": 1 + "animationSpeed": 1, + "showOSD": true }, "location": { "name": "Tokyo", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 14245f97..a49c029b 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -177,6 +177,7 @@ Singleton { property real radiusRatio: 1.0 property real screenRadiusRatio: 1.0 property real animationSpeed: 1.0 + property bool showOSD: true } // location diff --git a/Modules/ControlCenter/Cards/MediaCard.qml b/Modules/ControlCenter/Cards/MediaCard.qml index f5fa1889..5e4ad4f8 100644 --- a/Modules/ControlCenter/Cards/MediaCard.qml +++ b/Modules/ControlCenter/Cards/MediaCard.qml @@ -32,12 +32,12 @@ NBox { color: Color.mPrimary Layout.alignment: Qt.AlignHCenter } + // NText { // text: "No media player detected" // color: Color.mOnSurfaceVariant // Layout.alignment: Qt.AlignHCenter // } - Item { Layout.fillWidth: true Layout.fillHeight: true @@ -65,7 +65,7 @@ NBox { RowLayout { anchors.fill: parent spacing: Style.marginS * scaling - + NIcon { icon: "caret-down" font.pointSize: Style.fontSizeXXL * scaling @@ -92,12 +92,12 @@ NBox { var players = MediaService.getAvailablePlayers() for (var i = 0; i < players.length; i++) { menuItems.push({ - label: players[i].identity, - action: i.toString(), - icon: "disc", - enabled: true, - visible: true - }) + "label": players[i].identity, + "action": i.toString(), + "icon": "disc", + "enabled": true, + "visible": true + }) } playerContextMenu.model = menuItems playerContextMenu.openAtItem(playerSelectorButton, playerSelectorButton.width - playerContextMenu.width, playerSelectorButton.height) @@ -109,7 +109,7 @@ NBox { parent: root width: 200 * scaling - onTriggered: function(action) { + onTriggered: function (action) { var index = parseInt(action) if (!isNaN(index)) { MediaService.selectedPlayerIndex = index @@ -342,4 +342,4 @@ NBox { } } } -} \ No newline at end of file +} diff --git a/Modules/OSD/BrightnessOSD.qml b/Modules/OSD/BrightnessOSD.qml new file mode 100644 index 00000000..c2d5e3fb --- /dev/null +++ b/Modules/OSD/BrightnessOSD.qml @@ -0,0 +1,219 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services +import qs.Widgets + +Loader { + id: windowLoader + active: false + + readonly property real scaling: ScalingService.getScreenScale(Quickshell.screens[0]) + readonly property real currentBrightness: { + if (BrightnessService.monitors.length > 0) { + return BrightnessService.monitors[0].brightness || 0 + } + return 0 + } + + // Used to avoid showing OSD on Quickshell startup + property bool firstBrightnessReceived: false + + function getIcon() { + var brightness = currentBrightness + return brightness <= 0.5 ? "brightness-low" : "brightness-high" + } + + sourceComponent: PanelWindow { + id: panel + + screen: Quickshell.screens[0] // Use primary screen + + anchors { + top: true + } + + implicitWidth: 320 * windowLoader.scaling + implicitHeight: osdItem.height + + // Set margins based on bar position + margins.top: { + switch (Settings.data.bar.position) { + case "top": + return (Style.barHeight + Style.marginS) * windowLoader.scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * windowLoader.scaling : 0) + default: + return Style.marginL * windowLoader.scaling + } + } + + color: Color.transparent + + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + exclusionMode: PanelWindow.ExclusionMode.Ignore + + Rectangle { + id: osdItem + + width: parent.width + height: Math.round(contentLayout.implicitHeight + Style.marginL * 2 * windowLoader.scaling) + radius: Style.radiusL * windowLoader.scaling + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(2, Style.borderM * windowLoader.scaling) + visible: false + opacity: 0 + scale: 0.7 + + anchors.horizontalCenter: parent.horizontalCenter + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Behavior on scale { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Timer { + id: hideTimer + interval: 2000 + onTriggered: osdItem.hide() + } + + RowLayout { + id: contentLayout + anchors.fill: parent + anchors.margins: Style.marginM * windowLoader.scaling + spacing: Style.marginM * windowLoader.scaling + + NIcon { + icon: windowLoader.getIcon() + color: Color.mOnSurface + font.pointSize: Style.fontSizeXL * windowLoader.scaling + Layout.alignment: Qt.AlignVCenter + } + + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: Style.marginXS * windowLoader.scaling + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: Math.round(6 * windowLoader.scaling) + radius: Math.round(3 * windowLoader.scaling) + color: Color.mSurfaceVariant + + Rectangle { + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + width: parent.width * windowLoader.currentBrightness + radius: parent.radius + color: Color.mPrimary + + Behavior on width { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + } + + NText { + text: Math.round(windowLoader.currentBrightness * 100) + "%" + color: Color.mOnSurfaceVariant + font.pointSize: Style.fontSizeS * windowLoader.scaling + Layout.alignment: Qt.AlignVCenter + Layout.minimumWidth: Math.round(32 * windowLoader.scaling) + } + } + } + + function show() { + hideTimer.stop() + osdItem.visible = true + osdItem.opacity = 1 + osdItem.scale = 1.0 + hideTimer.start() + } + + function hide() { + hideTimer.stop() + osdItem.opacity = 0 + osdItem.scale = 0.7 + + Qt.callLater(function () { + osdItem.visible = false + windowLoader.active = false + }) + } + } + + function showOSD() { + osdItem.show() + } + } + + // Monitor brightness changes from all monitors + Connections { + target: BrightnessService + + function onMonitorsChanged() { + // Connect to brightness changes for each monitor + for (var i = 0; i < BrightnessService.monitors.length; i++) { + let monitor = BrightnessService.monitors[i] + monitor.brightnessUpdated.connect(windowLoader.onBrightnessChanged) + } + } + } + + // Connect to existing monitors on component completion + Component.onCompleted: { + for (var i = 0; i < BrightnessService.monitors.length; i++) { + let monitor = BrightnessService.monitors[i] + monitor.brightnessUpdated.connect(windowLoader.onBrightnessChanged) + } + } + + function onBrightnessChanged(newBrightness) { + if (!firstBrightnessReceived) { + // Ignore the first brightness change on startup + firstBrightnessReceived = true + } else { + showOSD() + } + } + + // Signal to coordinate with other OSDs + signal osdShowing + + function showOSD() { + // Check if OSD is enabled in settings + if (!Settings.data.general.showOSD) { + return + } + + osdShowing() // Notify other OSDs to hide + windowLoader.active = true + if (windowLoader.item) { + windowLoader.item.showOSD() + } + } + + function hideOSD() { + if (windowLoader.item) { + windowLoader.item.osdItem.hideImmediately() + } + } +} diff --git a/Modules/OSD/VolumeOSD.qml b/Modules/OSD/VolumeOSD.qml new file mode 100644 index 00000000..1c28291c --- /dev/null +++ b/Modules/OSD/VolumeOSD.qml @@ -0,0 +1,217 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services +import qs.Widgets + +Loader { + id: windowLoader + active: false + + readonly property real currentVolume: AudioService.volume + readonly property bool isMuted: AudioService.muted + readonly property real scaling: ScalingService.getScreenScale(Quickshell.screens[0]) + + // Used to avoid showing OSD on Quickshell startup + property bool firstVolumeReceived: false + property bool firstMuteReceived: false + + function getIcon() { + if (AudioService.muted) { + return "volume-mute" + } + return (AudioService.volume <= Number.EPSILON) ? "volume-zero" : (AudioService.volume <= 0.5) ? "volume-low" : "volume-high" + } + + sourceComponent: PanelWindow { + id: panel + + screen: Quickshell.screens[0] // Use primary screen + + anchors { + top: true + } + + implicitWidth: 320 * windowLoader.scaling + implicitHeight: osdItem.height + + // Set margins based on bar position + margins.top: { + switch (Settings.data.bar.position) { + case "top": + return (Style.barHeight + Style.marginS) * windowLoader.scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * windowLoader.scaling : 0) + default: + return Style.marginL * windowLoader.scaling + } + } + + color: Color.transparent + + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + exclusionMode: PanelWindow.ExclusionMode.Ignore + + Rectangle { + id: osdItem + + width: parent.width + height: Math.round(contentLayout.implicitHeight + Style.marginL * 2 * windowLoader.scaling) + radius: Style.radiusL * windowLoader.scaling + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(2, Style.borderM * windowLoader.scaling) + visible: false + opacity: 0 + scale: 0.7 + + anchors.horizontalCenter: parent.horizontalCenter + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Behavior on scale { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Timer { + id: hideTimer + interval: 2000 + onTriggered: osdItem.hide() + } + + RowLayout { + id: contentLayout + anchors.fill: parent + anchors.margins: Style.marginM * windowLoader.scaling + spacing: Style.marginM * windowLoader.scaling + + NIcon { + icon: windowLoader.getIcon() + color: windowLoader.isMuted ? Color.mError : Color.mOnSurface + font.pointSize: Style.fontSizeXL * windowLoader.scaling + Layout.alignment: Qt.AlignVCenter + } + + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: Style.marginXS * windowLoader.scaling + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: Math.round(6 * windowLoader.scaling) + radius: Math.round(3 * windowLoader.scaling) + color: Color.mSurfaceVariant + + Rectangle { + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + width: parent.width * (windowLoader.isMuted ? 0 : Math.min(1.0, windowLoader.currentVolume)) + radius: parent.radius + color: windowLoader.isMuted ? Color.mError : Color.mPrimary + + Behavior on width { + NumberAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + } + + NText { + text: windowLoader.isMuted ? "0%" : Math.round(windowLoader.currentVolume * 100) + "%" + color: Color.mOnSurfaceVariant + font.pointSize: Style.fontSizeS * windowLoader.scaling + Layout.alignment: Qt.AlignVCenter + Layout.minimumWidth: Math.round(32 * windowLoader.scaling) + } + } + } + + function show() { + hideTimer.stop() + osdItem.visible = true + osdItem.opacity = 1 + osdItem.scale = 1.0 + hideTimer.start() + } + + function hide() { + hideTimer.stop() + osdItem.opacity = 0 + osdItem.scale = 0.7 + + Qt.callLater(function () { + osdItem.visible = false + windowLoader.active = false + }) + } + } + + function showOSD() { + osdItem.show() + } + } + + // Monitor volume changes + Connections { + target: AudioService + + function onVolumeChanged() { + if (!firstVolumeReceived) { + // Ignore the first volume change on startup + firstVolumeReceived = true + } else { + showOSD() + } + } + + function onMutedChanged() { + if (!firstMuteReceived) { + // Ignore the first mute state change on startup + firstMuteReceived = true + } else { + showOSD() + } + } + } + + // Signal to coordinate with other OSDs + signal osdShowing + + function showOSD() { + // Check if OSD is enabled in settings + if (!Settings.data.general.showOSD) { + return + } + + osdShowing() // Notify other OSDs to hide + windowLoader.active = true + if (windowLoader.item) { + windowLoader.item.showOSD() + } + } + + function hideOSD() { + if (windowLoader.item) { + windowLoader.item.osdItem.hideImmediately() + } + } +} diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index 0e0d8932..add48277 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -76,6 +76,13 @@ ColumnLayout { onToggled: checked => Settings.data.general.dimDesktop = checked } + NToggle { + label: "Show volume and brightness OSD" + description: "Display on-screen notifications when adjusting volume or brightness." + checked: Settings.data.general.showOSD + onToggled: checked => Settings.data.general.showOSD = checked + } + ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true diff --git a/shell.qml b/shell.qml index e7f53d16..cdf774f2 100644 --- a/shell.qml +++ b/shell.qml @@ -36,6 +36,7 @@ import qs.Modules.Bar.WiFi import qs.Modules.ControlCenter import qs.Modules.Launcher import qs.Modules.Notification +import qs.Modules.OSD import qs.Modules.Settings import qs.Modules.Toast import qs.Modules.Wallpaper @@ -59,6 +60,19 @@ ShellRoot { ToastOverlay {} + // OSD overlays for volume and brightness + VolumeOSD { + id: volumeOSD + objectName: "volumeOSD" + onOsdShowing: brightnessOSD.hideOSD() + } + + BrightnessOSD { + id: brightnessOSD + objectName: "brightnessOSD" + onOsdShowing: volumeOSD.hideOSD() + } + // IPCService is treated as a service // but it's actually an Item that needs to exists in the shell. IPCService {} From c48e87e0123da35f21cd151ec195b1ba4d711d04 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Tue, 23 Sep 2025 17:53:40 +0200 Subject: [PATCH 48/54] Settings: update default settings --- 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 d040ed1b..4336292a 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -1,5 +1,5 @@ { - "settingsVersion": 4, + "settingsVersion": 5, "bar": { "position": "top", "backgroundOpacity": 1, diff --git a/Commons/Settings.qml b/Commons/Settings.qml index a49c029b..a154cc60 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -114,7 +114,7 @@ Singleton { JsonAdapter { id: adapter - property int settingsVersion: 4 + property int settingsVersion: 5 // bar property JsonObject bar: JsonObject { From 4cd53c4083a0789ce5c88264a4cf655e9c21f9d7 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Tue, 23 Sep 2025 18:07:14 +0200 Subject: [PATCH 49/54] OSD: unified Volume & Brightness OSD into one file (OSD.qml), move OSD settings to NotificationTab --- Modules/OSD/{BrightnessOSD.qml => OSD.qml} | 124 ++++++++++-- Modules/OSD/VolumeOSD.qml | 217 --------------------- Modules/Settings/Tabs/GeneralTab.qml | 7 - Modules/Settings/Tabs/NotificationsTab.qml | 7 + shell.qml | 6 +- 5 files changed, 120 insertions(+), 241 deletions(-) rename Modules/OSD/{BrightnessOSD.qml => OSD.qml} (62%) delete mode 100644 Modules/OSD/VolumeOSD.qml diff --git a/Modules/OSD/BrightnessOSD.qml b/Modules/OSD/OSD.qml similarity index 62% rename from Modules/OSD/BrightnessOSD.qml rename to Modules/OSD/OSD.qml index c2d5e3fb..0063d5d4 100644 --- a/Modules/OSD/BrightnessOSD.qml +++ b/Modules/OSD/OSD.qml @@ -11,20 +11,78 @@ Loader { id: windowLoader active: false + // OSD Type enum + enum Type { + Volume, + Brightness + } + + property int osdType: OSD.Type.Volume readonly property real scaling: ScalingService.getScreenScale(Quickshell.screens[0]) + + // Volume properties + readonly property real currentVolume: AudioService.volume + readonly property bool isMuted: AudioService.muted + property bool firstVolumeReceived: false + property bool firstMuteReceived: false + + // Brightness properties readonly property real currentBrightness: { if (BrightnessService.monitors.length > 0) { return BrightnessService.monitors[0].brightness || 0 } return 0 } - - // Used to avoid showing OSD on Quickshell startup property bool firstBrightnessReceived: false + // Get appropriate icon based on OSD type function getIcon() { - var brightness = currentBrightness - return brightness <= 0.5 ? "brightness-low" : "brightness-high" + if (osdType === OSD.Type.Volume) { + if (AudioService.muted) { + return "volume-mute" + } + return (AudioService.volume <= Number.EPSILON) ? "volume-zero" : (AudioService.volume <= 0.5) ? "volume-low" : "volume-high" + } else { + // Brightness + var brightness = currentBrightness + return brightness <= 0.5 ? "brightness-low" : "brightness-high" + } + } + + // Get current value (0-1 range) + function getCurrentValue() { + if (osdType === OSD.Type.Volume) { + return isMuted ? 0 : currentVolume + } else { + return currentBrightness + } + } + + // Get display percentage + function getDisplayPercentage() { + if (osdType === OSD.Type.Volume) { + return isMuted ? "0%" : Math.round(currentVolume * 100) + "%" + } else { + return Math.round(currentBrightness * 100) + "%" + } + } + + // Get progress bar color + function getProgressColor() { + if (osdType === OSD.Type.Volume) { + return isMuted ? Color.mError : Color.mPrimary + } else { + return Color.mPrimary + } + } + + // Get icon color + function getIconColor() { + if (osdType === OSD.Type.Volume) { + return isMuted ? Color.mError : Color.mOnSurface + } else { + return Color.mOnSurface + } } sourceComponent: PanelWindow { @@ -97,7 +155,7 @@ Loader { NIcon { icon: windowLoader.getIcon() - color: Color.mOnSurface + color: windowLoader.getIconColor() font.pointSize: Style.fontSizeXL * windowLoader.scaling Layout.alignment: Qt.AlignVCenter } @@ -117,9 +175,9 @@ Loader { anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom - width: parent.width * windowLoader.currentBrightness + width: parent.width * Math.min(1.0, windowLoader.getCurrentValue()) radius: parent.radius - color: Color.mPrimary + color: windowLoader.getProgressColor() Behavior on width { NumberAnimation { @@ -127,11 +185,17 @@ Loader { easing.type: Easing.OutCubic } } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } } } NText { - text: Math.round(windowLoader.currentBrightness * 100) + "%" + text: windowLoader.getDisplayPercentage() color: Color.mOnSurfaceVariant font.pointSize: Style.fontSizeS * windowLoader.scaling Layout.alignment: Qt.AlignVCenter @@ -158,6 +222,14 @@ Loader { windowLoader.active = false }) } + + function hideImmediately() { + hideTimer.stop() + osdItem.opacity = 0 + osdItem.scale = 0.7 + osdItem.visible = false + windowLoader.active = false + } } function showOSD() { @@ -165,12 +237,34 @@ Loader { } } - // Monitor brightness changes from all monitors + // Volume change monitoring + Connections { + target: AudioService + enabled: osdType === OSD.Type.Volume + + function onVolumeChanged() { + if (!firstVolumeReceived) { + firstVolumeReceived = true + } else { + showOSD() + } + } + + function onMutedChanged() { + if (!firstMuteReceived) { + firstMuteReceived = true + } else { + showOSD() + } + } + } + + // Brightness change monitoring Connections { target: BrightnessService + enabled: osdType === OSD.Type.Brightness function onMonitorsChanged() { - // Connect to brightness changes for each monitor for (var i = 0; i < BrightnessService.monitors.length; i++) { let monitor = BrightnessService.monitors[i] monitor.brightnessUpdated.connect(windowLoader.onBrightnessChanged) @@ -178,17 +272,17 @@ Loader { } } - // Connect to existing monitors on component completion Component.onCompleted: { - for (var i = 0; i < BrightnessService.monitors.length; i++) { - let monitor = BrightnessService.monitors[i] - monitor.brightnessUpdated.connect(windowLoader.onBrightnessChanged) + if (osdType === OSD.Type.Brightness) { + for (var i = 0; i < BrightnessService.monitors.length; i++) { + let monitor = BrightnessService.monitors[i] + monitor.brightnessUpdated.connect(windowLoader.onBrightnessChanged) + } } } function onBrightnessChanged(newBrightness) { if (!firstBrightnessReceived) { - // Ignore the first brightness change on startup firstBrightnessReceived = true } else { showOSD() diff --git a/Modules/OSD/VolumeOSD.qml b/Modules/OSD/VolumeOSD.qml deleted file mode 100644 index 1c28291c..00000000 --- a/Modules/OSD/VolumeOSD.qml +++ /dev/null @@ -1,217 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell -import Quickshell.Wayland -import qs.Commons -import qs.Services -import qs.Widgets - -Loader { - id: windowLoader - active: false - - readonly property real currentVolume: AudioService.volume - readonly property bool isMuted: AudioService.muted - readonly property real scaling: ScalingService.getScreenScale(Quickshell.screens[0]) - - // Used to avoid showing OSD on Quickshell startup - property bool firstVolumeReceived: false - property bool firstMuteReceived: false - - function getIcon() { - if (AudioService.muted) { - return "volume-mute" - } - return (AudioService.volume <= Number.EPSILON) ? "volume-zero" : (AudioService.volume <= 0.5) ? "volume-low" : "volume-high" - } - - sourceComponent: PanelWindow { - id: panel - - screen: Quickshell.screens[0] // Use primary screen - - anchors { - top: true - } - - implicitWidth: 320 * windowLoader.scaling - implicitHeight: osdItem.height - - // Set margins based on bar position - margins.top: { - switch (Settings.data.bar.position) { - case "top": - return (Style.barHeight + Style.marginS) * windowLoader.scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * windowLoader.scaling : 0) - default: - return Style.marginL * windowLoader.scaling - } - } - - color: Color.transparent - - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - exclusionMode: PanelWindow.ExclusionMode.Ignore - - Rectangle { - id: osdItem - - width: parent.width - height: Math.round(contentLayout.implicitHeight + Style.marginL * 2 * windowLoader.scaling) - radius: Style.radiusL * windowLoader.scaling - color: Color.mSurface - border.color: Color.mOutline - border.width: Math.max(2, Style.borderM * windowLoader.scaling) - visible: false - opacity: 0 - scale: 0.7 - - anchors.horizontalCenter: parent.horizontalCenter - - Behavior on opacity { - NumberAnimation { - duration: Style.animationFast - easing.type: Easing.OutCubic - } - } - - Behavior on scale { - NumberAnimation { - duration: Style.animationFast - easing.type: Easing.OutCubic - } - } - - Timer { - id: hideTimer - interval: 2000 - onTriggered: osdItem.hide() - } - - RowLayout { - id: contentLayout - anchors.fill: parent - anchors.margins: Style.marginM * windowLoader.scaling - spacing: Style.marginM * windowLoader.scaling - - NIcon { - icon: windowLoader.getIcon() - color: windowLoader.isMuted ? Color.mError : Color.mOnSurface - font.pointSize: Style.fontSizeXL * windowLoader.scaling - Layout.alignment: Qt.AlignVCenter - } - - RowLayout { - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - spacing: Style.marginXS * windowLoader.scaling - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: Math.round(6 * windowLoader.scaling) - radius: Math.round(3 * windowLoader.scaling) - color: Color.mSurfaceVariant - - Rectangle { - anchors.left: parent.left - anchors.top: parent.top - anchors.bottom: parent.bottom - width: parent.width * (windowLoader.isMuted ? 0 : Math.min(1.0, windowLoader.currentVolume)) - radius: parent.radius - color: windowLoader.isMuted ? Color.mError : Color.mPrimary - - Behavior on width { - NumberAnimation { - duration: Style.animationFast - easing.type: Easing.OutCubic - } - } - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - } - } - - NText { - text: windowLoader.isMuted ? "0%" : Math.round(windowLoader.currentVolume * 100) + "%" - color: Color.mOnSurfaceVariant - font.pointSize: Style.fontSizeS * windowLoader.scaling - Layout.alignment: Qt.AlignVCenter - Layout.minimumWidth: Math.round(32 * windowLoader.scaling) - } - } - } - - function show() { - hideTimer.stop() - osdItem.visible = true - osdItem.opacity = 1 - osdItem.scale = 1.0 - hideTimer.start() - } - - function hide() { - hideTimer.stop() - osdItem.opacity = 0 - osdItem.scale = 0.7 - - Qt.callLater(function () { - osdItem.visible = false - windowLoader.active = false - }) - } - } - - function showOSD() { - osdItem.show() - } - } - - // Monitor volume changes - Connections { - target: AudioService - - function onVolumeChanged() { - if (!firstVolumeReceived) { - // Ignore the first volume change on startup - firstVolumeReceived = true - } else { - showOSD() - } - } - - function onMutedChanged() { - if (!firstMuteReceived) { - // Ignore the first mute state change on startup - firstMuteReceived = true - } else { - showOSD() - } - } - } - - // Signal to coordinate with other OSDs - signal osdShowing - - function showOSD() { - // Check if OSD is enabled in settings - if (!Settings.data.general.showOSD) { - return - } - - osdShowing() // Notify other OSDs to hide - windowLoader.active = true - if (windowLoader.item) { - windowLoader.item.showOSD() - } - } - - function hideOSD() { - if (windowLoader.item) { - windowLoader.item.osdItem.hideImmediately() - } - } -} diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index add48277..0e0d8932 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -76,13 +76,6 @@ ColumnLayout { onToggled: checked => Settings.data.general.dimDesktop = checked } - NToggle { - label: "Show volume and brightness OSD" - description: "Display on-screen notifications when adjusting volume or brightness." - checked: Settings.data.general.showOSD - onToggled: checked => Settings.data.general.showOSD = checked - } - ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true diff --git a/Modules/Settings/Tabs/NotificationsTab.qml b/Modules/Settings/Tabs/NotificationsTab.qml index 64093d49..be404a08 100644 --- a/Modules/Settings/Tabs/NotificationsTab.qml +++ b/Modules/Settings/Tabs/NotificationsTab.qml @@ -39,6 +39,13 @@ ColumnLayout { onToggled: checked => Settings.data.notifications.doNotDisturb = checked } + NToggle { + label: "Enable on screen display" + description: "Show volume and brightness changes in real-time." + checked: Settings.data.general.showOSD + onToggled: checked => Settings.data.general.showOSD = checked + } + NComboBox { label: "Location" description: "Where notifications appear on screen." diff --git a/shell.qml b/shell.qml index cdf774f2..b7418b24 100644 --- a/shell.qml +++ b/shell.qml @@ -61,15 +61,17 @@ ShellRoot { ToastOverlay {} // OSD overlays for volume and brightness - VolumeOSD { + OSD { id: volumeOSD objectName: "volumeOSD" + osdType: OSD.Type.Volume onOsdShowing: brightnessOSD.hideOSD() } - BrightnessOSD { + OSD { id: brightnessOSD objectName: "brightnessOSD" + osdType: OSD.Type.Brightness onOsdShowing: volumeOSD.hideOSD() } From 6d4ca4ffc0a195430ac793cce1eb1e9bafa7a727 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Tue, 23 Sep 2025 12:40:40 -0400 Subject: [PATCH 50/54] OSD: moved settings in the appropriate spot --- Assets/settings-default.json | 6 +++--- Commons/Settings.qml | 4 ++-- Modules/OSD/OSD.qml | 2 +- Modules/Settings/Tabs/NotificationsTab.qml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 4336292a..7a34f42d 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -70,8 +70,7 @@ "forceBlackScreenCorners": false, "radiusRatio": 1, "screenRadiusRatio": 1, - "animationSpeed": 1, - "showOSD": true + "animationSpeed": 1 }, "location": { "name": "Tokyo", @@ -131,7 +130,8 @@ "lastSeenTs": 0, "lowUrgencyDuration": 3, "normalUrgencyDuration": 8, - "criticalUrgencyDuration": 15 + "criticalUrgencyDuration": 15, + "enableOSD": true }, "audio": { "volumeStep": 5, diff --git a/Commons/Settings.qml b/Commons/Settings.qml index a154cc60..45b3018c 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -54,7 +54,7 @@ Singleton { // Then it should be commented out again, regular users don't need to generate // default settings on every start // TODO: automate this someday! - //generateDefaultSettings() + // generateDefaultSettings() // Patch-in the local default, resolved to user's home adapter.general.avatarImage = defaultAvatar @@ -177,7 +177,6 @@ Singleton { property real radiusRatio: 1.0 property real screenRadiusRatio: 1.0 property real animationSpeed: 1.0 - property bool showOSD: true } // location @@ -254,6 +253,7 @@ Singleton { property int lowUrgencyDuration: 3 property int normalUrgencyDuration: 8 property int criticalUrgencyDuration: 15 + property bool enableOSD: true } // audio diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 0063d5d4..6adf5eab 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -294,7 +294,7 @@ Loader { function showOSD() { // Check if OSD is enabled in settings - if (!Settings.data.general.showOSD) { + if (!Settings.data.notifications.enableOSD) { return } diff --git a/Modules/Settings/Tabs/NotificationsTab.qml b/Modules/Settings/Tabs/NotificationsTab.qml index be404a08..3b1b7fdb 100644 --- a/Modules/Settings/Tabs/NotificationsTab.qml +++ b/Modules/Settings/Tabs/NotificationsTab.qml @@ -42,8 +42,8 @@ ColumnLayout { NToggle { label: "Enable on screen display" description: "Show volume and brightness changes in real-time." - checked: Settings.data.general.showOSD - onToggled: checked => Settings.data.general.showOSD = checked + checked: Settings.data.notifications.enableOSD + onToggled: checked => Settings.data.notifications.enableOSD = checked } NComboBox { From 0b5ef30b347c80d3a9321fc445e77fef3e105668 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Tue, 23 Sep 2025 18:42:05 +0200 Subject: [PATCH 51/54] OSD: fix race condition --- Modules/OSD/OSD.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/OSD/OSD.qml b/Modules/OSD/OSD.qml index 0063d5d4..b10e4a5f 100644 --- a/Modules/OSD/OSD.qml +++ b/Modules/OSD/OSD.qml @@ -306,8 +306,11 @@ Loader { } function hideOSD() { - if (windowLoader.item) { + if (windowLoader.item && windowLoader.item.osdItem) { windowLoader.item.osdItem.hideImmediately() + } else if (windowLoader.active) { + // If window exists but osdItem isn't ready, just deactivate the loader + windowLoader.active = false } } } From e9efab0d59abf25c0bcc39b90211ae684d32f8d0 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Tue, 23 Sep 2025 14:23:41 -0400 Subject: [PATCH 52/54] Cava: also enable during lockscreen --- Services/CavaService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/CavaService.qml b/Services/CavaService.qml index 1c03f6f1..07b8de7f 100644 --- a/Services/CavaService.qml +++ b/Services/CavaService.qml @@ -8,7 +8,7 @@ import qs.Commons Singleton { id: root - property bool shouldRun: BarService.hasAudioVisualizer || (PanelService.getPanel("controlCenterPanel") === PanelService.openedPanel) + property bool shouldRun: BarService.hasAudioVisualizer || (PanelService.getPanel("controlCenterPanel") === PanelService.openedPanel) || PanelService.lockScreen.active property var values: Array(barsCount).fill(0) property int barsCount: 24 property var config: ({ From a2b57c516513cd6e9c34d3247308805de4063062 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Tue, 23 Sep 2025 14:42:55 -0400 Subject: [PATCH 53/54] Panels: more reliable draggable toggling --- Modules/Settings/SettingsPanel.qml | 2 +- Modules/Wallpaper/WallpaperPanel.qml | 2 +- Services/PanelService.qml | 31 +++++++++++++++++----------- Widgets/NFilePicker.qml | 8 +++---- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Modules/Settings/SettingsPanel.qml b/Modules/Settings/SettingsPanel.qml index c60762d8..418f4223 100644 --- a/Modules/Settings/SettingsPanel.qml +++ b/Modules/Settings/SettingsPanel.qml @@ -21,7 +21,7 @@ NPanel { panelKeyboardFocus: true - draggable: (PanelService.openedPopup === null) + draggable: !PanelService.hasOpenedPopup // Tabs enumeration, order is NOT relevant enum Tab { diff --git a/Modules/Wallpaper/WallpaperPanel.qml b/Modules/Wallpaper/WallpaperPanel.qml index 4828df14..7c0bad58 100644 --- a/Modules/Wallpaper/WallpaperPanel.qml +++ b/Modules/Wallpaper/WallpaperPanel.qml @@ -19,7 +19,7 @@ NPanel { panelAnchorVerticalCenter: true panelKeyboardFocus: true - draggable: (PanelService.openedPopup === null) + draggable: !PanelService.hasOpenedPopup panelContent: Rectangle { id: wallpaperPanel diff --git a/Services/PanelService.qml b/Services/PanelService.qml index caefab3b..5d33aeb7 100644 --- a/Services/PanelService.qml +++ b/Services/PanelService.qml @@ -10,18 +10,19 @@ Singleton { // This is not a panel... property var lockScreen: null - // Currently opened panel - property var openedPanel: null - readonly property bool hasOpenedPanel: (openedPanel !== null) - - // Currently opened popup - property var openedPopup: null - + // Panels property var registeredPanels: ({}) - + property var openedPanel: null + property bool hasOpenedPanel: false signal willOpen signal willClose + // Currently opened popups, can have more than one. + // ex: when opening an NIconPicker from a widget setting. + property var openedPopups: [] + property bool hasOpenedPopup: false + signal popupChanged + // Register this panel function registerPanel(panel) { registeredPanels[panel.objectName] = panel @@ -44,12 +45,15 @@ Singleton { openedPanel.close() } openedPanel = panel + hasOpenedPanel = true // emit signal willOpen() } function willClosePanel(panel) { + hasOpenedPanel = false + // emit signal willClose() } @@ -62,11 +66,14 @@ Singleton { // Popups function willOpenPopup(popup) { - openedPopup = popup + openedPopups.push(popup) + hasOpenedPopup = (openedPopups.length !== 0) + popupChanged() } + function willClosePopup(popup) { - if (openedPopup && openedPopup === popup) { - openedPopup = null - } + openedPopups = openedPopups.filter(p => p !== popup) + hasOpenedPopup = (openedPopups.length !== 0) + popupChanged() } } diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index 2f701b21..f5fb2336 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -37,8 +37,8 @@ Item { PanelService.openedPanel.isMasked = true } - if (PanelService.openedPopup !== null) { - PanelService.openedPopup.isMasked = true + for (var i = 0; i < PanelService.openedPopups.length; i++) { + PanelService.openedPopups[i].isMasked = true } isOpen = true @@ -70,8 +70,8 @@ Item { PanelService.openedPanel.isMasked = false } - if (PanelService.openedPopup !== null) { - PanelService.openedPopup.isMasked = false + for (var i = 0; i < PanelService.openedPopups.length; i++) { + PanelService.openedPopups[i].isMasked = false } afterClose() From 9a9d68c78d31e18ca0c529b99697195a91884678 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Tue, 23 Sep 2025 15:32:24 -0400 Subject: [PATCH 54/54] NButton: Simplified by removing the press state which was causing issues with Popups opening hover the button --- Widgets/NButton.qml | 34 +++++++++++----------------------- Widgets/NInputAction.qml | 1 - 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/Widgets/NButton.qml b/Widgets/NButton.qml index 7dc8841c..e80b6b31 100644 --- a/Widgets/NButton.qml +++ b/Widgets/NButton.qml @@ -13,7 +13,6 @@ Rectangle { property color backgroundColor: Color.mPrimary property color textColor: Color.mOnPrimary property color hoverColor: Color.mTertiary - property color pressColor: Color.mSecondary property bool enabled: true property real fontSize: Style.fontSizeM * scaling property int fontWeight: Style.fontWeightBold @@ -38,8 +37,6 @@ Rectangle { color: { if (!enabled) return outlined ? Color.transparent : Qt.lighter(Color.mSurfaceVariant, 1.2) - if (pressed) - return pressColor if (hovered) return hoverColor return outlined ? Color.transparent : backgroundColor @@ -113,7 +110,7 @@ Rectangle { if (!root.enabled) return Color.mOnSurfaceVariant if (root.outlined) { - if (root.pressed || root.hovered) + if (root.hovered) return root.textColor return root.backgroundColor } @@ -153,33 +150,24 @@ Rectangle { } onExited: { root.hovered = false - root.pressed = false if (tooltipText) { tooltip.hide() } } onPressed: mouse => { - root.pressed = true + if (tooltipText) { + tooltip.hide() + } + if (mouse.button === Qt.LeftButton) { + root.clicked() + } else if (mouse.button == Qt.RightButton) { + root.rightClicked() + } else if (mouse.button == Qt.MiddleButton) { + root.middleClicked() + } } - onReleased: mouse => { - root.pressed = false - if (tooltipText) { - tooltip.hide() - } - if (!root.hovered) { - return - } - 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.pressed = false root.hovered = false if (tooltipText) { tooltip.hide() diff --git a/Widgets/NInputAction.qml b/Widgets/NInputAction.qml index 5b3706eb..abe03961 100644 --- a/Widgets/NInputAction.qml +++ b/Widgets/NInputAction.qml @@ -47,7 +47,6 @@ RowLayout { backgroundColor: Color.mSecondary textColor: Color.mOnSecondary hoverColor: Color.mTertiary - pressColor: Color.mPrimary enabled: root.actionButtonEnabled onClicked: {