QuickSettings: editable widgets/button section in the ControlCenter

This commit is contained in:
ItsLemmy
2025-10-08 23:05:52 -04:00
parent c4d1a142ab
commit bff195309a
19 changed files with 542 additions and 357 deletions
@@ -13,6 +13,10 @@ NBox {
property string sectionId: ""
property var widgetModel: []
property var availableWidgets: []
property bool enableMoveBetweenSections: true
property var widgetRegistry: null
property string settingsDialogComponent: "BarWidgetSettingsDialog.qml"
readonly property real miniButtonSize: Style.baseWidgetSize * 0.65
@@ -154,7 +158,7 @@ NBox {
// Store the widget index for drag operations
property int widgetIndex: index
readonly property int buttonsWidth: Math.round(20 * scaling)
readonly property int buttonsCount: 1 + BarWidgetRegistry.widgetHasUserSettings(modelData.id)
readonly property int buttonsCount: 1 + (root.widgetRegistry ? root.widgetRegistry.widgetHasUserSettings(modelData.id) : 0)
// Visual feedback during drag
opacity: flowDragArea.draggedIndex === index ? 0.5 : 1.0
@@ -197,9 +201,10 @@ NBox {
onTriggered: action => root.moveWidget(root.sectionId, index, action)
}
// Update the MouseArea to use the new context menu
// MouseArea for the context menu
MouseArea {
id: contextMouseArea
enabled: enableMoveBetweenSections
anchors.fill: parent
acceptedButtons: Qt.RightButton
z: -1 // Below the buttons but above background
@@ -209,9 +214,7 @@ NBox {
// Check if click is not on the buttons area
const localX = mouse.x
const buttonsStartX = parent.width - (parent.buttonsCount * parent.buttonsWidth)
if (localX < buttonsStartX) {
// Use the helper function to open at mouse position
contextMenu.openAtItem(widgetItem, mouse.x, mouse.y)
}
}
@@ -236,7 +239,7 @@ NBox {
Layout.preferredWidth: buttonsCount * buttonsWidth
Loader {
active: BarWidgetRegistry.widgetHasUserSettings(modelData.id)
active: root.widgetRegistry && root.widgetRegistry.widgetHasUserSettings(modelData.id)
sourceComponent: NIconButton {
icon: "settings"
tooltipText: I18n.tr("tooltips.widget-settings")
@@ -247,7 +250,7 @@ NBox {
colorBgHover: Qt.alpha(Color.mOnPrimary, Style.opacityLight)
colorFgHover: Color.mOnPrimary
onClicked: {
var component = Qt.createComponent(Qt.resolvedUrl("BarWidgetSettingsDialog.qml"))
var component = Qt.createComponent(Qt.resolvedUrl(root.settingsDialogComponent))
function instantiateAndOpen() {
var dialog = component.createObject(root, {
"widgetIndex": index,
@@ -258,19 +261,19 @@ NBox {
if (dialog) {
dialog.open()
} else {
Logger.error("BarSectionEditor", "Failed to create settings dialog instance")
Logger.error("WidgetSectionEditor", "Failed to create settings dialog instance")
}
}
if (component.status === Component.Ready) {
instantiateAndOpen()
} else if (component.status === Component.Error) {
Logger.error("BarSectionEditor", component.errorString())
Logger.error("WidgetSectionEditor", component.errorString())
} else {
component.statusChanged.connect(function () {
if (component.status === Component.Ready) {
instantiateAndOpen()
} else if (component.status === Component.Error) {
Logger.error("BarSectionEditor", component.errorString())
Logger.error("WidgetSectionEditor", component.errorString())
}
})
}
+10
View File
@@ -29,6 +29,7 @@ NPanel {
Audio,
Bar,
ColorScheme,
ControlCenter,
OSD,
Display,
Dock,
@@ -111,6 +112,10 @@ NPanel {
id: notificationsTab
NotificationsTab {}
}
Component {
id: controlCenterTab
ControlCenterTab {}
}
// Order *DOES* matter
function updateTabsModel() {
@@ -124,6 +129,11 @@ NPanel {
"label": "settings.bar.title",
"icon": "settings-bar",
"source": barTab
}, {
"id": SettingsPanel.Tab.ControlCenter,
"label": "settings.control-center.title",
"icon": "settings-bar",
"source": controlCenterTab
}, {
"id": SettingsPanel.Tab.Dock,
"label": "settings.dock.title",
+10 -4
View File
@@ -5,7 +5,7 @@ import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
import qs.Modules.Settings.Bar
import qs.Modules.Settings.Extras
ColumnLayout {
id: root
@@ -201,9 +201,11 @@ ColumnLayout {
spacing: Style.marginM * scaling
// Left Section
BarSectionEditor {
SectionEditor {
sectionName: "Left"
sectionId: "left"
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/Bar/BarWidgetSettingsDialog.qml")
widgetRegistry: BarWidgetRegistry
widgetModel: Settings.data.bar.widgets.left
availableWidgets: availableWidgets
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
@@ -216,9 +218,11 @@ ColumnLayout {
}
// Center Section
BarSectionEditor {
SectionEditor {
sectionName: "Center"
sectionId: "center"
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/Bar/BarWidgetSettingsDialog.qml")
widgetRegistry: BarWidgetRegistry
widgetModel: Settings.data.bar.widgets.center
availableWidgets: availableWidgets
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
@@ -231,9 +235,11 @@ ColumnLayout {
}
// Right Section
BarSectionEditor {
SectionEditor {
sectionName: "Right"
sectionId: "right"
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Settings/Bar/BarWidgetSettingsDialog.qml")
widgetRegistry: BarWidgetRegistry
widgetModel: Settings.data.bar.widgets.right
availableWidgets: availableWidgets
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
+140
View File
@@ -0,0 +1,140 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services
import qs.Widgets
import qs.Modules.Settings.Extras
ColumnLayout {
id: root
spacing: Style.marginL * scaling
// Handler for drag start - disables panel background clicks
function handleDragStart() {
var panel = PanelService.getPanel("settingsPanel")
if (panel && panel.disableBackgroundClick) {
panel.disableBackgroundClick()
}
}
// Handler for drag end - re-enables panel background clicks
function handleDragEnd() {
var panel = PanelService.getPanel("settingsPanel")
if (panel && panel.enableBackgroundClick) {
panel.enableBackgroundClick()
}
}
// Widgets Management Section
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NHeader {
label: I18n.tr("settings.controlCenter.widgets.section.label")
description: I18n.tr("settings.controlCenter.widgets.section.description")
}
// Bar Sections
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Style.marginM * scaling
spacing: Style.marginM * scaling
// Quick Settings
SectionEditor {
sectionName: "Quick Settings"
sectionId: "quickSettings"
settingsDialogComponent: ""
widgetRegistry: ControlCenterWidgetRegistry
widgetModel: Settings.data.controlCenter.widgets["quickSettings"]
availableWidgets: availableWidgets
enableMoveBetweenSections: false
onAddWidget: (widgetId, section) => _addWidgetToSection(widgetId, section)
onRemoveWidget: (section, index) => _removeWidgetFromSection(section, index)
onReorderWidget: (section, fromIndex, toIndex) => _reorderWidgetInSection(section, fromIndex, toIndex)
onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsInSection(section, index, settings)
onDragPotentialStarted: root.handleDragStart()
onDragPotentialEnded: root.handleDragEnd()
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// ---------------------------------
// Signal functions
// ---------------------------------
function _addWidgetToSection(widgetId, section) {
var newWidget = {
"id": widgetId
}
if (ControlCenterWidgetRegistry.widgetHasUserSettings(widgetId)) {
var metadata = ControlCenterWidgetRegistry.widgetMetadata[widgetId]
if (metadata) {
Object.keys(metadata).forEach(function (key) {
if (key !== "allowUserSettings") {
newWidget[key] = metadata[key]
}
})
}
}
Settings.data.controlCenter.widgets[section].push(newWidget)
}
function _removeWidgetFromSection(section, index) {
if (index >= 0 && index < Settings.data.controlCenter.widgets[section].length) {
var newArray = Settings.data.controlCenter.widgets[section].slice()
var removedWidgets = newArray.splice(index, 1)
Settings.data.controlCenter.widgets[section] = newArray
// Check that we still have a control center
if (removedWidgets[0].id === "ControlCenter" && BarService.lookupWidget("ControlCenter") === undefined) {
ToastService.showWarning(I18n.tr("toast.missing-control-center.label"), I18n.tr("toast.missing-control-center.description"), 12000)
}
}
}
function _reorderWidgetInSection(section, fromIndex, toIndex) {
if (fromIndex >= 0 && fromIndex < Settings.data.controlCenter.widgets[section].length && toIndex >= 0 && toIndex < Settings.data.controlCenter.widgets[section].length) {
// Create a new array to avoid modifying the original
var newArray = Settings.data.controlCenter.widgets[section].slice()
var item = newArray[fromIndex]
newArray.splice(fromIndex, 1)
newArray.splice(toIndex, 0, item)
Settings.data.controlCenter.widgets[section] = newArray
//Logger.log("BarTab", "Widget reordered. New array:", JSON.stringify(newArray))
}
}
function _updateWidgetSettingsInSection(section, index, settings) {
// Update the widget settings in the Settings data
Settings.data.controlCenter.widgets[section][index] = settings
//Logger.log("BarTab", `Updated widget settings for ${settings.id} in ${section} section`)
}
// Base list model for all combo boxes
ListModel {
id: availableWidgets
}
Component.onCompleted: {
// Fill out availableWidgets ListModel
availableWidgets.clear()
ControlCenterWidgetRegistry.getAvailableWidgets().forEach(entry => {
availableWidgets.append({
"key": entry,
"name": entry
})
})
}
}