From 7f34ca41224ea53edd71dc72697c6fb594cc26ff Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 3 Sep 2025 19:09:36 -0400 Subject: [PATCH 01/14] Custom buttons: WIP implementing custom properties --- Commons/Settings.qml | 63 +++++++++++++++++++++++++-- Modules/Bar/Bar.qml | 7 ++- Modules/Bar/Widgets/CustomButton.qml | 26 +++++++++++ Modules/SettingsPanel/Tabs/BarTab.qml | 14 +++--- Services/BarWidgetRegistry.qml | 28 +++++++++--- Widgets/NSectionEditor.qml | 35 +++++++++++---- Widgets/NWidgetLoader.qml | 14 +++--- 7 files changed, 154 insertions(+), 33 deletions(-) create mode 100644 Modules/Bar/Widgets/CustomButton.qml diff --git a/Commons/Settings.qml b/Commons/Settings.qml index feebe5d..c01c5fe 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -100,6 +100,31 @@ Singleton { Logger.log("Settings", "Settings loaded successfully") isLoaded = true + for (var i = 0; i < adapter.bar.widgets.left.length; i++) { + var obj = adapter.bar.widgets.left[i] + if (typeof obj === "string") { + adapter.bar.widgets.left[i] = { + "id": obj + } + } + } + for (var i = 0; i < adapter.bar.widgets.center.length; i++) { + var obj = adapter.bar.widgets.center[i] + if (typeof obj === "string") { + adapter.bar.widgets.center[i] = { + "id": obj + } + } + } + for (var i = 0; i < adapter.bar.widgets.right.length; i++) { + var obj = adapter.bar.widgets.right[i] + if (typeof obj === "string") { + adapter.bar.widgets.right[i] = { + "id": obj + } + } + } + // Emit the signal root.settingsLoaded() @@ -126,6 +151,8 @@ Singleton { JsonAdapter { id: adapter + property int settingsVersion: 1 + // bar property JsonObject bar: JsonObject { property string position: "top" // Possible values: "top", "bottom" @@ -140,9 +167,39 @@ Singleton { // Widget configuration for modular bar system property JsonObject widgets widgets: JsonObject { - property list left: ["SystemMonitor", "ActiveWindow", "MediaMini"] - property list center: ["Workspace"] - property list right: ["ScreenRecorderIndicator", "Tray", "NotificationHistory", "WiFi", "Bluetooth", "Battery", "Volume", "Brightness", "NightLight", "Clock", "SidePanelToggle"] + property list left: [{ + "id": "SystemMonitor" + }, { + "id": "ActiveWindow" + }, { + "id": "MediaMini" + }] + property list center: [{ + "id": "Workspace" + }] + property list right: [{ + "id": "ScreenRecorderIndicator" + }, { + "id": "Tray" + }, { + "id": "NotificationHistory" + }, { + "id": "WiFi" + }, { + "id": "Bluetooth" + }, { + "id": "Battery" + }, { + "id": "Volume" + }, { + "id": "Brightness" + }, { + "id": "NightLight" + }, { + "id": "Clock" + }, { + "id": "SidePanelToggle" + }] } } diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 59e6655..2c8e6f7 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -73,7 +73,7 @@ Variants { Repeater { model: Settings.data.bar.widgets.left delegate: NWidgetLoader { - widgetName: modelData + widgetId: modelData.id widgetProps: { "screen": root.modelData || null, "scaling": ScalingService.getScreenScale(screen), @@ -100,8 +100,7 @@ Variants { Repeater { model: Settings.data.bar.widgets.center delegate: NWidgetLoader { - - widgetName: modelData + widgetId: modelData.id widgetProps: { "screen": root.modelData || null, "scaling": ScalingService.getScreenScale(screen), @@ -129,7 +128,7 @@ Variants { Repeater { model: Settings.data.bar.widgets.right delegate: NWidgetLoader { - widgetName: modelData + widgetId: modelData.id widgetProps: { "screen": root.modelData || null, "scaling": ScalingService.getScreenScale(screen), diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml new file mode 100644 index 0000000..0467f73 --- /dev/null +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -0,0 +1,26 @@ +import Quickshell +import qs.Commons +import qs.Widgets +import qs.Services + +NIconButton { + id: root + + property ShellScreen screen + property real scaling: 1.0 + property bool allowUserSettings: true + + icon: "favorite" + tooltipText: "Hello world" + sizeRatio: 0.8 + + colorBg: Color.mSurfaceVariant + colorFg: Color.mOnSurface + colorBorder: Color.transparent + colorBorderHover: Color.transparent + + anchors.verticalCenter: parent.verticalCenter + onClicked: { + + } +} diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index e927456..697a0b7 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -162,7 +162,7 @@ ColumnLayout { sectionId: "left" widgetModel: Settings.data.bar.widgets.left availableWidgets: availableWidgets - onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section) + onAddWidget: (widgetId, section) => addWidgetToSection(widgetId, section) onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) } @@ -173,7 +173,7 @@ ColumnLayout { sectionId: "center" widgetModel: Settings.data.bar.widgets.center availableWidgets: availableWidgets - onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section) + onAddWidget: (widgetId, section) => addWidgetToSection(widgetId, section) onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) } @@ -184,7 +184,7 @@ ColumnLayout { sectionId: "right" widgetModel: Settings.data.bar.widgets.right availableWidgets: availableWidgets - onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section) + onAddWidget: (widgetId, section) => addWidgetToSection(widgetId, section) onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) } @@ -198,14 +198,16 @@ ColumnLayout { } // Helper functions - function addWidgetToSection(widgetName, section) { - //Logger.log("BarTab", "Adding widget", widgetName, "to section", section) + function addWidgetToSection(widgetId, section) { + //Logger.log("BarTab", "Adding widget", widgetId, "to section", section) var sectionArray = Settings.data.bar.widgets[section] if (sectionArray) { // Create a new array to avoid modifying the original var newArray = sectionArray.slice() - newArray.push(widgetName) + newArray.push({ + "id": widgetId + }) //Logger.log("BarTab", "Widget added. New array:", JSON.stringify(newArray)) // Assign the new array diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index fc7016a..f99ebe6 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -16,6 +16,7 @@ Singleton { "Bluetooth": bluetoothComponent, "Brightness": brightnessComponent, "Clock": clockComponent, + "CustomButton": customButtonComponent, "DarkModeToggle": darkModeToggle, "KeyboardLayout": keyboardLayoutComponent, "MediaMini": mediaMiniComponent, @@ -33,6 +34,15 @@ Singleton { "Workspace": workspaceComponent }) + property var widgetMetadata: ({ + "CustomButton": { + allowUserSettings: true, + icon: "favorite", + execute: "" + }, + }) + + // Component definitions - these are loaded once at startup property Component activeWindowComponent: Component { ActiveWindow {} @@ -52,6 +62,9 @@ Singleton { property Component clockComponent: Component { Clock {} } + property Component customButtonComponent: Component { + CustomButton {} + } property Component darkModeToggle: Component { DarkModeToggle {} } @@ -100,20 +113,25 @@ Singleton { // ------------------------------ // Helper function to get widget component by name - function getWidget(name) { - return widgets[name] || null + function getWidget(id) { + return widgets[id] || null } // Helper function to check if widget exists - function hasWidget(name) { - return name in widgets + function hasWidget(id) { + return id in widgets } - // Get list of available widget names + // Get list of available widget id function getAvailableWidgets() { return Object.keys(widgets) } + // Helper function to check if widget has user settings + function widgetHasUserSettings(id) { + return (widgetMetadata[id] !== undefined) && (widgetMetadata[id].allowUserSettings === true) + } + function getNPillDirection(widget) { try { if (widget.barSection === "leftSection") { diff --git a/Widgets/NSectionEditor.qml b/Widgets/NSectionEditor.qml index a0a38a9..6813b28 100644 --- a/Widgets/NSectionEditor.qml +++ b/Widgets/NSectionEditor.qml @@ -3,6 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import qs.Commons import qs.Widgets +import qs.Services NBox { id: root @@ -12,7 +13,7 @@ NBox { property var widgetModel: [] property var availableWidgets: [] - signal addWidget(string widgetName, string section) + signal addWidget(string widgetId, string section) signal removeWidget(string section, int index) signal reorderWidget(string section, int fromIndex, int toIndex) @@ -32,8 +33,8 @@ NBox { } // Generate widget color from name checksum - function getWidgetColor(name) { - const totalSum = name.split('').reduce((acc, character) => { + function getWidgetColor(widgetId) { + const totalSum = widgetId.split('').reduce((acc, character) => { return acc + character.charCodeAt(0) }, 0) switch (totalSum % 5) { @@ -110,17 +111,18 @@ NBox { spacing: Style.marginS * scaling flow: Flow.LeftToRight + Repeater { model: widgetModel delegate: Rectangle { id: widgetItem required property int index - required property string modelData + required property var modelData width: widgetContent.implicitWidth + Style.marginL * scaling height: 40 * scaling radius: Style.radiusL * scaling - color: root.getWidgetColor(modelData) + color: root.getWidgetColor(modelData.id) border.color: Color.mOutline border.width: Math.max(1, Style.borderS * scaling) @@ -151,7 +153,7 @@ NBox { spacing: Style.marginXS * scaling NText { - text: modelData + text: modelData.id font.pointSize: Style.fontSizeS * scaling color: Color.mOnPrimary horizontalAlignment: Text.AlignHCenter @@ -159,6 +161,23 @@ NBox { Layout.preferredWidth: 80 * scaling } + Loader { + active: BarWidgetRegistry.widgetHasUserSettings(modelData.id) + sourceComponent: NIconButton { + icon: "settings" + sizeRatio: 0.6 + colorBorder: Color.applyOpacity(Color.mOutline, "40") + colorBg: Color.mOnSurface + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + onClicked: { + // TODO open settings + } + } + } + + NIconButton { icon: "close" sizeRatio: 0.6 @@ -193,13 +212,13 @@ NBox { return } - //Logger.log("NSectionEditor", `Started dragging widget: ${modelData} at index ${index}`) + //Logger.log("NSectionEditor", `Started dragging widget: ${modelData.id} at index ${index}`) // Bring to front when starting drag widgetItem.z = 1000 } onReleased: { - //Logger.log("NSectionEditor", `Released widget: ${modelData} at index ${index}`) + //Logger.log("NSectionEditor", `Released widget: ${modelData.id} at index ${index}`) // Reset z-index when drag ends widgetItem.z = 0 diff --git a/Widgets/NWidgetLoader.qml b/Widgets/NWidgetLoader.qml index 1f4046c..17bf887 100644 --- a/Widgets/NWidgetLoader.qml +++ b/Widgets/NWidgetLoader.qml @@ -6,7 +6,7 @@ import qs.Commons Item { id: root - property string widgetName: "" + property string widgetId: "" property var widgetProps: ({}) property bool enabled: true @@ -27,12 +27,12 @@ Item { id: loader anchors.fill: parent - active: Settings.isLoaded && enabled && widgetName !== "" + active: Settings.isLoaded && enabled && widgetId !== "" sourceComponent: { if (!active) { return null } - return BarWidgetRegistry.getWidget(widgetName) + return BarWidgetRegistry.getWidget(widgetId) } onLoaded: { @@ -49,14 +49,14 @@ Item { item.onLoaded() } - //Logger.log("NWidgetLoader", "Loaded", widgetName, "on screen", item.screen.name) + //Logger.log("NWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name) } } // Error handling - onWidgetNameChanged: { - if (widgetName && !BarWidgetRegistry.hasWidget(widgetName)) { - Logger.warn("WidgetLoader", "Widget not found in registry:", widgetName) + onWidgetIdChanged: { + if (widgetId && !BarWidgetRegistry.hasWidget(widgetId)) { + Logger.warn("WidgetLoader", "Widget not found in registry:", widgetId) } } } From 598bc48957737083ec1af8116ff28c8f4381f26c Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 3 Sep 2025 20:51:51 -0400 Subject: [PATCH 02/14] Custom buttons: improved UI, still wip --- Modules/Bar/Widgets/CustomButton.qml | 69 +++++-- Modules/SettingsPanel/Tabs/BarTab.qml | 9 + Widgets/NSectionEditor.qml | 249 +++++++++++++++++++++++--- 3 files changed, 279 insertions(+), 48 deletions(-) diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index 0467f73..42fa154 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -1,26 +1,57 @@ -import Quickshell +import QtQuick +import QtQuick.Layouts import qs.Commons -import qs.Widgets import qs.Services +import qs.Widgets NIconButton { id: root - - property ShellScreen screen + + // Widget properties passed from Bar.qml + property var screen property real scaling: 1.0 - property bool allowUserSettings: true - - icon: "favorite" - tooltipText: "Hello world" - sizeRatio: 0.8 - - colorBg: Color.mSurfaceVariant - colorFg: Color.mOnSurface - colorBorder: Color.transparent - colorBorderHover: Color.transparent - - anchors.verticalCenter: parent.verticalCenter - onClicked: { - + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + // Get user settings from Settings data + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} } -} + + // Use settings or defaults from BarWidgetRegistry + property string userIcon: widgetSettings.icon || BarWidgetRegistry.widgetMetadata["CustomButton"].icon + property string userExecute: widgetSettings.execute || BarWidgetRegistry.widgetMetadata["CustomButton"].execute + + icon: userIcon + tooltipText: userExecute ? `Execute: ${userExecute}` : "Custom Button - Configure in settings" + + colorBg: Color.transparent + colorFg: Color.mOnSurface + colorBgHover: Color.applyOpacity(Color.mPrimary, "20") + colorFgHover: Color.mPrimary + + onClicked: { + if (userExecute) { + // Execute the user's command + Quickshell.execDetached(userExecute.split(" ")) + Logger.log("CustomButton", `Executing command: ${userExecute}`) + } else { + Logger.warn("CustomButton", "No command configured for this button") + } + } + + // Visual feedback when no command is set + opacity: userExecute ? 1.0 : 0.6 + + Component.onCompleted: { + Logger.log("CustomButton", `Initialized with icon: ${userIcon}, command: ${userExecute}`) + } +} \ No newline at end of file diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index 697a0b7..6a76022 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -165,6 +165,7 @@ ColumnLayout { onAddWidget: (widgetId, section) => addWidgetToSection(widgetId, section) onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => updateWidgetSettingsInSection(section, index, settings) } // Center Section @@ -176,6 +177,7 @@ ColumnLayout { onAddWidget: (widgetId, section) => addWidgetToSection(widgetId, section) onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => updateWidgetSettingsInSection(section, index, settings) } // Right Section @@ -187,6 +189,7 @@ ColumnLayout { onAddWidget: (widgetId, section) => addWidgetToSection(widgetId, section) onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => updateWidgetSettingsInSection(section, index, settings) } } } @@ -197,6 +200,12 @@ ColumnLayout { Layout.bottomMargin: Style.marginXL * scaling } + function updateWidgetSettingsInSection(section, index, settings) { + // Update the widget settings in the Settings data + Settings.data.bar.widgets[section][index] = settings + Logger.log("BarTab", `Updated widget settings for ${settings.id} in ${section} section`) + } + // Helper functions function addWidgetToSection(widgetId, section) { //Logger.log("BarTab", "Adding widget", widgetId, "to section", section) diff --git a/Widgets/NSectionEditor.qml b/Widgets/NSectionEditor.qml index 6813b28..db4cdb6 100644 --- a/Widgets/NSectionEditor.qml +++ b/Widgets/NSectionEditor.qml @@ -1,5 +1,6 @@ import QtQuick import QtQuick.Controls +import QtQuick.Effects import QtQuick.Layouts import qs.Commons import qs.Widgets @@ -16,6 +17,7 @@ NBox { signal addWidget(string widgetId, string section) signal removeWidget(string section, int index) signal reorderWidget(string section, int fromIndex, int toIndex) + signal updateWidgetSettings(string section, int index, var settings) color: Color.mSurface Layout.fillWidth: true @@ -33,21 +35,209 @@ NBox { } // Generate widget color from name checksum - function getWidgetColor(widgetId) { - const totalSum = widgetId.split('').reduce((acc, character) => { + function getWidgetColor(widget) { + const totalSum = JSON.stringify(widget).split('').reduce((acc, character) => { return acc + character.charCodeAt(0) }, 0) - switch (totalSum % 5) { - case 0: - return Color.mPrimary - case 1: - return Color.mSecondary - case 2: - return Color.mTertiary - case 3: - return Color.mError - case 4: - return Color.mOnSurface + switch (totalSum % 10) { + case 0: + return Color.mPrimary + case 1: + return Color.mSecondary + case 2: + return Color.mTertiary + case 3: + return Color.mError + case 4: + return Color.mOnSurface + case 5: + return Qt.darker(Color.mPrimary, 1.3) + case 6: + return Qt.darker(Color.mSecondary, 1.3) + case 7: + return Qt.darker(Color.mTertiary, 1.3) + case 8: + return Qt.darker(Color.mError, 1.3) + case 9: + return Qt.darker(Color.mOnSurface, 1.3) + } + } + + // Widget Settings Dialog Component + Component { + id: widgetSettingsDialog + + Popup { + id: settingsPopup + + property int widgetIndex: -1 + property var widgetData: null + property string widgetId: "" + + // Center popup in parent + x: (parent.width - width) * 0.5 + y: (parent.height - height) * 0.5 + + width: 400 * scaling + height: content.implicitHeight + padding * 2 + padding: Style.marginL * scaling + modal: true + + background: Rectangle { + id: bgRect + color: Color.mSurface + radius: Style.radiusL * scaling + border.color: Color.mPrimary + border.width: Style.borderM * scaling + } + + + + ColumnLayout { + id: content + width: parent.width + spacing: Style.marginM * scaling + + // Title + RowLayout { + Layout.fillWidth: true + + NText { + text: "Widget Settings: " + settingsPopup.widgetId + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + colorBg: Color.transparent + colorFg: Color.mOnSurface + colorBgHover: Color.applyOpacity(Color.mError, "20") + colorFgHover: Color.mError + onClicked: settingsPopup.close() + } + } + + // Separator + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + } + + // Settings based on widget type + Loader { + id: settingsLoader + Layout.fillWidth: true + sourceComponent: { + if (settingsPopup.widgetId === "CustomButton") { + return customButtonSettings + } + // Add more widget settings components here as needed + return null + } + } + + // Action buttons + RowLayout { + Layout.fillWidth: true + Layout.topMargin: Style.marginM * scaling + + Item { + Layout.fillWidth: true + } + + NButton { + text: "Cancel" + outlined: true + onClicked: settingsPopup.close() + } + + NButton { + text: "Save" + onClicked: { + if (settingsLoader.item && settingsLoader.item.saveSettings) { + var newSettings = settingsLoader.item.saveSettings() + root.updateWidgetSettings(sectionId, settingsPopup.widgetIndex, newSettings) + settingsPopup.close() + } + } + } + } + } + + // CustomButton settings component + Component { + id: customButtonSettings + + ColumnLayout { + spacing: Style.marginM * scaling + + property alias iconField: iconInput + property alias executeField: executeInput + + function saveSettings() { + var settings = Object.assign({}, settingsPopup.widgetData) + settings.icon = iconInput.text + settings.execute = executeInput.text + return settings + } + + // Icon setting + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS * scaling + + NText { + text: "Icon Name" + font.pointSize: Style.fontSizeS * scaling + color: Color.mOnSurfaceVariant + } + + NTextInput{ + id: iconInput + Layout.fillWidth: true + //placeholder: "Enter icon name (e.g., favorite, home, settings)" + text: settingsPopup.widgetData.icon || "" + } + + NText { + text: "Use Material Icon names from the icon set" + font.pointSize: Style.fontSizeXS * scaling + color: Color.applyOpacity(Color.mOnSurfaceVariant, "80") + } + } + + // Execute command setting + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginXS * scaling + + NText { + text: "Execute Command" + font.pointSize: Style.fontSizeS * scaling + color: Color.mOnSurfaceVariant + } + + NTextInput { + id: executeInput + Layout.fillWidth: true + //placeholder: "Enter command to execute (e.g., firefox, code, terminal)" + text: settingsPopup.widgetData.execute || "" + } + + NText { + text: "Command or application to run when clicked" + font.pointSize: Style.fontSizeXS * scaling + color: Color.applyOpacity(Color.mOnSurfaceVariant, "80") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + } + } + } } } @@ -122,7 +312,7 @@ NBox { width: widgetContent.implicitWidth + Style.marginL * scaling height: 40 * scaling radius: Style.radiusL * scaling - color: root.getWidgetColor(modelData.id) + color: root.getWidgetColor(modelData) border.color: Color.mOutline border.width: Math.max(1, Style.borderS * scaling) @@ -172,7 +362,14 @@ NBox { colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") colorFgHover: Color.mOnPrimary onClicked: { - // TODO open settings + // Open widget settings dialog + var dialog = widgetSettingsDialog.createObject(root, { + widgetIndex: index, + widgetData: modelData, + widgetId: modelData.id, + parent: Overlay.overlay + }) + dialog.open() } } } @@ -199,15 +396,15 @@ NBox { drag.target: parent onPressed: mouse => { - // Check if the click is on the close button area - const closeButtonX = widgetContent.x + widgetContent.width - 20 * scaling - const closeButtonY = widgetContent.y - const closeButtonWidth = 20 * scaling - const closeButtonHeight = 20 * scaling + // Check if the click is on the settings or close button area + const buttonsX = widgetContent.x + widgetContent.width - 45 * scaling + const buttonsY = widgetContent.y + const buttonsWidth = 45 * scaling + const buttonsHeight = 20 * scaling - if (mouseX >= closeButtonX && mouseX <= closeButtonX + closeButtonWidth - && mouseY >= closeButtonY && mouseY <= closeButtonY + closeButtonHeight) { - // Click is on the close button, don't start drag + if (mouseX >= buttonsX && mouseX <= buttonsX + buttonsWidth + && mouseY >= buttonsY && mouseY <= buttonsY + buttonsHeight) { + // Click is on the buttons, don't start drag mouse.accepted = false return } @@ -255,13 +452,7 @@ NBox { if (targetIndex !== -1 && targetIndex !== index) { const fromIndex = index const toIndex = targetIndex - // Logger.log( - // "NSectionEditor", - // `Dropped widget from index ${fromIndex} to position ${toIndex} (distance: ${minDistance.toFixed( - // 2)})`) reorderWidget(sectionId, fromIndex, toIndex) - } else { - Logger.warn("NSectionEditor", `No valid drop target found for widget at index ${index}`) } } } From 807867ef429f0290adfe154227e1cef4c6155e23 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 3 Sep 2025 21:27:42 -0400 Subject: [PATCH 03/14] Custom buttons: refactored files structure --- Modules/Bar/Widgets/CustomButton.qml | 25 +- .../SettingsPanel/Extras/BarSectionEditor.qml | 264 +++--------------- .../Extras/BarWidgetSettingsDialog.qml | 147 ++++++++++ Modules/SettingsPanel/Tabs/BarTab.qml | 7 +- Services/BarWidgetRegistry.qml | 13 +- Widgets/NTextInput.qml | 7 +- 6 files changed, 218 insertions(+), 245 deletions(-) rename Widgets/NSectionEditor.qml => Modules/SettingsPanel/Extras/BarSectionEditor.qml (59%) create mode 100644 Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index 42fa154..1b9462d 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -1,19 +1,21 @@ import QtQuick import QtQuick.Layouts +import Quickshell import qs.Commons import qs.Services import qs.Widgets NIconButton { id: root - + // Widget properties passed from Bar.qml property var screen property real scaling: 1.0 + property string barSection: "" property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 - + // Get user settings from Settings data property var widgetSettings: { var section = barSection.replace("Section", "").toLowerCase() @@ -25,19 +27,15 @@ NIconButton { } return {} } - + // Use settings or defaults from BarWidgetRegistry property string userIcon: widgetSettings.icon || BarWidgetRegistry.widgetMetadata["CustomButton"].icon property string userExecute: widgetSettings.execute || BarWidgetRegistry.widgetMetadata["CustomButton"].execute - + icon: userIcon tooltipText: userExecute ? `Execute: ${userExecute}` : "Custom Button - Configure in settings" - - colorBg: Color.transparent - colorFg: Color.mOnSurface - colorBgHover: Color.applyOpacity(Color.mPrimary, "20") - colorFgHover: Color.mPrimary - + opacity: userExecute ? Style.opacityFull : Style.opacityMedium + onClicked: { if (userExecute) { // Execute the user's command @@ -47,11 +45,8 @@ NIconButton { Logger.warn("CustomButton", "No command configured for this button") } } - - // Visual feedback when no command is set - opacity: userExecute ? 1.0 : 0.6 - + Component.onCompleted: { Logger.log("CustomButton", `Initialized with icon: ${userIcon}, command: ${userExecute}`) } -} \ No newline at end of file +} diff --git a/Widgets/NSectionEditor.qml b/Modules/SettingsPanel/Extras/BarSectionEditor.qml similarity index 59% rename from Widgets/NSectionEditor.qml rename to Modules/SettingsPanel/Extras/BarSectionEditor.qml index db4cdb6..0f4767a 100644 --- a/Widgets/NSectionEditor.qml +++ b/Modules/SettingsPanel/Extras/BarSectionEditor.qml @@ -37,207 +37,29 @@ NBox { // Generate widget color from name checksum function getWidgetColor(widget) { const totalSum = JSON.stringify(widget).split('').reduce((acc, character) => { - return acc + character.charCodeAt(0) - }, 0) + return acc + character.charCodeAt(0) + }, 0) switch (totalSum % 10) { - case 0: - return Color.mPrimary - case 1: - return Color.mSecondary - case 2: - return Color.mTertiary - case 3: - return Color.mError - case 4: - return Color.mOnSurface - case 5: - return Qt.darker(Color.mPrimary, 1.3) - case 6: - return Qt.darker(Color.mSecondary, 1.3) - case 7: - return Qt.darker(Color.mTertiary, 1.3) - case 8: - return Qt.darker(Color.mError, 1.3) - case 9: - return Qt.darker(Color.mOnSurface, 1.3) - } - } - - // Widget Settings Dialog Component - Component { - id: widgetSettingsDialog - - Popup { - id: settingsPopup - - property int widgetIndex: -1 - property var widgetData: null - property string widgetId: "" - - // Center popup in parent - x: (parent.width - width) * 0.5 - y: (parent.height - height) * 0.5 - - width: 400 * scaling - height: content.implicitHeight + padding * 2 - padding: Style.marginL * scaling - modal: true - - background: Rectangle { - id: bgRect - color: Color.mSurface - radius: Style.radiusL * scaling - border.color: Color.mPrimary - border.width: Style.borderM * scaling - } - - - - ColumnLayout { - id: content - width: parent.width - spacing: Style.marginM * scaling - - // Title - RowLayout { - Layout.fillWidth: true - - NText { - text: "Widget Settings: " + settingsPopup.widgetId - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mPrimary - Layout.fillWidth: true - } - - NIconButton { - icon: "close" - colorBg: Color.transparent - colorFg: Color.mOnSurface - colorBgHover: Color.applyOpacity(Color.mError, "20") - colorFgHover: Color.mError - onClicked: settingsPopup.close() - } - } - - // Separator - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: Color.mOutline - } - - // Settings based on widget type - Loader { - id: settingsLoader - Layout.fillWidth: true - sourceComponent: { - if (settingsPopup.widgetId === "CustomButton") { - return customButtonSettings - } - // Add more widget settings components here as needed - return null - } - } - - // Action buttons - RowLayout { - Layout.fillWidth: true - Layout.topMargin: Style.marginM * scaling - - Item { - Layout.fillWidth: true - } - - NButton { - text: "Cancel" - outlined: true - onClicked: settingsPopup.close() - } - - NButton { - text: "Save" - onClicked: { - if (settingsLoader.item && settingsLoader.item.saveSettings) { - var newSettings = settingsLoader.item.saveSettings() - root.updateWidgetSettings(sectionId, settingsPopup.widgetIndex, newSettings) - settingsPopup.close() - } - } - } - } - } - - // CustomButton settings component - Component { - id: customButtonSettings - - ColumnLayout { - spacing: Style.marginM * scaling - - property alias iconField: iconInput - property alias executeField: executeInput - - function saveSettings() { - var settings = Object.assign({}, settingsPopup.widgetData) - settings.icon = iconInput.text - settings.execute = executeInput.text - return settings - } - - // Icon setting - ColumnLayout { - Layout.fillWidth: true - spacing: Style.marginXS * scaling - - NText { - text: "Icon Name" - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnSurfaceVariant - } - - NTextInput{ - id: iconInput - Layout.fillWidth: true - //placeholder: "Enter icon name (e.g., favorite, home, settings)" - text: settingsPopup.widgetData.icon || "" - } - - NText { - text: "Use Material Icon names from the icon set" - font.pointSize: Style.fontSizeXS * scaling - color: Color.applyOpacity(Color.mOnSurfaceVariant, "80") - } - } - - // Execute command setting - ColumnLayout { - Layout.fillWidth: true - spacing: Style.marginXS * scaling - - NText { - text: "Execute Command" - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnSurfaceVariant - } - - NTextInput { - id: executeInput - Layout.fillWidth: true - //placeholder: "Enter command to execute (e.g., firefox, code, terminal)" - text: settingsPopup.widgetData.execute || "" - } - - NText { - text: "Command or application to run when clicked" - font.pointSize: Style.fontSizeXS * scaling - color: Color.applyOpacity(Color.mOnSurfaceVariant, "80") - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - } - } - } + case 0: + return Color.mPrimary + case 1: + return Color.mSecondary + case 2: + return Color.mTertiary + case 3: + return Color.mError + case 4: + return Color.mOnSurface + case 5: + return Qt.darker(Color.mPrimary, 1.3) + case 6: + return Qt.darker(Color.mSecondary, 1.3) + case 7: + return Qt.darker(Color.mTertiary, 1.3) + case 8: + return Qt.darker(Color.mError, 1.3) + case 9: + return Qt.darker(Color.mOnSurface, 1.3) } } @@ -301,7 +123,6 @@ NBox { spacing: Style.marginS * scaling flow: Flow.LeftToRight - Repeater { model: widgetModel delegate: Rectangle { @@ -363,18 +184,25 @@ NBox { colorFgHover: Color.mOnPrimary onClicked: { // Open widget settings dialog - var dialog = widgetSettingsDialog.createObject(root, { - widgetIndex: index, - widgetData: modelData, - widgetId: modelData.id, - parent: Overlay.overlay - }) + var dialog = Qt.createComponent("BarWidgetSettingsDialog.qml").createObject(root, { + "widgetIndex": index, + "widgetData": modelData, + "widgetId": modelData.id, + "parent": Overlay.overlay + }) + // }) + + // var dialog = widgetSettingsDialog.createObject(root, { + // widgetIndex: index, + // widgetData: modelData, + // widgetId: modelData.id, + // parent: Overlay.overlay + // }) dialog.open() } } } - NIconButton { icon: "close" sizeRatio: 0.6 @@ -402,20 +230,20 @@ NBox { const buttonsWidth = 45 * scaling const buttonsHeight = 20 * scaling - if (mouseX >= buttonsX && mouseX <= buttonsX + buttonsWidth - && mouseY >= buttonsY && mouseY <= buttonsY + buttonsHeight) { + if (mouseX >= buttonsX && mouseX <= buttonsX + buttonsWidth && mouseY >= buttonsY + && mouseY <= buttonsY + buttonsHeight) { // Click is on the buttons, don't start drag mouse.accepted = false return } - //Logger.log("NSectionEditor", `Started dragging widget: ${modelData.id} at index ${index}`) + //Logger.log("BarSectionEditor", `Started dragging widget: ${modelData.id} at index ${index}`) // Bring to front when starting drag widgetItem.z = 1000 } onReleased: { - //Logger.log("NSectionEditor", `Released widget: ${modelData.id} at index ${index}`) + //Logger.log("BarSectionEditor", `Released widget: ${modelData.id} at index ${index}`) // Reset z-index when drag ends widgetItem.z = 0 @@ -478,16 +306,16 @@ NBox { radius: Style.radiusS * scaling } - onEntered: function (drag) {//Logger.log("NSectionEditor", "Entered start drop zone") + onEntered: function (drag) {//Logger.log("BarSectionEditor", "Entered start drop zone") } onDropped: function (drop) { - //Logger.log("NSectionEditor", "Dropped on start zone") + //Logger.log("BarSectionEditor", "Dropped on start zone") if (drop.source && drop.source.widgetIndex !== undefined) { const fromIndex = drop.source.widgetIndex const toIndex = 0 // Insert at the beginning if (fromIndex !== toIndex) { - //Logger.log("NSectionEditor", `Dropped widget from index ${fromIndex} to beginning`) + //Logger.log("BarSectionEditor", `Dropped widget from index ${fromIndex} to beginning`) reorderWidget(sectionId, fromIndex, toIndex) } } @@ -512,16 +340,16 @@ NBox { radius: Style.radiusS * scaling } - onEntered: function (drag) {//Logger.log("NSectionEditor", "Entered end drop zone") + onEntered: function (drag) {//Logger.log("BarSectionEditor", "Entered end drop zone") } onDropped: function (drop) { - //Logger.log("NSectionEditor", "Dropped on end zone") + //Logger.log("BarSectionEditor", "Dropped on end zone") if (drop.source && drop.source.widgetIndex !== undefined) { const fromIndex = drop.source.widgetIndex const toIndex = widgetModel.length // Insert at the end if (fromIndex !== toIndex) { - //Logger.log("NSectionEditor", `Dropped widget from index ${fromIndex} to end`) + //Logger.log("BarSectionEditor", `Dropped widget from index ${fromIndex} to end`) reorderWidget(sectionId, fromIndex, toIndex) } } diff --git a/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml b/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml new file mode 100644 index 0000000..956beda --- /dev/null +++ b/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml @@ -0,0 +1,147 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +// Widget Settings Dialog Component +Popup { + id: settingsPopup + + property int widgetIndex: -1 + property var widgetData: null + property string widgetId: "" + + // Center popup in parent + x: (parent.width - width) * 0.5 + y: (parent.height - height) * 0.5 + + width: 400 * scaling + height: content.implicitHeight + padding * 2 + padding: Style.marginL * scaling + modal: true + + background: Rectangle { + id: bgRect + color: Color.mSurface + radius: Style.radiusL * scaling + border.color: Color.mPrimary + border.width: Style.borderM * scaling + } + + ColumnLayout { + id: content + width: parent.width + spacing: Style.marginM * scaling + + // Title + RowLayout { + Layout.fillWidth: true + + NText { + text: "Widget Settings: " + settingsPopup.widgetId + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + colorBg: Color.transparent + colorFg: Color.mOnSurface + colorBgHover: Color.applyOpacity(Color.mError, "20") + colorFgHover: Color.mError + onClicked: settingsPopup.close() + } + } + + // Separator + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + } + + // Settings based on widget type + Loader { + id: settingsLoader + Layout.fillWidth: true + sourceComponent: { + if (settingsPopup.widgetId === "CustomButton") { + return customButtonSettings + } + // Add more widget settings components here as needed + return null + } + } + + // Action buttons + RowLayout { + Layout.fillWidth: true + Layout.topMargin: Style.marginM * scaling + + Item { + Layout.fillWidth: true + } + + NButton { + text: "Cancel" + outlined: true + onClicked: settingsPopup.close() + } + + NButton { + text: "Save" + onClicked: { + if (settingsLoader.item && settingsLoader.item.saveSettings) { + var newSettings = settingsLoader.item.saveSettings() + root.updateWidgetSettings(sectionId, settingsPopup.widgetIndex, newSettings) + settingsPopup.close() + } + } + } + } + } + + // CustomButton settings component + Component { + id: customButtonSettings + + ColumnLayout { + spacing: Style.marginM * scaling + + property alias iconField: iconInput + property alias executeField: executeInput + + function saveSettings() { + var settings = Object.assign({}, settingsPopup.widgetData) + settings.icon = iconInput.text + settings.execute = executeInput.text + return settings + } + + // Icon setting + NTextInput { + id: iconInput + Layout.fillWidth: true + label: "Icon Name" + description: "Use Material Icon names from the icon set" + text: settingsPopup.widgetData.icon || "" + placeholderText: "Enter icon name (e.g., favorite, home, settings)" + } + + // Execute command setting + NTextInput { + id: executeInput + Layout.fillWidth: true + label: "Execute Command" + description: "Command or application to run when clicked" + text: settingsPopup.widgetData.execute || "" + placeholderText: "Enter command to execute (app or custom script)" + } + } + } +} diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index 6a76022..949ae04 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts import qs.Commons import qs.Services import qs.Widgets +import qs.Modules.SettingsPanel.Extras ColumnLayout { id: root @@ -157,7 +158,7 @@ ColumnLayout { spacing: Style.marginM * scaling // Left Section - NSectionEditor { + BarSectionEditor { sectionName: "Left" sectionId: "left" widgetModel: Settings.data.bar.widgets.left @@ -169,7 +170,7 @@ ColumnLayout { } // Center Section - NSectionEditor { + BarSectionEditor { sectionName: "Center" sectionId: "center" widgetModel: Settings.data.bar.widgets.center @@ -181,7 +182,7 @@ ColumnLayout { } // Right Section - NSectionEditor { + BarSectionEditor { sectionName: "Right" sectionId: "right" widgetModel: Settings.data.bar.widgets.right diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index f99ebe6..155b583 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -35,13 +35,12 @@ Singleton { }) property var widgetMetadata: ({ - "CustomButton": { - allowUserSettings: true, - icon: "favorite", - execute: "" - }, - }) - + "CustomButton": { + "allowUserSettings": true, + "icon": "favorite", + "execute": "" + } + }) // Component definitions - these are loaded once at startup property Component activeWindowComponent: Component { diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index 829c92a..1e54a51 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -39,10 +39,13 @@ ColumnLayout { // Container Rectangle { id: frame - implicitWidth: parent.width - implicitHeight: Style.baseWidgetSize * 1.1 * scaling + + Layout.fillWidth: true Layout.minimumWidth: 80 * scaling Layout.maximumWidth: root.inputMaxWidth + + implicitWidth: parent.width + implicitHeight: Style.baseWidgetSize * 1.1 * scaling radius: Style.radiusM * scaling color: Color.mSurface border.color: Color.mOutline From 06a11f003bc1f342507d1a40344317ffd5fb9e62 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 3 Sep 2025 21:58:51 -0400 Subject: [PATCH 04/14] SettingsPanel: fixed audio tab name --- Modules/Bar/Widgets/Microphone.qml | 2 +- Modules/Bar/Widgets/Volume.qml | 2 +- Modules/SettingsPanel/SettingsPanel.qml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml index b9761a3..5aa7ba9 100644 --- a/Modules/Bar/Widgets/Microphone.qml +++ b/Modules/Bar/Widgets/Microphone.qml @@ -93,7 +93,7 @@ Item { } onClicked: { var settingsPanel = PanelService.getPanel("settingsPanel") - settingsPanel.requestedTab = SettingsPanel.Tab.AudioService + settingsPanel.requestedTab = SettingsPanel.Tab.Audio settingsPanel.open(screen) } onRightClicked: { diff --git a/Modules/Bar/Widgets/Volume.qml b/Modules/Bar/Widgets/Volume.qml index 36e396b..9d98eea 100644 --- a/Modules/Bar/Widgets/Volume.qml +++ b/Modules/Bar/Widgets/Volume.qml @@ -78,7 +78,7 @@ Item { } onClicked: { var settingsPanel = PanelService.getPanel("settingsPanel") - settingsPanel.requestedTab = SettingsPanel.Tab.AudioService + settingsPanel.requestedTab = SettingsPanel.Tab.Audio settingsPanel.open(screen) } onRightClicked: { diff --git a/Modules/SettingsPanel/SettingsPanel.qml b/Modules/SettingsPanel/SettingsPanel.qml index 4dfcfad..f3eeea5 100644 --- a/Modules/SettingsPanel/SettingsPanel.qml +++ b/Modules/SettingsPanel/SettingsPanel.qml @@ -30,7 +30,7 @@ NPanel { // Tabs enumeration, order is NOT relevant enum Tab { About, - AudioService, + Audio, Bar, Launcher, Brightness, @@ -131,7 +131,7 @@ NPanel { "icon": "apps", "source": launcherTab }, { - "id": SettingsPanel.Tab.AudioService, + "id": SettingsPanel.Tab.Audio, "label": "Audio", "icon": "volume_up", "source": audioTab From 1f919e4469c3f347f54fa763d191956b63061cf1 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 3 Sep 2025 21:59:03 -0400 Subject: [PATCH 05/14] NIconButton: added support for middle click --- Widgets/NIconButton.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Widgets/NIconButton.qml b/Widgets/NIconButton.qml index 9955ecc..0650839 100644 --- a/Widgets/NIconButton.qml +++ b/Widgets/NIconButton.qml @@ -28,6 +28,7 @@ Rectangle { signal exited signal clicked signal rightClicked + signal middleClicked implicitWidth: size implicitHeight: size @@ -59,7 +60,7 @@ Rectangle { enabled: root.enabled anchors.fill: parent cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton hoverEnabled: true onEntered: { hovering = true @@ -83,6 +84,8 @@ Rectangle { root.clicked() } else if (mouse.button === Qt.RightButton) { root.rightClicked() + } else if (mouse.button === Qt.MiddleButton) { + root.middleClicked() } } } From 17944211d520e25da61fec31ee918e265e295c0d Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 3 Sep 2025 21:59:33 -0400 Subject: [PATCH 06/14] Custom buttons: WIP support for left/right/middle click --- Modules/Bar/Widgets/CustomButton.qml | 58 +++++++++++++++---- .../SettingsPanel/Extras/BarSectionEditor.qml | 8 --- .../Extras/BarWidgetSettingsDialog.qml | 7 ++- Modules/SettingsPanel/Tabs/BarTab.qml | 58 +++++++------------ Services/BarWidgetRegistry.qml | 4 +- 5 files changed, 75 insertions(+), 60 deletions(-) diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index 1b9462d..973e628 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -4,6 +4,7 @@ import Quickshell import qs.Commons import qs.Services import qs.Widgets +import qs.Modules.SettingsPanel NIconButton { id: root @@ -29,24 +30,57 @@ NIconButton { } // Use settings or defaults from BarWidgetRegistry - property string userIcon: widgetSettings.icon || BarWidgetRegistry.widgetMetadata["CustomButton"].icon - property string userExecute: widgetSettings.execute || BarWidgetRegistry.widgetMetadata["CustomButton"].execute + readonly property string userIcon: widgetSettings.icon || BarWidgetRegistry.widgetMetadata["CustomButton"].icon + readonly property string userLeftClickExec: widgetSettings.leftClickExec + || BarWidgetRegistry.widgetMetadata["CustomButton"].leftClickExec + readonly property string userRightClickExec: widgetSettings.rightClickExec + || BarWidgetRegistry.widgetMetadata["CustomButton"].rightClickExec + readonly property string userMiddleClickExec: widgetSettings.middleClickExec + || BarWidgetRegistry.widgetMetadata["CustomButton"].middleClickExec + readonly property bool hasExec: (userLeftClickExec || userRightClickExec || userMiddleClickExec) icon: userIcon - tooltipText: userExecute ? `Execute: ${userExecute}` : "Custom Button - Configure in settings" - opacity: userExecute ? Style.opacityFull : Style.opacityMedium + tooltipText: { + if (!hasExec) { + return "Custom Button - Configure in settings" + } else { + var lines = [] + if (userLeftClickExec !== "") { + lines.push(`Left click: ${userLeftClickExec}\n`) + } + if (userRightClickExec !== "") { + lines.push(`Right click: ${userRightClickExec}\n`) + } + if (userLeftClickExec !== "") { + lines.push(`Middle click: ${userMiddleClickExec}\n`) + } + } + } + opacity: hasExec ? Style.opacityFull : Style.opacityMedium onClicked: { - if (userExecute) { - // Execute the user's command - Quickshell.execDetached(userExecute.split(" ")) - Logger.log("CustomButton", `Executing command: ${userExecute}`) - } else { - Logger.warn("CustomButton", "No command configured for this button") + if (userLeftClickExec) { + Quickshell.execDetached(userLeftClickExec.split(" ")) + Logger.log("CustomButton", `Executing command: ${userLeftClickExec}`) + } else if (!hasExec) { + // No script was defined, open settings + var settingsPanel = PanelService.getPanel("settingsPanel") + settingsPanel.requestedTab = SettingsPanel.Tab.Bar + settingsPanel.open(screen) } } - Component.onCompleted: { - Logger.log("CustomButton", `Initialized with icon: ${userIcon}, command: ${userExecute}`) + onRightClicked: { + if (userRightClickExec) { + Quickshell.execDetached(userRightClickExec.split(" ")) + Logger.log("CustomButton", `Executing command: ${userRightClickExec}`) + } + } + + onMiddleClicked: { + if (userMiddleClickExec) { + Quickshell.execDetached(userMiddleClickExec.split(" ")) + Logger.log("CustomButton", `Executing command: ${userMiddleClickExec}`) + } } } diff --git a/Modules/SettingsPanel/Extras/BarSectionEditor.qml b/Modules/SettingsPanel/Extras/BarSectionEditor.qml index 0f4767a..8963234 100644 --- a/Modules/SettingsPanel/Extras/BarSectionEditor.qml +++ b/Modules/SettingsPanel/Extras/BarSectionEditor.qml @@ -190,14 +190,6 @@ NBox { "widgetId": modelData.id, "parent": Overlay.overlay }) - // }) - - // var dialog = widgetSettingsDialog.createObject(root, { - // widgetIndex: index, - // widgetData: modelData, - // widgetId: modelData.id, - // parent: Overlay.overlay - // }) dialog.open() } } diff --git a/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml b/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml index 956beda..922f0a2 100644 --- a/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml +++ b/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml @@ -96,6 +96,7 @@ Popup { NButton { text: "Save" onClicked: { + if (settingsLoader.item && settingsLoader.item.saveSettings) { var newSettings = settingsLoader.item.saveSettings() root.updateWidgetSettings(sectionId, settingsPopup.widgetIndex, newSettings) @@ -128,7 +129,7 @@ Popup { id: iconInput Layout.fillWidth: true label: "Icon Name" - description: "Use Material Icon names from the icon set" + description: "Use Material Icon names from the icon set." text: settingsPopup.widgetData.icon || "" placeholderText: "Enter icon name (e.g., favorite, home, settings)" } @@ -138,8 +139,8 @@ Popup { id: executeInput Layout.fillWidth: true label: "Execute Command" - description: "Command or application to run when clicked" - text: settingsPopup.widgetData.execute || "" + description: "Command or application to run when clicked." + text: settingsPopup.widgetData.leftClickExec || "" placeholderText: "Enter command to execute (app or custom script)" } } diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index 949ae04..566228d 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -201,65 +201,51 @@ ColumnLayout { Layout.bottomMargin: Style.marginXL * scaling } + // --------------------------------- + // Helper functions function updateWidgetSettingsInSection(section, index, settings) { // Update the widget settings in the Settings data Settings.data.bar.widgets[section][index] = settings - Logger.log("BarTab", `Updated widget settings for ${settings.id} in ${section} section`) + //Logger.log("BarTab", `Updated widget settings for ${settings.id} in ${section} section`) } - // Helper functions function addWidgetToSection(widgetId, section) { - //Logger.log("BarTab", "Adding widget", widgetId, "to section", section) - var sectionArray = Settings.data.bar.widgets[section] - - if (sectionArray) { - // Create a new array to avoid modifying the original - var newArray = sectionArray.slice() - newArray.push({ - "id": widgetId - }) - //Logger.log("BarTab", "Widget added. New array:", JSON.stringify(newArray)) - - // Assign the new array - Settings.data.bar.widgets[section] = newArray + var newWidget = { + "id": widgetId } + if (BarWidgetRegistry.widgetHasUserSettings(widgetId)) { + var metadata = BarWidgetRegistry.widgetMetadata[widgetId] + if (metadata) { + Object.keys(metadata).forEach(function (key) { + if (key !== "allowUserSettings") { + newWidget[key] = metadata[key] + } + }) + } + } + Settings.data.bar.widgets[section].push(newWidget) } function removeWidgetFromSection(section, index) { - // Logger.log("BarTab", "Removing widget from section", section, "at index", index) - var sectionArray = Settings.data.bar.widgets[section] - - //Logger.log("BarTab", "Current section array:", JSON.stringify(sectionArray)) - if (sectionArray && index >= 0 && index < sectionArray.length) { - // Create a new array to avoid modifying the original - var newArray = sectionArray.slice() + if (index >= 0 && index < Settings.data.bar.widgets[section].length) { + var newArray = Settings.data.bar.widgets[section].slice() newArray.splice(index, 1) - //Logger.log("BarTab", "Widget removed. New array:", JSON.stringify(newArray)) - - // Assign the new array Settings.data.bar.widgets[section] = newArray - } else { - - //Logger.log("BarTab", "Invalid section or index:", section, index, "array length:", - // sectionArray ? sectionArray.length : "null") } } function reorderWidgetInSection(section, fromIndex, toIndex) { - //Logger.log("BarTab", "Reordering widget in section", section, "from", fromIndex, "to", toIndex) - var sectionArray = Settings.data.bar.widgets[section] - if (sectionArray && fromIndex >= 0 && fromIndex < sectionArray.length && toIndex >= 0 - && toIndex < sectionArray.length) { + if (fromIndex >= 0 && fromIndex < Settings.data.bar.widgets[section].length && toIndex >= 0 + && toIndex < Settings.data.bar.widgets[section].length) { // Create a new array to avoid modifying the original - var newArray = sectionArray.slice() + var newArray = Settings.data.bar.widgets[section].slice() var item = newArray[fromIndex] newArray.splice(fromIndex, 1) newArray.splice(toIndex, 0, item) - Logger.log("BarTab", "Widget reordered. New array:", JSON.stringify(newArray)) - // Assign the new array Settings.data.bar.widgets[section] = newArray + //Logger.log("BarTab", "Widget reordered. New array:", JSON.stringify(newArray)) } } diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index 155b583..b2f2b5a 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -38,7 +38,9 @@ Singleton { "CustomButton": { "allowUserSettings": true, "icon": "favorite", - "execute": "" + "leftClickExec": "", + "rightClickExec": "", + "middleClickExec": "" } }) From 21383b03c5f28cb7dfb55d9913f7e3e937aeb8de Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 3 Sep 2025 22:22:22 -0400 Subject: [PATCH 07/14] Custom Buttons: working left/right/middle click --- Modules/Bar/Widgets/CustomButton.qml | 7 +-- .../Extras/BarWidgetSettingsDialog.qml | 44 ++++++++++++------- Widgets/NTextInput.qml | 2 +- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index 973e628..8e8252b 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -46,14 +46,15 @@ NIconButton { } else { var lines = [] if (userLeftClickExec !== "") { - lines.push(`Left click: ${userLeftClickExec}\n`) + lines.push(`Left click: ${userLeftClickExec}`) } if (userRightClickExec !== "") { - lines.push(`Right click: ${userRightClickExec}\n`) + lines.push(`Right click: ${userRightClickExec}`) } if (userLeftClickExec !== "") { - lines.push(`Middle click: ${userMiddleClickExec}\n`) + lines.push(`Middle click: ${userMiddleClickExec}`) } + return lines.join("
") } } opacity: hasExec ? Style.opacityFull : Style.opacityMedium diff --git a/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml b/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml index 922f0a2..c17f98a 100644 --- a/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml +++ b/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml @@ -18,9 +18,9 @@ Popup { x: (parent.width - width) * 0.5 y: (parent.height - height) * 0.5 - width: 400 * scaling + width: 420 * scaling height: content.implicitHeight + padding * 2 - padding: Style.marginL * scaling + padding: Style.marginXL * scaling modal: true background: Rectangle { @@ -50,10 +50,6 @@ Popup { NIconButton { icon: "close" - colorBg: Color.transparent - colorFg: Color.mOnSurface - colorBgHover: Color.applyOpacity(Color.mError, "20") - colorFgHover: Color.mError onClicked: settingsPopup.close() } } @@ -96,7 +92,6 @@ Popup { NButton { text: "Save" onClicked: { - if (settingsLoader.item && settingsLoader.item.saveSettings) { var newSettings = settingsLoader.item.saveSettings() root.updateWidgetSettings(sectionId, settingsPopup.widgetIndex, newSettings) @@ -114,13 +109,12 @@ Popup { ColumnLayout { spacing: Style.marginM * scaling - property alias iconField: iconInput - property alias executeField: executeInput - function saveSettings() { var settings = Object.assign({}, settingsPopup.widgetData) settings.icon = iconInput.text - settings.execute = executeInput.text + settings.leftClickExec = leftClickExecInput.text + settings.rightClickExec = rightClickExecInput.text + settings.middleClickExec = middleClickExecInput.text return settings } @@ -128,21 +122,41 @@ Popup { NTextInput { id: iconInput Layout.fillWidth: true + Layout.bottomMargin: Style.marginXL * scaling label: "Icon Name" description: "Use Material Icon names from the icon set." text: settingsPopup.widgetData.icon || "" placeholderText: "Enter icon name (e.g., favorite, home, settings)" } - // Execute command setting + + NTextInput { - id: executeInput + id: leftClickExecInput Layout.fillWidth: true - label: "Execute Command" - description: "Command or application to run when clicked." + label: "Left Click Command" + description: "Command or application to run when left clicked." text: settingsPopup.widgetData.leftClickExec || "" placeholderText: "Enter command to execute (app or custom script)" } + + NTextInput { + id: rightClickExecInput + Layout.fillWidth: true + label: "Right Click Command" + description: "Command or application to run when right clicked." + text: settingsPopup.widgetData.rightClickExec || "" + placeholderText: "Enter command to execute (app or custom script)" + } + + NTextInput { + id: middleClickExecInput + Layout.fillWidth: true + label: "Middle Click Command" + description: "Command or application to run when middle clicked." + text: settingsPopup.widgetData.middleClickExec || "" + placeholderText: "Enter command to execute (app or custom script)" + } } } } diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index 1e54a51..1cb7141 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -79,7 +79,7 @@ ColumnLayout { readOnly: root.readOnly enabled: root.enabled color: Color.mOnSurface - placeholderTextColor: Color.mOnSurfaceVariant + placeholderTextColor: Qt.alpha(Color.mOnSurfaceVariant, 0.6) background: null font.family: fontFamily font.pointSize: fontSize From 4f3e0bdb1e7d15588ca93e55339f7d23716a5116 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 3 Sep 2025 22:30:42 -0400 Subject: [PATCH 08/14] SettingsPanel: remove keyboard focus, so it will close gracefully if clicking on something else (like others NPanels) --- Modules/SettingsPanel/SettingsPanel.qml | 3 --- Modules/SidePanel/SidePanel.qml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Modules/SettingsPanel/SettingsPanel.qml b/Modules/SettingsPanel/SettingsPanel.qml index f3eeea5..4670553 100644 --- a/Modules/SettingsPanel/SettingsPanel.qml +++ b/Modules/SettingsPanel/SettingsPanel.qml @@ -24,9 +24,6 @@ NPanel { panelAnchorHorizontalCenter: true panelAnchorVerticalCenter: true - // Enable keyboard focus for settings panel - panelKeyboardFocus: true - // Tabs enumeration, order is NOT relevant enum Tab { About, diff --git a/Modules/SidePanel/SidePanel.qml b/Modules/SidePanel/SidePanel.qml index e0e8189..ebdbf95 100644 --- a/Modules/SidePanel/SidePanel.qml +++ b/Modules/SidePanel/SidePanel.qml @@ -8,7 +8,7 @@ import qs.Services import qs.Widgets NPanel { - id: panel + id: root panelWidth: 460 * scaling panelHeight: 708 * scaling From f39dd2aa1cb82d425d86495e685f84d4612fa8e8 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 3 Sep 2025 22:59:59 -0400 Subject: [PATCH 09/14] Custom Button: better bar editor --- .../SettingsPanel/Extras/BarSectionEditor.qml | 78 ++++++++++--------- .../Extras/BarWidgetSettingsDialog.qml | 2 - 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/Modules/SettingsPanel/Extras/BarSectionEditor.qml b/Modules/SettingsPanel/Extras/BarSectionEditor.qml index 8963234..3a778b6 100644 --- a/Modules/SettingsPanel/Extras/BarSectionEditor.qml +++ b/Modules/SettingsPanel/Extras/BarSectionEditor.qml @@ -131,7 +131,7 @@ NBox { required property var modelData width: widgetContent.implicitWidth + Style.marginL * scaling - height: 40 * scaling + height: Style.baseWidgetSize * 1.15 * scaling radius: Style.radiusL * scaling color: root.getWidgetColor(modelData) border.color: Color.mOutline @@ -145,6 +145,8 @@ NBox { // Store the widget index for drag operations property int widgetIndex: index + readonly property int buttonsWidth: Math.round(20 * scaling) + readonly property int buttonsCount: 1 + BarWidgetRegistry.widgetHasUserSettings(modelData.id) // Visual feedback during drag states: State { @@ -161,7 +163,7 @@ NBox { id: widgetContent anchors.centerIn: parent - spacing: Style.marginXS * scaling + spacing: Style.marginXXS * scaling NText { text: modelData.id @@ -172,10 +174,34 @@ NBox { Layout.preferredWidth: 80 * scaling } - Loader { - active: BarWidgetRegistry.widgetHasUserSettings(modelData.id) - sourceComponent: NIconButton { - icon: "settings" + RowLayout { + spacing: 0 + Layout.preferredWidth: buttonsCount * buttonsWidth + Loader { + active: BarWidgetRegistry.widgetHasUserSettings(modelData.id) + sourceComponent: NIconButton { + icon: "settings" + sizeRatio: 0.6 + colorBorder: Color.applyOpacity(Color.mOutline, "40") + colorBg: Color.mOnSurface + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + onClicked: { + // Open widget settings dialog + var dialog = Qt.createComponent("BarWidgetSettingsDialog.qml").createObject(root, { + "widgetIndex": index, + "widgetData": modelData, + "widgetId": modelData.id, + "parent": Overlay.overlay + }) + dialog.open() + } + } + } + + NIconButton { + icon: "close" sizeRatio: 0.6 colorBorder: Color.applyOpacity(Color.mOutline, "40") colorBg: Color.mOnSurface @@ -183,33 +209,15 @@ NBox { colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") colorFgHover: Color.mOnPrimary onClicked: { - // Open widget settings dialog - var dialog = Qt.createComponent("BarWidgetSettingsDialog.qml").createObject(root, { - "widgetIndex": index, - "widgetData": modelData, - "widgetId": modelData.id, - "parent": Overlay.overlay - }) - dialog.open() + removeWidget(sectionId, index) } } } - - NIconButton { - icon: "close" - sizeRatio: 0.6 - colorBorder: Color.applyOpacity(Color.mOutline, "40") - colorBg: Color.mOnSurface - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - onClicked: { - removeWidget(sectionId, index) - } - } } // Mouse area for drag and drop + property int mouseXStartDrag: 0 + MouseArea { id: mouseArea anchors.fill: parent @@ -217,13 +225,8 @@ NBox { onPressed: mouse => { // Check if the click is on the settings or close button area - const buttonsX = widgetContent.x + widgetContent.width - 45 * scaling - const buttonsY = widgetContent.y - const buttonsWidth = 45 * scaling - const buttonsHeight = 20 * scaling - - if (mouseX >= buttonsX && mouseX <= buttonsX + buttonsWidth && mouseY >= buttonsY - && mouseY <= buttonsY + buttonsHeight) { + const buttonsX = widgetContent.x + widgetContent.width - (buttonsWidth * buttonsCount) + if (mouseX >= buttonsX) { // Click is on the buttons, don't start drag mouse.accepted = false return @@ -232,13 +235,18 @@ NBox { //Logger.log("BarSectionEditor", `Started dragging widget: ${modelData.id} at index ${index}`) // Bring to front when starting drag widgetItem.z = 1000 + mouseXStartDrag = mouseX } - onReleased: { + onReleased: mouse => { //Logger.log("BarSectionEditor", `Released widget: ${modelData.id} at index ${index}`) // Reset z-index when drag ends widgetItem.z = 0 + console.log(mouseXStartDrag - mouse.x) + + + // Get the global mouse position const globalDropX = mouseArea.mouseX + widgetItem.x + widgetFlow.x const globalDropY = mouseArea.mouseY + widgetItem.y + widgetFlow.y diff --git a/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml b/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml index c17f98a..cccacbb 100644 --- a/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml +++ b/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml @@ -129,8 +129,6 @@ Popup { placeholderText: "Enter icon name (e.g., favorite, home, settings)" } - - NTextInput { id: leftClickExecInput Layout.fillWidth: true From 9e819084af945f0d86b7187cbf44819e71828fd1 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Thu, 4 Sep 2025 00:04:02 -0400 Subject: [PATCH 10/14] BarSettings: reworking drag&drop --- .../SettingsPanel/Extras/BarSectionEditor.qml | 441 +++++++++--------- 1 file changed, 226 insertions(+), 215 deletions(-) diff --git a/Modules/SettingsPanel/Extras/BarSectionEditor.qml b/Modules/SettingsPanel/Extras/BarSectionEditor.qml index 3a778b6..0702347 100644 --- a/Modules/SettingsPanel/Extras/BarSectionEditor.qml +++ b/Modules/SettingsPanel/Extras/BarSectionEditor.qml @@ -115,93 +115,72 @@ NBox { } // Drag and Drop Widget Area - Flow { - id: widgetFlow - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: 65 * scaling - spacing: Style.marginS * scaling - flow: Flow.LeftToRight + // Replace your Flow section with this: - Repeater { - model: widgetModel - delegate: Rectangle { - id: widgetItem - required property int index - required property var modelData +// Drag and Drop Widget Area - use Item container +Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 65 * scaling - width: widgetContent.implicitWidth + Style.marginL * scaling - height: Style.baseWidgetSize * 1.15 * scaling - radius: Style.radiusL * scaling - color: root.getWidgetColor(modelData) - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) + Flow { + id: widgetFlow + anchors.fill: parent + spacing: Style.marginS * scaling + flow: Flow.LeftToRight - // Drag properties - Drag.keys: ["widget"] - Drag.active: mouseArea.drag.active - Drag.hotSpot.x: width / 2 - Drag.hotSpot.y: height / 2 + Repeater { + model: widgetModel + delegate: Rectangle { + id: widgetItem + required property int index + required property var modelData - // Store the widget index for drag operations - property int widgetIndex: index - readonly property int buttonsWidth: Math.round(20 * scaling) - readonly property int buttonsCount: 1 + BarWidgetRegistry.widgetHasUserSettings(modelData.id) + width: widgetContent.implicitWidth + Style.marginL * scaling + height: Style.baseWidgetSize * 1.15 * scaling + radius: Style.radiusL * scaling + color: root.getWidgetColor(modelData) + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) - // Visual feedback during drag - states: State { - when: mouseArea.drag.active - PropertyChanges { - target: widgetItem - scale: 1.1 - opacity: 0.9 - z: 1000 - } + // Store the widget index for drag operations + property int widgetIndex: index + readonly property int buttonsWidth: Math.round(20 * scaling) + readonly property int buttonsCount: 1 + BarWidgetRegistry.widgetHasUserSettings(modelData.id) + + // Visual feedback during drag + states: State { + when: flowDragArea.draggedIndex === index + PropertyChanges { + target: widgetItem + scale: 1.1 + opacity: 0.9 + z: 1000 + } + } + + RowLayout { + id: widgetContent + anchors.centerIn: parent + spacing: Style.marginXXS * scaling + + NText { + text: modelData.id + font.pointSize: Style.fontSizeS * scaling + color: Color.mOnPrimary + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + Layout.preferredWidth: 80 * scaling } RowLayout { - id: widgetContent - - anchors.centerIn: parent - spacing: Style.marginXXS * scaling - - NText { - text: modelData.id - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnPrimary - horizontalAlignment: Text.AlignHCenter - elide: Text.ElideRight - Layout.preferredWidth: 80 * scaling - } - - RowLayout { - spacing: 0 - Layout.preferredWidth: buttonsCount * buttonsWidth - Loader { - active: BarWidgetRegistry.widgetHasUserSettings(modelData.id) - sourceComponent: NIconButton { - icon: "settings" - sizeRatio: 0.6 - colorBorder: Color.applyOpacity(Color.mOutline, "40") - colorBg: Color.mOnSurface - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - onClicked: { - // Open widget settings dialog - var dialog = Qt.createComponent("BarWidgetSettingsDialog.qml").createObject(root, { - "widgetIndex": index, - "widgetData": modelData, - "widgetId": modelData.id, - "parent": Overlay.overlay - }) - dialog.open() - } - } - } - - NIconButton { - icon: "close" + spacing: 0 + Layout.preferredWidth: buttonsCount * buttonsWidth + + Loader { + active: BarWidgetRegistry.widgetHasUserSettings(modelData.id) + sourceComponent: NIconButton { + icon: "settings" sizeRatio: 0.6 colorBorder: Color.applyOpacity(Color.mOutline, "40") colorBg: Color.mOnSurface @@ -209,151 +188,183 @@ NBox { colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") colorFgHover: Color.mOnPrimary onClicked: { - removeWidget(sectionId, index) + var dialog = Qt.createComponent("BarWidgetSettingsDialog.qml").createObject(root, { + "widgetIndex": index, + "widgetData": modelData, + "widgetId": modelData.id, + "parent": Overlay.overlay + }) + dialog.open() } } } - } - // Mouse area for drag and drop - property int mouseXStartDrag: 0 - - MouseArea { - id: mouseArea - anchors.fill: parent - drag.target: parent - - onPressed: mouse => { - // Check if the click is on the settings or close button area - const buttonsX = widgetContent.x + widgetContent.width - (buttonsWidth * buttonsCount) - if (mouseX >= buttonsX) { - // Click is on the buttons, don't start drag - mouse.accepted = false - return - } - - //Logger.log("BarSectionEditor", `Started dragging widget: ${modelData.id} at index ${index}`) - // Bring to front when starting drag - widgetItem.z = 1000 - mouseXStartDrag = mouseX - } - - onReleased: mouse => { - //Logger.log("BarSectionEditor", `Released widget: ${modelData.id} at index ${index}`) - // Reset z-index when drag ends - widgetItem.z = 0 - - console.log(mouseXStartDrag - mouse.x) - - - - // Get the global mouse position - const globalDropX = mouseArea.mouseX + widgetItem.x + widgetFlow.x - const globalDropY = mouseArea.mouseY + widgetItem.y + widgetFlow.y - - // Find which widget the drop position is closest to - let targetIndex = -1 - let minDistance = Infinity - - for (var i = 0; i < widgetModel.length; i++) { - if (i !== index) { - // Get the position of other widgets - const otherWidget = widgetFlow.children[i] - if (otherWidget && otherWidget.widgetIndex !== undefined) { - // Calculate the center of the other widget - const otherCenterX = otherWidget.x + otherWidget.width / 2 + widgetFlow.x - const otherCenterY = otherWidget.y + otherWidget.height / 2 + widgetFlow.y - - // Calculate distance to the center of this widget - const distance = Math.sqrt(Math.pow(globalDropX - otherCenterX, - 2) + Math.pow(globalDropY - otherCenterY, 2)) - - if (distance < minDistance) { - minDistance = distance - targetIndex = otherWidget.widgetIndex - } - } - } - } - - // Only reorder if we found a valid target and it's different from current position - if (targetIndex !== -1 && targetIndex !== index) { - const fromIndex = index - const toIndex = targetIndex - reorderWidget(sectionId, fromIndex, toIndex) + NIconButton { + icon: "close" + sizeRatio: 0.6 + colorBorder: Color.applyOpacity(Color.mOutline, "40") + colorBg: Color.mOnSurface + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + onClicked: { + removeWidget(sectionId, index) } } } } } } - - // Drop zone at the beginning (positioned absolutely) - DropArea { - id: startDropZone - width: 40 * scaling - height: 40 * scaling - x: widgetFlow.x - y: widgetFlow.y + (widgetFlow.height - height) / 2 - keys: ["widget"] - z: 1001 // Above the Flow - - Rectangle { - anchors.fill: parent - color: startDropZone.containsDrag ? Color.applyOpacity(Color.mPrimary, "20") : Color.transparent - border.color: startDropZone.containsDrag ? Color.mPrimary : Color.transparent - border.width: startDropZone.containsDrag ? 2 : 0 - radius: Style.radiusS * scaling - } - - onEntered: function (drag) {//Logger.log("BarSectionEditor", "Entered start drop zone") - } - - onDropped: function (drop) { - //Logger.log("BarSectionEditor", "Dropped on start zone") - if (drop.source && drop.source.widgetIndex !== undefined) { - const fromIndex = drop.source.widgetIndex - const toIndex = 0 // Insert at the beginning - if (fromIndex !== toIndex) { - //Logger.log("BarSectionEditor", `Dropped widget from index ${fromIndex} to beginning`) - reorderWidget(sectionId, fromIndex, toIndex) - } - } - } - } - - // Drop zone at the end (positioned absolutely) - DropArea { - id: endDropZone - width: 40 * scaling - height: 40 * scaling - x: widgetFlow.x + widgetFlow.width - width - y: widgetFlow.y + (widgetFlow.height - height) / 2 - keys: ["widget"] - z: 1001 // Above the Flow - - Rectangle { - anchors.fill: parent - color: endDropZone.containsDrag ? Color.applyOpacity(Color.mPrimary, "20") : Color.transparent - border.color: endDropZone.containsDrag ? Color.mPrimary : Color.transparent - border.width: endDropZone.containsDrag ? 2 : 0 - radius: Style.radiusS * scaling - } - - onEntered: function (drag) {//Logger.log("BarSectionEditor", "Entered end drop zone") - } - - onDropped: function (drop) { - //Logger.log("BarSectionEditor", "Dropped on end zone") - if (drop.source && drop.source.widgetIndex !== undefined) { - const fromIndex = drop.source.widgetIndex - const toIndex = widgetModel.length // Insert at the end - if (fromIndex !== toIndex) { - //Logger.log("BarSectionEditor", `Dropped widget from index ${fromIndex} to end`) - reorderWidget(sectionId, fromIndex, toIndex) - } - } - } - } + } + +// MouseArea outside Flow, covering the same area + MouseArea { + id: flowDragArea + anchors.fill: parent + z: 999 // Above all widgets to ensure it gets events first + + // Critical properties for proper event handling + acceptedButtons: Qt.LeftButton + preventStealing: true // Prevent child items from stealing events + propagateComposedEvents: false // Don't propagate to children during drag + hoverEnabled: true + + property point startPos: Qt.point(0, 0) + property bool dragStarted: false + property int draggedIndex: -1 + property real dragThreshold: 15 * scaling + property Item draggedWidget: null + property point clickOffsetInWidget: Qt.point(0, 0) // Add this line + + onPressed: mouse => { + startPos = Qt.point(mouse.x, mouse.y) + dragStarted = false + draggedIndex = -1 + draggedWidget = null + + console.log("Mouse pressed at:", mouse.x, mouse.y) + + // Find which widget was clicked + for (var i = 0; i < widgetModel.length; i++) { + const widget = widgetFlow.children[i] + if (widget && widget.widgetIndex !== undefined) { + if (mouse.x >= widget.x && mouse.x <= widget.x + widget.width && + mouse.y >= widget.y && mouse.y <= widget.y + widget.height) { + + const localX = mouse.x - widget.x + const buttonsStartX = widget.width - (widget.buttonsCount * widget.buttonsWidth) + + if (localX < buttonsStartX) { + draggedIndex = widget.widgetIndex + draggedWidget = widget + + // Calculate and store where within the widget the user clicked + const clickOffsetX = mouse.x - widget.x // Distance from widget's left edge + const clickOffsetY = mouse.y - widget.y // Distance from widget's top edge + clickOffsetInWidget = Qt.point(clickOffsetX, clickOffsetY) + + Logger.log("BarSectionEditor", "Selected widget:", widgetModel[i].id, "at index", i) + Logger.log("BarSectionEditor", "Widget position:", widget.x, widget.y) + Logger.log("BarSectionEditor", "Mouse position:", mouse.x, mouse.y) + Logger.log("BarSectionEditor", "Click offset within widget:", clickOffsetInWidget.x, clickOffsetInWidget.y) + + // Immediately set prevent stealing to true when drag candidate is found + preventStealing = true + break + }else { + // Click was on buttons - allow event propagation + mouse.accepted = false + return + } + } + } + } + } + + onPositionChanged: mouse => { + if (draggedIndex !== -1) { + const deltaX = mouse.x - startPos.x + const deltaY = mouse.y - startPos.y + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY) + + //Logger.log("BarSectionEditor", "Position changed - distance:", distance.toFixed(2)) + + if (!dragStarted && distance > dragThreshold) { + dragStarted = true + Logger.log("BarSectionEditor", "Drag started") + + // Enable visual feedback + if (draggedWidget) { + draggedWidget.z = 1000 + } + } + + if (dragStarted && draggedWidget) { + // Adjust position to account for where within the widget the user clicked + draggedWidget.x = mouse.x - clickOffsetInWidget.x + draggedWidget.y = mouse.y - clickOffsetInWidget.y + } + } + } + + onReleased: mouse => { + if (dragStarted && draggedWidget) { + // Find drop target using current mouse position + let targetIndex = -1 + let minDistance = Infinity + + for (var i = 0; i < widgetModel.length; i++) { + if (i !== draggedIndex) { + const widget = widgetFlow.children[i] + if (widget && widget.widgetIndex !== undefined) { + const centerX = widget.x + widget.width / 2 + const centerY = widget.y + widget.height / 2 + const distance = Math.sqrt(Math.pow(mouse.x - centerX, 2) + Math.pow(mouse.y - centerY, 2)) + + if (distance < minDistance) { + minDistance = distance + targetIndex = widget.widgetIndex + } + } + } + } + + Logger.log("BarSectionEditor", "Drop target index:", targetIndex) + + // Reset widget position and z-order + draggedWidget.x = 0 + draggedWidget.y = 0 + draggedWidget.z = 0 + + if (targetIndex !== -1 && targetIndex !== draggedIndex) { + reorderWidget(sectionId, draggedIndex, targetIndex) + } + } else if (draggedIndex !== -1 && !dragStarted) { + // This was a click without drag - simulate click on the widget + // Find the clicked widget and trigger appropriate action + const widget = draggedWidget + if (widget) { + // Could add click handling here if needed + } + } + + // Reset everything + dragStarted = false + draggedIndex = -1 + draggedWidget = null + preventStealing = false // Allow normal event propagation again + } + + // Handle case where mouse leaves the area during drag + onExited: { + if (dragStarted && draggedWidget) { + // Reset position but keep drag state until release + draggedWidget.x = 0 + draggedWidget.y = 0 + draggedWidget.z = 0 + } + } + } +} } } From 4a45e73125be7976e63254e702afc7625f9c59f7 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Thu, 4 Sep 2025 00:27:38 -0400 Subject: [PATCH 11/14] BarSettings: better D&D --- .../SettingsPanel/Extras/BarSectionEditor.qml | 522 ++++++++++-------- Modules/SettingsPanel/Tabs/BarTab.qml | 45 +- 2 files changed, 315 insertions(+), 252 deletions(-) diff --git a/Modules/SettingsPanel/Extras/BarSectionEditor.qml b/Modules/SettingsPanel/Extras/BarSectionEditor.qml index 0702347..7bfd14a 100644 --- a/Modules/SettingsPanel/Extras/BarSectionEditor.qml +++ b/Modules/SettingsPanel/Extras/BarSectionEditor.qml @@ -117,254 +117,316 @@ NBox { // Drag and Drop Widget Area // Replace your Flow section with this: -// Drag and Drop Widget Area - use Item container -Item { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: 65 * scaling + // Drag and Drop Widget Area - use Item container + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 65 * scaling - Flow { - id: widgetFlow - anchors.fill: parent - spacing: Style.marginS * scaling - flow: Flow.LeftToRight + Flow { + id: widgetFlow + anchors.fill: parent + spacing: Style.marginS * scaling + flow: Flow.LeftToRight - Repeater { - model: widgetModel - delegate: Rectangle { - id: widgetItem - required property int index - required property var modelData + Repeater { + model: widgetModel + delegate: Rectangle { + id: widgetItem + required property int index + required property var modelData - width: widgetContent.implicitWidth + Style.marginL * scaling - height: Style.baseWidgetSize * 1.15 * scaling - radius: Style.radiusL * scaling - color: root.getWidgetColor(modelData) - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) + width: widgetContent.implicitWidth + Style.marginL * scaling + height: Style.baseWidgetSize * 1.15 * scaling + radius: Style.radiusL * scaling + color: root.getWidgetColor(modelData) + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) - // Store the widget index for drag operations - property int widgetIndex: index - readonly property int buttonsWidth: Math.round(20 * scaling) - readonly property int buttonsCount: 1 + BarWidgetRegistry.widgetHasUserSettings(modelData.id) + // Store the widget index for drag operations + property int widgetIndex: index + readonly property int buttonsWidth: Math.round(20 * scaling) + readonly property int buttonsCount: 1 + BarWidgetRegistry.widgetHasUserSettings(modelData.id) - // Visual feedback during drag - states: State { - when: flowDragArea.draggedIndex === index - PropertyChanges { - target: widgetItem - scale: 1.1 - opacity: 0.9 - z: 1000 - } - } + // Visual feedback during drag + states: State { + when: flowDragArea.draggedIndex === index + PropertyChanges { + target: widgetItem + scale: 1.1 + opacity: 0.9 + z: 1000 + } + } - RowLayout { - id: widgetContent - anchors.centerIn: parent - spacing: Style.marginXXS * scaling + RowLayout { + id: widgetContent + anchors.centerIn: parent + spacing: Style.marginXXS * scaling - NText { - text: modelData.id - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnPrimary - horizontalAlignment: Text.AlignHCenter - elide: Text.ElideRight - Layout.preferredWidth: 80 * scaling - } + NText { + text: modelData.id + font.pointSize: Style.fontSizeS * scaling + color: Color.mOnPrimary + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + Layout.preferredWidth: 80 * scaling + } - RowLayout { - spacing: 0 - Layout.preferredWidth: buttonsCount * buttonsWidth - - Loader { - active: BarWidgetRegistry.widgetHasUserSettings(modelData.id) - sourceComponent: NIconButton { - icon: "settings" - sizeRatio: 0.6 - colorBorder: Color.applyOpacity(Color.mOutline, "40") - colorBg: Color.mOnSurface - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - onClicked: { - var dialog = Qt.createComponent("BarWidgetSettingsDialog.qml").createObject(root, { - "widgetIndex": index, - "widgetData": modelData, - "widgetId": modelData.id, - "parent": Overlay.overlay - }) - dialog.open() + RowLayout { + spacing: 0 + Layout.preferredWidth: buttonsCount * buttonsWidth + + Loader { + active: BarWidgetRegistry.widgetHasUserSettings(modelData.id) + sourceComponent: NIconButton { + icon: "settings" + sizeRatio: 0.6 + colorBorder: Color.applyOpacity(Color.mOutline, "40") + colorBg: Color.mOnSurface + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + onClicked: { + var dialog = Qt.createComponent("BarWidgetSettingsDialog.qml").createObject(root, { + "widgetIndex": index, + "widgetData": modelData, + "widgetId": modelData.id, + "parent": Overlay.overlay + }) + dialog.open() + } + } + } + + NIconButton { + icon: "close" + sizeRatio: 0.6 + colorBorder: Color.applyOpacity(Color.mOutline, "40") + colorBg: Color.mOnSurface + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + onClicked: { + removeWidget(sectionId, index) + } } } } - - NIconButton { - icon: "close" - sizeRatio: 0.6 - colorBorder: Color.applyOpacity(Color.mOutline, "40") - colorBg: Color.mOnSurface - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - onClicked: { - removeWidget(sectionId, index) - } - } } } } - } - } -// MouseArea outside Flow, covering the same area - MouseArea { - id: flowDragArea - anchors.fill: parent - z: 999 // Above all widgets to ensure it gets events first - - // Critical properties for proper event handling - acceptedButtons: Qt.LeftButton - preventStealing: true // Prevent child items from stealing events - propagateComposedEvents: false // Don't propagate to children during drag - hoverEnabled: true - - property point startPos: Qt.point(0, 0) - property bool dragStarted: false - property int draggedIndex: -1 - property real dragThreshold: 15 * scaling - property Item draggedWidget: null - property point clickOffsetInWidget: Qt.point(0, 0) // Add this line - - onPressed: mouse => { - startPos = Qt.point(mouse.x, mouse.y) - dragStarted = false - draggedIndex = -1 - draggedWidget = null - - console.log("Mouse pressed at:", mouse.x, mouse.y) - - // Find which widget was clicked - for (var i = 0; i < widgetModel.length; i++) { - const widget = widgetFlow.children[i] - if (widget && widget.widgetIndex !== undefined) { - if (mouse.x >= widget.x && mouse.x <= widget.x + widget.width && - mouse.y >= widget.y && mouse.y <= widget.y + widget.height) { - - const localX = mouse.x - widget.x - const buttonsStartX = widget.width - (widget.buttonsCount * widget.buttonsWidth) - - if (localX < buttonsStartX) { - draggedIndex = widget.widgetIndex - draggedWidget = widget - - // Calculate and store where within the widget the user clicked - const clickOffsetX = mouse.x - widget.x // Distance from widget's left edge - const clickOffsetY = mouse.y - widget.y // Distance from widget's top edge - clickOffsetInWidget = Qt.point(clickOffsetX, clickOffsetY) - - Logger.log("BarSectionEditor", "Selected widget:", widgetModel[i].id, "at index", i) - Logger.log("BarSectionEditor", "Widget position:", widget.x, widget.y) - Logger.log("BarSectionEditor", "Mouse position:", mouse.x, mouse.y) - Logger.log("BarSectionEditor", "Click offset within widget:", clickOffsetInWidget.x, clickOffsetInWidget.y) - - // Immediately set prevent stealing to true when drag candidate is found - preventStealing = true - break - }else { - // Click was on buttons - allow event propagation - mouse.accepted = false - return - } + // MouseArea outside Flow, covering the same area + MouseArea { + id: flowDragArea + anchors.fill: parent + z: 999 // Above all widgets to ensure it gets events first + + // Critical properties for proper event handling + acceptedButtons: Qt.LeftButton + preventStealing: false // Prevent child items from stealing events + propagateComposedEvents: draggedIndex != -1 // Don't propagate to children during drag + hoverEnabled: draggedIndex != -1 + + property point startPos: Qt.point(0, 0) + property bool dragStarted: false + property int draggedIndex: -1 + property real dragThreshold: 15 * scaling + property Item draggedWidget: null + property point clickOffsetInWidget: Qt.point(0, 0) + property point originalWidgetPos: Qt.point(0, 0) // ADD THIS: Store original position + + onPressed: mouse => { + startPos = Qt.point(mouse.x, mouse.y) + dragStarted = false + draggedIndex = -1 + draggedWidget = null + + // Find which widget was clicked + for (var i = 0; i < widgetModel.length; i++) { + const widget = widgetFlow.children[i] + if (widget && widget.widgetIndex !== undefined) { + if (mouse.x >= widget.x && mouse.x <= widget.x + widget.width && mouse.y >= widget.y + && mouse.y <= widget.y + widget.height) { + + const localX = mouse.x - widget.x + const buttonsStartX = widget.width - (widget.buttonsCount * widget.buttonsWidth) + + if (localX < buttonsStartX) { + draggedIndex = widget.widgetIndex + draggedWidget = widget + + // Calculate and store where within the widget the user clicked + const clickOffsetX = mouse.x - widget.x + const clickOffsetY = mouse.y - widget.y + clickOffsetInWidget = Qt.point(clickOffsetX, clickOffsetY) + + // STORE ORIGINAL POSITION + originalWidgetPos = Qt.point(widget.x, widget.y) + + // Immediately set prevent stealing to true when drag candidate is found + preventStealing = true + break + } else { + // Click was on buttons - allow event propagation + mouse.accepted = false + return + } + } + } + } + } + + onPositionChanged: mouse => { + if (draggedIndex !== -1) { + const deltaX = mouse.x - startPos.x + const deltaY = mouse.y - startPos.y + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY) + + if (!dragStarted && distance > dragThreshold) { + dragStarted = true + //Logger.log("BarSectionEditor", "Drag started") + + // Enable visual feedback + if (draggedWidget) { + draggedWidget.z = 1000 + } + } + + if (dragStarted && draggedWidget) { + // Adjust position to account for where within the widget the user clicked + draggedWidget.x = mouse.x - clickOffsetInWidget.x + draggedWidget.y = mouse.y - clickOffsetInWidget.y + } + } + } + + onReleased: mouse => { + if (dragStarted && draggedWidget) { + // Find drop target using improved logic + let targetIndex = -1 + let minDistance = Infinity + const mouseX = mouse.x + const mouseY = mouse.y + + // Check if we should insert at the beginning + let insertAtBeginning = true + let insertAtEnd = true + + // Check if the dragged item is already the last item + let isLastItem = true + for (var k = 0; k < widgetModel.length; k++) { + if (k !== draggedIndex && k > draggedIndex) { + isLastItem = false + break + } + } + + for (var i = 0; i < widgetModel.length; i++) { + if (i !== draggedIndex) { + const widget = widgetFlow.children[i] + if (widget && widget.widgetIndex !== undefined) { + const centerX = widget.x + widget.width / 2 + const centerY = widget.y + widget.height / 2 + const distance = Math.sqrt(Math.pow(mouseX - centerX, 2) + Math.pow(mouseY - centerY, 2)) + + // Check if mouse is to the right of this widget + if (mouseX > widget.x + widget.width / 2) { + insertAtBeginning = false + } + // Check if mouse is to the left of this widget + if (mouseX < widget.x + widget.width / 2) { + insertAtEnd = false + } + + if (distance < minDistance) { + minDistance = distance + targetIndex = widget.widgetIndex + } + } + } + } + + // If dragging the last item to the right, don't reorder + if (isLastItem && insertAtEnd) { + insertAtEnd = false + targetIndex = -1 + Logger.log("BarSectionEditor", "Last item dropped to right - no reordering needed") + } + + // Determine final target index based on position + let finalTargetIndex = targetIndex + + if (insertAtBeginning && widgetModel.length > 1) { + // Insert at the very beginning (position 0) + finalTargetIndex = 0 + Logger.log("BarSectionEditor", "Inserting at beginning") + } else if (insertAtEnd && widgetModel.length > 1) { + // Insert at the very end + let maxIndex = -1 + for (var j = 0; j < widgetModel.length; j++) { + if (j !== draggedIndex) { + maxIndex = Math.max(maxIndex, j) + } + } + finalTargetIndex = maxIndex + Logger.log("BarSectionEditor", "Inserting at end, target:", finalTargetIndex) + } else if (targetIndex !== -1) { + // Normal case - determine if we should insert before or after the target + const targetWidget = widgetFlow.children[targetIndex] + if (targetWidget) { + const targetCenterX = targetWidget.x + targetWidget.width / 2 + if (mouseX > targetCenterX) { + // Mouse is to the right of target center, insert after + Logger.log("BarSectionEditor", "Inserting after widget at index:", targetIndex) + } else { + // Mouse is to the left of target center, insert before + finalTargetIndex = targetIndex + Logger.log("BarSectionEditor", "Inserting before widget at index:", targetIndex) + } + } + } + + Logger.log("BarSectionEditor", "Final drop target index:", finalTargetIndex) + + // Check if reordering is needed + if (finalTargetIndex !== -1 && finalTargetIndex !== draggedIndex) { + // Reordering will happen - reset position for the Flow to handle + draggedWidget.x = 0 + draggedWidget.y = 0 + draggedWidget.z = 0 + reorderWidget(sectionId, draggedIndex, finalTargetIndex) + } else { + // No reordering - restore original position + draggedWidget.x = originalWidgetPos.x + draggedWidget.y = originalWidgetPos.y + draggedWidget.z = 0 + Logger.log("BarSectionEditor", "No reordering - restoring original position") + } + } else if (draggedIndex !== -1 && !dragStarted) { + + // This was a click without drag - could add click handling here if needed + } + + // Reset everything + dragStarted = false + draggedIndex = -1 + draggedWidget = null + preventStealing = false // Allow normal event propagation again + originalWidgetPos = Qt.point(0, 0) // Reset stored position + } + + // Handle case where mouse leaves the area during drag + onExited: { + if (dragStarted && draggedWidget) { + // Restore original position when mouse leaves area + draggedWidget.x = originalWidgetPos.x + draggedWidget.y = originalWidgetPos.y + draggedWidget.z = 0 } } } } - - onPositionChanged: mouse => { - if (draggedIndex !== -1) { - const deltaX = mouse.x - startPos.x - const deltaY = mouse.y - startPos.y - const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY) - - //Logger.log("BarSectionEditor", "Position changed - distance:", distance.toFixed(2)) - - if (!dragStarted && distance > dragThreshold) { - dragStarted = true - Logger.log("BarSectionEditor", "Drag started") - - // Enable visual feedback - if (draggedWidget) { - draggedWidget.z = 1000 - } - } - - if (dragStarted && draggedWidget) { - // Adjust position to account for where within the widget the user clicked - draggedWidget.x = mouse.x - clickOffsetInWidget.x - draggedWidget.y = mouse.y - clickOffsetInWidget.y - } - } - } - - onReleased: mouse => { - if (dragStarted && draggedWidget) { - // Find drop target using current mouse position - let targetIndex = -1 - let minDistance = Infinity - - for (var i = 0; i < widgetModel.length; i++) { - if (i !== draggedIndex) { - const widget = widgetFlow.children[i] - if (widget && widget.widgetIndex !== undefined) { - const centerX = widget.x + widget.width / 2 - const centerY = widget.y + widget.height / 2 - const distance = Math.sqrt(Math.pow(mouse.x - centerX, 2) + Math.pow(mouse.y - centerY, 2)) - - if (distance < minDistance) { - minDistance = distance - targetIndex = widget.widgetIndex - } - } - } - } - - Logger.log("BarSectionEditor", "Drop target index:", targetIndex) - - // Reset widget position and z-order - draggedWidget.x = 0 - draggedWidget.y = 0 - draggedWidget.z = 0 - - if (targetIndex !== -1 && targetIndex !== draggedIndex) { - reorderWidget(sectionId, draggedIndex, targetIndex) - } - } else if (draggedIndex !== -1 && !dragStarted) { - // This was a click without drag - simulate click on the widget - // Find the clicked widget and trigger appropriate action - const widget = draggedWidget - if (widget) { - // Could add click handling here if needed - } - } - - // Reset everything - dragStarted = false - draggedIndex = -1 - draggedWidget = null - preventStealing = false // Allow normal event propagation again - } - - // Handle case where mouse leaves the area during drag - onExited: { - if (dragStarted && draggedWidget) { - // Reset position but keep drag state until release - draggedWidget.x = 0 - draggedWidget.y = 0 - draggedWidget.z = 0 - } - } - } -} } } diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index 566228d..e1bf63a 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -163,10 +163,10 @@ ColumnLayout { sectionId: "left" widgetModel: Settings.data.bar.widgets.left availableWidgets: availableWidgets - onAddWidget: (widgetId, section) => addWidgetToSection(widgetId, section) - onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) - onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) - onUpdateWidgetSettings: (section, index, settings) => updateWidgetSettingsInSection(section, index, settings) + onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) } // Center Section @@ -175,10 +175,10 @@ ColumnLayout { sectionId: "center" widgetModel: Settings.data.bar.widgets.center availableWidgets: availableWidgets - onAddWidget: (widgetId, section) => addWidgetToSection(widgetId, section) - onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) - onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) - onUpdateWidgetSettings: (section, index, settings) => updateWidgetSettingsInSection(section, index, settings) + onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) } // Right Section @@ -187,10 +187,10 @@ ColumnLayout { sectionId: "right" widgetModel: Settings.data.bar.widgets.right availableWidgets: availableWidgets - onAddWidget: (widgetId, section) => addWidgetToSection(widgetId, section) - onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) - onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) - onUpdateWidgetSettings: (section, index, settings) => updateWidgetSettingsInSection(section, index, settings) + onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section) + onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex) + onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings) } } } @@ -202,14 +202,9 @@ ColumnLayout { } // --------------------------------- - // Helper functions - function updateWidgetSettingsInSection(section, index, settings) { - // Update the widget settings in the Settings data - Settings.data.bar.widgets[section][index] = settings - //Logger.log("BarTab", `Updated widget settings for ${settings.id} in ${section} section`) - } - - function addWidgetToSection(widgetId, section) { + // Signal functions + // --------------------------------- + function _addWidgetToSection(widgetId, section) { var newWidget = { "id": widgetId } @@ -226,7 +221,7 @@ ColumnLayout { Settings.data.bar.widgets[section].push(newWidget) } - function removeWidgetFromSection(section, index) { + function _removeWidgetFromSection(section, index) { if (index >= 0 && index < Settings.data.bar.widgets[section].length) { var newArray = Settings.data.bar.widgets[section].slice() newArray.splice(index, 1) @@ -234,7 +229,7 @@ ColumnLayout { } } - function reorderWidgetInSection(section, fromIndex, toIndex) { + function _reorderWidgetInSection(section, fromIndex, toIndex) { if (fromIndex >= 0 && fromIndex < Settings.data.bar.widgets[section].length && toIndex >= 0 && toIndex < Settings.data.bar.widgets[section].length) { @@ -249,6 +244,12 @@ ColumnLayout { } } + function _updateWidgetSettingsInSection(section, index, settings) { + // Update the widget settings in the Settings data + Settings.data.bar.widgets[section][index] = settings + //Logger.log("BarTab", `Updated widget settings for ${settings.id} in ${section} section`) + } + // Base list model for all combo boxes ListModel { id: availableWidgets From 30e1c2d2b3bf3e5e3fdb6695dafaef229d87547e Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Thu, 4 Sep 2025 08:28:41 -0400 Subject: [PATCH 12/14] BarSectionEditor: cleaned up logs --- Modules/SettingsPanel/Extras/BarSectionEditor.qml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Modules/SettingsPanel/Extras/BarSectionEditor.qml b/Modules/SettingsPanel/Extras/BarSectionEditor.qml index 7bfd14a..830ee46 100644 --- a/Modules/SettingsPanel/Extras/BarSectionEditor.qml +++ b/Modules/SettingsPanel/Extras/BarSectionEditor.qml @@ -352,7 +352,7 @@ NBox { if (isLastItem && insertAtEnd) { insertAtEnd = false targetIndex = -1 - Logger.log("BarSectionEditor", "Last item dropped to right - no reordering needed") + //Logger.log("BarSectionEditor", "Last item dropped to right - no reordering needed") } // Determine final target index based on position @@ -361,7 +361,7 @@ NBox { if (insertAtBeginning && widgetModel.length > 1) { // Insert at the very beginning (position 0) finalTargetIndex = 0 - Logger.log("BarSectionEditor", "Inserting at beginning") + //Logger.log("BarSectionEditor", "Inserting at beginning") } else if (insertAtEnd && widgetModel.length > 1) { // Insert at the very end let maxIndex = -1 @@ -371,7 +371,7 @@ NBox { } } finalTargetIndex = maxIndex - Logger.log("BarSectionEditor", "Inserting at end, target:", finalTargetIndex) + //Logger.log("BarSectionEditor", "Inserting at end, target:", finalTargetIndex) } else if (targetIndex !== -1) { // Normal case - determine if we should insert before or after the target const targetWidget = widgetFlow.children[targetIndex] @@ -379,16 +379,16 @@ NBox { const targetCenterX = targetWidget.x + targetWidget.width / 2 if (mouseX > targetCenterX) { // Mouse is to the right of target center, insert after - Logger.log("BarSectionEditor", "Inserting after widget at index:", targetIndex) + //Logger.log("BarSectionEditor", "Inserting after widget at index:", targetIndex) } else { // Mouse is to the left of target center, insert before finalTargetIndex = targetIndex - Logger.log("BarSectionEditor", "Inserting before widget at index:", targetIndex) + //Logger.log("BarSectionEditor", "Inserting before widget at index:", targetIndex) } } } - Logger.log("BarSectionEditor", "Final drop target index:", finalTargetIndex) + //Logger.log("BarSectionEditor", "Final drop target index:", finalTargetIndex) // Check if reordering is needed if (finalTargetIndex !== -1 && finalTargetIndex !== draggedIndex) { @@ -402,7 +402,7 @@ NBox { draggedWidget.x = originalWidgetPos.x draggedWidget.y = originalWidgetPos.y draggedWidget.z = 0 - Logger.log("BarSectionEditor", "No reordering - restoring original position") + //Logger.log("BarSectionEditor", "No reordering - restoring original position") } } else if (draggedIndex !== -1 && !dragStarted) { From d8c91a942ffc6a1534777c57003a4b84e2d73928 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Thu, 4 Sep 2025 08:28:58 -0400 Subject: [PATCH 13/14] SettingsPanel: restoring keyboard focus --- Modules/SettingsPanel/SettingsPanel.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/SettingsPanel/SettingsPanel.qml b/Modules/SettingsPanel/SettingsPanel.qml index 4670553..65e742d 100644 --- a/Modules/SettingsPanel/SettingsPanel.qml +++ b/Modules/SettingsPanel/SettingsPanel.qml @@ -24,6 +24,8 @@ NPanel { panelAnchorHorizontalCenter: true panelAnchorVerticalCenter: true + panelKeyboardFocus: true + // Tabs enumeration, order is NOT relevant enum Tab { About, From 00d3f81aa11d0c4068c7030d5592b18ff0bf5787 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Thu, 4 Sep 2025 08:35:57 -0400 Subject: [PATCH 14/14] Bar: check if new widget modelData is available to avoid warnings. --- Modules/Bar/Bar.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 2c8e6f7..7365227 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -73,7 +73,7 @@ Variants { Repeater { model: Settings.data.bar.widgets.left delegate: NWidgetLoader { - widgetId: modelData.id + widgetId: (modelData.id !== undefined ? modelData.id : "") widgetProps: { "screen": root.modelData || null, "scaling": ScalingService.getScreenScale(screen), @@ -100,7 +100,7 @@ Variants { Repeater { model: Settings.data.bar.widgets.center delegate: NWidgetLoader { - widgetId: modelData.id + widgetId: (modelData.id !== undefined ? modelData.id : "") widgetProps: { "screen": root.modelData || null, "scaling": ScalingService.getScreenScale(screen), @@ -128,7 +128,7 @@ Variants { Repeater { model: Settings.data.bar.widgets.right delegate: NWidgetLoader { - widgetId: modelData.id + widgetId: (modelData.id !== undefined ? modelData.id : "") widgetProps: { "screen": root.modelData || null, "scaling": ScalingService.getScreenScale(screen),