From 679224e1d95ce1532fc0f5c64149e906d56dcd8f Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Tue, 14 Oct 2025 03:40:51 +0800 Subject: [PATCH] feat(settings): Add usage badges to widget selector Adds a visual indicator to the "Add Widget" dropdown in the Bar settings panel to show which widgets are already in use and where. - A small text badge ("L", "C", "R") now appears next to any widget that is already on a panel. - The badges are reactive and update automatically when widgets are added or removed. - This helps prevent accidental duplicate additions and makes widget management easier. --- Modules/Settings/Extras/SectionEditor.qml | 1 + Modules/Settings/Tabs/BarTab.qml | 50 +++++++++--- Services/BarService.qml | 4 + Widgets/NSearchableComboBox.qml | 97 ++++++++++++++++------- 4 files changed, 112 insertions(+), 40 deletions(-) diff --git a/Modules/Settings/Extras/SectionEditor.qml b/Modules/Settings/Extras/SectionEditor.qml index 58989aa4b..99c2a6b10 100644 --- a/Modules/Settings/Extras/SectionEditor.qml +++ b/Modules/Settings/Extras/SectionEditor.qml @@ -83,6 +83,7 @@ NBox { Item { Layout.fillWidth: true } + NSearchableComboBox { id: comboBox model: availableWidgets diff --git a/Modules/Settings/Tabs/BarTab.qml b/Modules/Settings/Tabs/BarTab.qml index 47207f0d5..5fc328785 100644 --- a/Modules/Settings/Tabs/BarTab.qml +++ b/Modules/Settings/Tabs/BarTab.qml @@ -300,9 +300,7 @@ ColumnLayout { Layout.bottomMargin: Style.marginXL } - // --------------------------------- // Signal functions - // --------------------------------- function _addWidgetToSection(widgetId, section) { var newWidget = { "id": widgetId @@ -372,19 +370,51 @@ ColumnLayout { } } + // Data model functions + function getWidgetLocations(widgetId) { + if (!BarService) + return [] + const instances = BarService.getAllRegisteredWidgets() + const locations = {} + for (var i = 0; i < instances.length; i++) { + if (instances[i].widgetId === widgetId) { + const section = instances[i].section + if (section === "left") + locations["L"] = true + else if (section === "center") + locations["C"] = true + else if (section === "right") + locations["R"] = true + } + } + return Object.keys(locations).join('') + } + + function updateAvailableWidgetsModel() { + availableWidgets.clear() + const widgets = BarWidgetRegistry.getAvailableWidgets() + widgets.forEach(entry => { + availableWidgets.append({ + "key": entry, + "name": entry, + "badgeLocations": getWidgetLocations(entry) + }) + }) + } + // Base list model for all combo boxes ListModel { id: availableWidgets } Component.onCompleted: { - // Fill out availableWidgets ListModel - availableWidgets.clear() - BarWidgetRegistry.getAvailableWidgets().forEach(entry => { - availableWidgets.append({ - "key": entry, - "name": entry - }) - }) + updateAvailableWidgetsModel() + } + + Connections { + target: BarService + function onActiveWidgetsChanged() { + updateAvailableWidgetsModel() + } } } diff --git a/Services/BarService.qml b/Services/BarService.qml index 9a28b5504..05c66c1e1 100644 --- a/Services/BarService.qml +++ b/Services/BarService.qml @@ -15,6 +15,8 @@ Singleton { // Key format: "screenName|section|widgetId|index" property var widgetInstances: ({}) + signal activeWidgetsChanged() + signal barReadyChanged(string screenName) // Simple timer that run once when the widget structure has changed @@ -68,6 +70,7 @@ Singleton { timerCheckVisualizer.restart() Logger.log("BarService", "Registered widget:", key) + root.activeWidgetsChanged() } // Unregister a widget instance @@ -75,6 +78,7 @@ Singleton { const key = [screenName, section, widgetId, index].join("|") delete widgetInstances[key] Logger.log("BarService", "Unregistered widget:", key) + root.activeWidgetsChanged() } // Lookup a specific widget instance (returns the actual QML instance) diff --git a/Widgets/NSearchableComboBox.qml b/Widgets/NSearchableComboBox.qml index 462d5a6b6..4e7baf5ef 100644 --- a/Widgets/NSearchableComboBox.qml +++ b/Widgets/NSearchableComboBox.qml @@ -20,6 +20,7 @@ RowLayout { property string currentKey: "" property string placeholder: "" property string searchPlaceholder: I18n.tr("placeholders.search") + property Component delegate: null readonly property real preferredHeight: Style.baseWidgetSize * 1.1 @@ -187,43 +188,79 @@ RowLayout { horizontalPolicy: ScrollBar.AlwaysOff verticalPolicy: ScrollBar.AsNeeded - delegate: ItemDelegate { - width: listView.width - hoverEnabled: true - highlighted: ListView.view.currentIndex === index + delegate: root.delegate ? root.delegate : defaultDelegate - onHoveredChanged: { - if (hovered) { - ListView.view.currentIndex = index + Component { + id: defaultDelegate + ItemDelegate { + id: delegateRoot + width: listView.width + hoverEnabled: true + highlighted: ListView.view.currentIndex === index + + onHoveredChanged: { + if (hovered) { + ListView.view.currentIndex = index + } } - } - onClicked: { - root.selected(filteredModel.get(index).key) - combo.currentIndex = root.findIndexByKeyInFiltered(filteredModel.get(index).key) - combo.popup.close() - } + onClicked: { + root.selected(filteredModel.get(index).key) + combo.currentIndex = root.findIndexByKeyInFiltered(filteredModel.get(index).key) + combo.popup.close() + } - contentItem: NText { - text: name - pointSize: Style.fontSizeM - color: highlighted ? Color.mOnTertiary : Color.mOnSurface - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight - Behavior on color { - ColorAnimation { - duration: Style.animationFast + contentItem: RowLayout { + width: parent.width + spacing: Style.marginM + + NText { + text: name + pointSize: Style.fontSizeM + color: highlighted ? Color.mOnTertiary : Color.mOnSurface + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + Layout.fillWidth: true + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + RowLayout { + spacing: Style.marginS + Layout.alignment: Qt.AlignRight + + Repeater { + model: badgeLocations + + delegate: NBox { + width: Style.baseWidgetSize * 0.7 + height: Style.baseWidgetSize * 0.7 + color: "transparent" + radius: Style.radiusS + border.width: 0 + + NText { + anchors.centerIn: parent + text: modelData + pointSize: Style.fontSizeXXS + font.weight: Style.fontWeightBold + color: highlighted ? Color.mOnTertiary : Color.mOnSurface + } + } } } } - - background: Rectangle { - width: listView.width - color: highlighted ? Color.mTertiary : Color.transparent - radius: Style.radiusS - Behavior on color { - ColorAnimation { - duration: Style.animationFast + background: Rectangle { + width: listView.width + color: highlighted ? Color.mTertiary : Color.transparent + radius: Style.radiusS + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } } } }