From 598bc48957737083ec1af8116ff28c8f4381f26c Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 3 Sep 2025 20:51:51 -0400 Subject: [PATCH] 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}`) } } }