Custom buttons: improved UI, still wip

This commit is contained in:
LemmyCook 2025-09-03 20:51:51 -04:00
parent 7f34ca4122
commit 598bc48957
3 changed files with 279 additions and 48 deletions

View file

@ -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}`)
}
}

View file

@ -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)

View file

@ -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}`)
}
}
}