From 8e562e070120ed64710da9ef95048c01d5c355ca Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 22 Aug 2025 16:37:45 +0200 Subject: [PATCH 01/16] WIP - modular bar (need to fix brightness & tray) --- Commons/Settings.qml | 9 + Modules/Bar/Bar.qml | 115 +-- Modules/Bar/{ => Widgets}/ActiveWindow.qml | 0 Modules/Bar/{ => Widgets}/Battery.qml | 0 Modules/Bar/{ => Widgets}/Bluetooth.qml | 2 - Modules/Bar/{ => Widgets}/BluetoothPanel.qml | 2 +- Modules/Bar/{ => Widgets}/Brightness.qml | 2 +- Modules/Bar/{ => Widgets}/Clock.qml | 0 Modules/Bar/{ => Widgets}/MediaMini.qml | 0 .../Bar/{ => Widgets}/NotificationHistory.qml | 0 .../{ => Widgets}/ScreenRecorderIndicator.qml | 0 Modules/Bar/{ => Widgets}/SidePanelToggle.qml | 0 Modules/Bar/{ => Widgets}/SystemMonitor.qml | 0 Modules/Bar/{ => Widgets}/Tray.qml | 0 Modules/Bar/{ => Widgets}/TrayMenu.qml | 2 +- Modules/Bar/{ => Widgets}/Volume.qml | 0 Modules/Bar/{ => Widgets}/WiFi.qml | 3 - Modules/Bar/{ => Widgets}/WiFiPanel.qml | 2 +- Modules/Bar/{ => Widgets}/Workspace.qml | 0 Modules/SettingsPanel/SettingsPanel.qml | 5 +- Modules/SettingsPanel/Tabs/AboutTab.qml | 4 +- Modules/SettingsPanel/Tabs/BarTab.qml | 676 ++++++++++++++++-- Modules/SettingsPanel/Tabs/BrightnessTab.qml | 31 +- Modules/SettingsPanel/Tabs/ColorSchemeTab.qml | 24 +- Modules/SettingsPanel/Tabs/GeneralTab.qml | 7 + Modules/SettingsPanel/Tabs/LauncherTab.qml | 4 +- Modules/SettingsPanel/Tabs/NetworkTab.qml | 7 + .../SettingsPanel/Tabs/ScreenRecorderTab.qml | 8 + Modules/SettingsPanel/Tabs/TimeWeatherTab.qml | 8 + .../Tabs/WallpaperSelectorTab.qml | 2 +- Modules/SettingsPanel/Tabs/WallpaperTab.qml | 8 + 31 files changed, 785 insertions(+), 136 deletions(-) rename Modules/Bar/{ => Widgets}/ActiveWindow.qml (100%) rename Modules/Bar/{ => Widgets}/Battery.qml (100%) rename Modules/Bar/{ => Widgets}/Bluetooth.qml (87%) rename Modules/Bar/{ => Widgets}/BluetoothPanel.qml (99%) rename Modules/Bar/{ => Widgets}/Brightness.qml (96%) rename Modules/Bar/{ => Widgets}/Clock.qml (100%) rename Modules/Bar/{ => Widgets}/MediaMini.qml (100%) rename Modules/Bar/{ => Widgets}/NotificationHistory.qml (100%) rename Modules/Bar/{ => Widgets}/ScreenRecorderIndicator.qml (100%) rename Modules/Bar/{ => Widgets}/SidePanelToggle.qml (100%) rename Modules/Bar/{ => Widgets}/SystemMonitor.qml (100%) rename Modules/Bar/{ => Widgets}/Tray.qml (100%) rename Modules/Bar/{ => Widgets}/TrayMenu.qml (99%) rename Modules/Bar/{ => Widgets}/Volume.qml (100%) rename Modules/Bar/{ => Widgets}/WiFi.qml (89%) rename Modules/Bar/{ => Widgets}/WiFiPanel.qml (99%) rename Modules/Bar/{ => Widgets}/Workspace.qml (100%) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 23fa432..0ffe01c 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -130,6 +130,15 @@ Singleton { property bool alwaysShowBatteryPercentage: false property real backgroundOpacity: 1.0 property list monitors: [] + + // 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", "Clock", "SidePanelToggle"] + } } // general diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 6cc30a7..61e7940 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -47,7 +47,7 @@ Variants { layer.enabled: true } - // Left + // Left Section - Dynamic Widgets Row { id: leftSection @@ -57,14 +57,25 @@ Variants { anchors.verticalCenter: parent.verticalCenter spacing: Style.marginS * scaling - SystemMonitor {} - - ActiveWindow {} - - MediaMini {} + Repeater { + model: Settings.data.bar.widgets.left + delegate: Loader { + id: widgetLoader + sourceComponent: getWidgetComponent(modelData) + active: true + anchors.verticalCenter: parent.verticalCenter + onStatusChanged: { + if (status === Loader.Error) { + console.warn(`Failed to load widget: ${modelData}`) + } else if (status === Loader.Ready) { + console.log(`Successfully loaded widget: ${modelData}`) + } + } + } + } } - // Center + // Center Section - Dynamic Widgets Row { id: centerSection @@ -73,10 +84,25 @@ Variants { anchors.verticalCenter: parent.verticalCenter spacing: Style.marginS * scaling - Workspace {} + Repeater { + model: Settings.data.bar.widgets.center + delegate: Loader { + id: widgetLoader + sourceComponent: getWidgetComponent(modelData) + active: true + anchors.verticalCenter: parent.verticalCenter + onStatusChanged: { + if (status === Loader.Error) { + console.warn(`Failed to load widget: ${modelData}`) + } else if (status === Loader.Ready) { + console.log(`Successfully loaded widget: ${modelData}`) + } + } + } + } } - // Right + // Right Section - Dynamic Widgets Row { id: rightSection @@ -86,44 +112,43 @@ Variants { anchors.verticalCenter: bar.verticalCenter spacing: Style.marginS * scaling - ScreenRecorderIndicator { - anchors.verticalCenter: parent.verticalCenter + Repeater { + model: Settings.data.bar.widgets.right + delegate: Loader { + id: widgetLoader + sourceComponent: getWidgetComponent(modelData) + active: true + anchors.verticalCenter: parent.verticalCenter + onStatusChanged: { + if (status === Loader.Error) { + console.warn(`Failed to load widget: ${modelData}`) + } else if (status === Loader.Ready) { + console.log(`Successfully loaded widget: ${modelData}`) + } + } + } } - - Tray { - anchors.verticalCenter: parent.verticalCenter - } - - NotificationHistory { - anchors.verticalCenter: parent.verticalCenter - } - - WiFi { - anchors.verticalCenter: parent.verticalCenter - } - - Bluetooth { - anchors.verticalCenter: parent.verticalCenter - } - - Battery { - anchors.verticalCenter: parent.verticalCenter - } - - Volume { - anchors.verticalCenter: parent.verticalCenter - } - - Brightness { - anchors.verticalCenter: parent.verticalCenter - } - - Clock { - anchors.verticalCenter: parent.verticalCenter - } - - SidePanelToggle {} } } + + // Auto-discover widget components + function getWidgetComponent(widgetName) { + if (!widgetName || widgetName.trim() === "") { + return null + } + + // Try to load the widget directly from file + const component = Qt.createComponent(`../Bar/Widgets/${widgetName}.qml`) + if (component.status === Component.Ready) { + return component + } + + console.warn(`Failed to load widget: ${widgetName}.qml`) + return null + } + + + + } } diff --git a/Modules/Bar/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml similarity index 100% rename from Modules/Bar/ActiveWindow.qml rename to Modules/Bar/Widgets/ActiveWindow.qml diff --git a/Modules/Bar/Battery.qml b/Modules/Bar/Widgets/Battery.qml similarity index 100% rename from Modules/Bar/Battery.qml rename to Modules/Bar/Widgets/Battery.qml diff --git a/Modules/Bar/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml similarity index 87% rename from Modules/Bar/Bluetooth.qml rename to Modules/Bar/Widgets/Bluetooth.qml index adc76e0..09c5c60 100644 --- a/Modules/Bar/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -10,9 +10,7 @@ import qs.Widgets NIconButton { id: root - readonly property bool bluetoothEnabled: Settings.data.network.bluetoothEnabled sizeMultiplier: 0.8 - visible: bluetoothEnabled colorBg: Color.mSurfaceVariant colorFg: Color.mOnSurface diff --git a/Modules/Bar/BluetoothPanel.qml b/Modules/Bar/Widgets/BluetoothPanel.qml similarity index 99% rename from Modules/Bar/BluetoothPanel.qml rename to Modules/Bar/Widgets/BluetoothPanel.qml index b583897..eeb4c7f 100644 --- a/Modules/Bar/BluetoothPanel.qml +++ b/Modules/Bar/Widgets/BluetoothPanel.qml @@ -118,7 +118,7 @@ NPanel { radius: Style.radiusM * scaling color: { if (availableDeviceArea.containsMouse && !isBusy) - return Color.mSecondary + return Color.mTertiary if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting) return Color.mPrimary diff --git a/Modules/Bar/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml similarity index 96% rename from Modules/Bar/Brightness.qml rename to Modules/Bar/Widgets/Brightness.qml index 39c0fb8..82bacb8 100644 --- a/Modules/Bar/Brightness.qml +++ b/Modules/Bar/Widgets/Brightness.qml @@ -10,7 +10,7 @@ Item { width: pill.width height: pill.height - visible: Settings.data.bar.showBrightness && firstBrightnessReceived && getMonitor() !== null + visible: firstBrightnessReceived && getMonitor() !== null // Used to avoid opening the pill on Quickshell startup property bool firstBrightnessReceived: false diff --git a/Modules/Bar/Clock.qml b/Modules/Bar/Widgets/Clock.qml similarity index 100% rename from Modules/Bar/Clock.qml rename to Modules/Bar/Widgets/Clock.qml diff --git a/Modules/Bar/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml similarity index 100% rename from Modules/Bar/MediaMini.qml rename to Modules/Bar/Widgets/MediaMini.qml diff --git a/Modules/Bar/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml similarity index 100% rename from Modules/Bar/NotificationHistory.qml rename to Modules/Bar/Widgets/NotificationHistory.qml diff --git a/Modules/Bar/ScreenRecorderIndicator.qml b/Modules/Bar/Widgets/ScreenRecorderIndicator.qml similarity index 100% rename from Modules/Bar/ScreenRecorderIndicator.qml rename to Modules/Bar/Widgets/ScreenRecorderIndicator.qml diff --git a/Modules/Bar/SidePanelToggle.qml b/Modules/Bar/Widgets/SidePanelToggle.qml similarity index 100% rename from Modules/Bar/SidePanelToggle.qml rename to Modules/Bar/Widgets/SidePanelToggle.qml diff --git a/Modules/Bar/SystemMonitor.qml b/Modules/Bar/Widgets/SystemMonitor.qml similarity index 100% rename from Modules/Bar/SystemMonitor.qml rename to Modules/Bar/Widgets/SystemMonitor.qml diff --git a/Modules/Bar/Tray.qml b/Modules/Bar/Widgets/Tray.qml similarity index 100% rename from Modules/Bar/Tray.qml rename to Modules/Bar/Widgets/Tray.qml diff --git a/Modules/Bar/TrayMenu.qml b/Modules/Bar/Widgets/TrayMenu.qml similarity index 99% rename from Modules/Bar/TrayMenu.qml rename to Modules/Bar/Widgets/TrayMenu.qml index 699d8d1..a3a24fd 100644 --- a/Modules/Bar/TrayMenu.qml +++ b/Modules/Bar/Widgets/TrayMenu.qml @@ -134,7 +134,7 @@ PopupWindow { Rectangle { anchors.fill: parent - color: mouseArea.containsMouse ? Color.mSecondary : Color.transparent + color: mouseArea.containsMouse ? Color.mTertiary : Color.transparent radius: Style.radiusS * scaling visible: !(modelData?.isSeparator ?? false) diff --git a/Modules/Bar/Volume.qml b/Modules/Bar/Widgets/Volume.qml similarity index 100% rename from Modules/Bar/Volume.qml rename to Modules/Bar/Widgets/Volume.qml diff --git a/Modules/Bar/WiFi.qml b/Modules/Bar/Widgets/WiFi.qml similarity index 89% rename from Modules/Bar/WiFi.qml rename to Modules/Bar/Widgets/WiFi.qml index 6b9b921..cdae9be 100644 --- a/Modules/Bar/WiFi.qml +++ b/Modules/Bar/Widgets/WiFi.qml @@ -10,10 +10,7 @@ import qs.Widgets NIconButton { id: root - readonly property bool wifiEnabled: Settings.data.network.wifiEnabled - sizeMultiplier: 0.8 - visible: wifiEnabled colorBg: Color.mSurfaceVariant colorFg: Color.mOnSurface diff --git a/Modules/Bar/WiFiPanel.qml b/Modules/Bar/Widgets/WiFiPanel.qml similarity index 99% rename from Modules/Bar/WiFiPanel.qml rename to Modules/Bar/Widgets/WiFiPanel.qml index ce5acef..8d88cd9 100644 --- a/Modules/Bar/WiFiPanel.qml +++ b/Modules/Bar/Widgets/WiFiPanel.qml @@ -147,7 +147,7 @@ NPanel { Layout.fillWidth: true Layout.preferredHeight: Style.baseWidgetSize * 1.5 * scaling radius: Style.radiusS * scaling - color: modelData.connected ? Color.mPrimary : (networkMouseArea.containsMouse ? Color.mSecondary : Color.transparent) + color: modelData.connected ? Color.mPrimary : (networkMouseArea.containsMouse ? Color.mTertiary : Color.transparent) RowLayout { anchors.fill: parent diff --git a/Modules/Bar/Workspace.qml b/Modules/Bar/Widgets/Workspace.qml similarity index 100% rename from Modules/Bar/Workspace.qml rename to Modules/Bar/Widgets/Workspace.qml diff --git a/Modules/SettingsPanel/SettingsPanel.qml b/Modules/SettingsPanel/SettingsPanel.qml index 0224d89..b5667f9 100644 --- a/Modules/SettingsPanel/SettingsPanel.qml +++ b/Modules/SettingsPanel/SettingsPanel.qml @@ -47,6 +47,7 @@ NPanel { id: barTab Tabs.BarTab {} } + Component { id: audioTab Tabs.AudioTab {} @@ -202,7 +203,7 @@ NPanel { width: parent.width height: 32 * scaling radius: Style.radiusS * scaling - color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mSecondary : Color.transparent) + color: selected ? Color.mPrimary : (tabItem.hovering ? Color.mTertiary : Color.transparent) readonly property bool selected: index === currentTabIndex property bool hovering: false property color tabTextColor: selected ? Color.mOnPrimary : (tabItem.hovering ? Color.mOnTertiary : Color.mOnSurface) @@ -265,7 +266,7 @@ NPanel { // Tab label on the main right side NText { text: root.tabsModel[currentTabIndex].label - font.pointSize: Style.fontSizeXL * scaling + font.pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mPrimary Layout.fillWidth: true diff --git a/Modules/SettingsPanel/Tabs/AboutTab.qml b/Modules/SettingsPanel/Tabs/AboutTab.qml index a7b7b3d..405ed1b 100644 --- a/Modules/SettingsPanel/Tabs/AboutTab.qml +++ b/Modules/SettingsPanel/Tabs/AboutTab.qml @@ -199,7 +199,7 @@ ColumnLayout { width: contributorsGrid.cellWidth - Style.marginL * scaling height: contributorsGrid.cellHeight - Style.marginXS * scaling radius: Style.radiusL * scaling - color: contributorArea.containsMouse ? Color.mSecondary : Color.transparent + color: contributorArea.containsMouse ? Color.mTertiary : Color.transparent RowLayout { anchors.fill: parent @@ -217,7 +217,7 @@ ColumnLayout { anchors.margins: Style.marginXS * scaling fallbackIcon: "person" borderColor: Color.mPrimary - borderWidth: Math.max(1, Style.borderL * scaling) + borderWidth: Math.max(1, Style.borderM * scaling) imageRadius: width * 0.5 } } diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index fe5cb42..d03c333 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -33,6 +33,13 @@ ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true + NText { + text: "Bar & Widgets" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true @@ -71,15 +78,6 @@ ColumnLayout { } } - NToggle { - label: "Show Active Window" - description: "Display the title of the currently focused window." - checked: Settings.data.bar.showActiveWindow - onToggled: checked => { - Settings.data.bar.showActiveWindow = checked - } - } - NToggle { label: "Show Active Window's Icon" description: "Display the app icon next to the title of the currently focused window." @@ -89,42 +87,6 @@ ColumnLayout { } } - NToggle { - label: "Show System Info" - description: "Display system statistics (CPU, RAM, Temperature)." - checked: Settings.data.bar.showSystemInfo - onToggled: checked => { - Settings.data.bar.showSystemInfo = checked - } - } - - NToggle { - label: "Show Media" - description: "Display media controls and information." - checked: Settings.data.bar.showMedia - onToggled: checked => { - Settings.data.bar.showMedia = checked - } - } - - NToggle { - label: "Show Notifications History" - description: "Display a shortcut to the notifications history." - checked: Settings.data.bar.showNotificationsHistory - onToggled: checked => { - Settings.data.bar.showNotificationsHistory = checked - } - } - - NToggle { - label: "Show Applications Tray" - description: "Display the applications tray." - checked: Settings.data.bar.showTray - onToggled: checked => { - Settings.data.bar.showTray = checked - } - } - NToggle { label: "Show Battery Percentage" description: "Show battery percentage at all times." @@ -172,7 +134,631 @@ ColumnLayout { } } } + + // Widget Management Section + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NText { + text: "Widget Management" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: "Configure which widgets appear in each section of the bar. Use the arrow buttons to reorder widgets, or the add/remove buttons to manage them." + font.pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + // Bar Sections + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: Style.marginM * scaling + + // Left Section + NCard { + Layout.fillWidth: true + Layout.minimumHeight: { + var widgetCount = Settings.data.bar.widgets.left.length + if (widgetCount === 0) return 120 * scaling + + var availableWidth = scrollView.availableWidth - (Style.marginM * scaling * 2) // Card margins + var avgWidgetWidth = 150 * scaling // Estimated widget width including spacing + var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) + var rows = Math.ceil(widgetCount / widgetsPerRow) + + // Header (40) + spacing (16) + (rows * widget height) + (rows-1 * spacing) + bottom margin (16) + return (40 + 16 + (rows * 48) + ((rows - 1) * Style.marginS) + 16) * scaling + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginM * scaling + spacing: Style.marginM * scaling + + RowLayout { + Layout.fillWidth: true + + NText { + text: "Left Section" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + Item { Layout.fillWidth: true } + + Rectangle { + width: 32 * scaling + height: 32 * scaling + radius: width * 0.5 + color: Color.mPrimary + border.color: Color.mPrimaryContainer + border.width: 2 * scaling + + NIcon { + anchors.centerIn: parent + text: "add" + color: Color.mOnPrimary + font.pointSize: Style.fontSizeM * scaling + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + addWidgetDialog.show("left") + } + } + } + } + + Flow { + id: leftWidgetsFlow + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 52 * scaling + spacing: Style.marginS * scaling + flow: Flow.LeftToRight + + Repeater { + model: Settings.data.bar.widgets.left + delegate: Rectangle { + width: widgetContent.implicitWidth + 16 * scaling + height: 48 * scaling + radius: Style.radiusS * scaling + color: Color.mPrimary + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + + RowLayout { + id: widgetContent + anchors.centerIn: parent + spacing: Style.marginXS * scaling + + NIconButton { + icon: "chevron_left" + size: 20 * scaling + colorBg: Color.applyOpacity(Color.mOnPrimary, "20") + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + enabled: index > 0 + onClicked: { + if (index > 0) { + reorderWidgetInSection("left", index, index - 1) + } + } + } + + NText { + text: modelData + font.pointSize: Style.fontSizeS * scaling + color: Color.mOnPrimary + horizontalAlignment: Text.AlignHCenter + } + + NIconButton { + icon: "chevron_right" + size: 20 * scaling + colorBg: Color.applyOpacity(Color.mOnPrimary, "20") + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + enabled: index < Settings.data.bar.widgets.left.length - 1 + onClicked: { + if (index < Settings.data.bar.widgets.left.length - 1) { + reorderWidgetInSection("left", index, index + 1) + } + } + } + + NIconButton { + icon: "close" + size: 20 * scaling + colorBg: Color.applyOpacity(Color.mOnPrimary, "20") + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + onClicked: { + removeWidgetFromSection("left", index) + } + } + } + } + } + } + } + } + + // Center Section + NCard { + Layout.fillWidth: true + Layout.minimumHeight: { + var widgetCount = Settings.data.bar.widgets.center.length + if (widgetCount === 0) return 120 * scaling + + var availableWidth = scrollView.availableWidth - (Style.marginM * scaling * 2) // Card margins + var avgWidgetWidth = 150 * scaling // Estimated widget width including spacing + var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) + var rows = Math.ceil(widgetCount / widgetsPerRow) + + // Header (40) + spacing (16) + (rows * widget height) + (rows-1 * spacing) + bottom margin (16) + return (40 + 16 + (rows * 48) + ((rows - 1) * Style.marginS) + 16) * scaling + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginM * scaling + spacing: Style.marginM * scaling + + RowLayout { + Layout.fillWidth: true + + NText { + text: "Center Section" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + Item { Layout.fillWidth: true } + + Rectangle { + width: 32 * scaling + height: 32 * scaling + radius: width * 0.5 + color: Color.mPrimary + border.color: Color.mPrimaryContainer + border.width: 2 * scaling + + NIcon { + anchors.centerIn: parent + text: "add" + color: Color.mOnPrimary + font.pointSize: Style.fontSizeM * scaling + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + addWidgetDialog.show("center") + } + } + } + } + + Flow { + id: centerWidgetsFlow + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 52 * scaling + spacing: Style.marginS * scaling + flow: Flow.LeftToRight + + Repeater { + model: Settings.data.bar.widgets.center + delegate: Rectangle { + width: widgetContent.implicitWidth + 16 * scaling + height: 48 * scaling + radius: Style.radiusS * scaling + color: Color.mPrimary + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + + RowLayout { + id: widgetContent + anchors.centerIn: parent + spacing: Style.marginXS * scaling + + NIconButton { + icon: "chevron_left" + size: 20 * scaling + colorBg: Color.applyOpacity(Color.mOnPrimary, "20") + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + enabled: index > 0 + onClicked: { + if (index > 0) { + reorderWidgetInSection("center", index, index - 1) + } + } + } + + NText { + text: modelData + font.pointSize: Style.fontSizeS * scaling + color: Color.mOnPrimary + horizontalAlignment: Text.AlignHCenter + } + + NIconButton { + icon: "chevron_right" + size: 20 * scaling + colorBg: Color.applyOpacity(Color.mOnPrimary, "20") + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + enabled: index < Settings.data.bar.widgets.center.length - 1 + onClicked: { + if (index < Settings.data.bar.widgets.center.length - 1) { + reorderWidgetInSection("center", index, index + 1) + } + } + } + + NIconButton { + icon: "close" + size: 20 * scaling + colorBg: Color.applyOpacity(Color.mOnPrimary, "20") + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + onClicked: { + removeWidgetFromSection("center", index) + } + } + } + } + } + } + } + } + + // Right Section + NCard { + Layout.fillWidth: true + Layout.minimumHeight: { + var widgetCount = Settings.data.bar.widgets.right.length + if (widgetCount === 0) return 120 * scaling + + var availableWidth = scrollView.availableWidth - (Style.marginM * scaling * 2) // Card margins + var avgWidgetWidth = 150 * scaling // Estimated widget width including spacing + var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) + var rows = Math.ceil(widgetCount / widgetsPerRow) + + // Header (40) + spacing (16) + (rows * widget height) + (rows-1 * spacing) + bottom margin (16) + return (40 + 16 + (rows * 48) + ((rows - 1) * Style.marginS) + 16) * scaling + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginM * scaling + spacing: Style.marginM * scaling + + RowLayout { + Layout.fillWidth: true + + NText { + text: "Right Section" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + Item { Layout.fillWidth: true } + + Rectangle { + width: 32 * scaling + height: 32 * scaling + radius: width * 0.5 + color: Color.mPrimary + border.color: Color.mPrimaryContainer + border.width: 2 * scaling + + NIcon { + anchors.centerIn: parent + text: "add" + color: Color.mOnPrimary + font.pointSize: Style.fontSizeM * scaling + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + addWidgetDialog.show("right") + } + } + } + } + + Flow { + id: rightWidgetsFlow + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 52 * scaling + spacing: Style.marginS * scaling + flow: Flow.LeftToRight + + Repeater { + model: Settings.data.bar.widgets.right + delegate: Rectangle { + width: widgetContent.implicitWidth + 16 * scaling + height: 48 * scaling + radius: Style.radiusS * scaling + color: Color.mPrimary + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + + RowLayout { + id: widgetContent + anchors.centerIn: parent + spacing: Style.marginXS * scaling + + NIconButton { + icon: "chevron_left" + size: 20 * scaling + colorBg: Color.applyOpacity(Color.mOnPrimary, "20") + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + enabled: index > 0 + onClicked: { + if (index > 0) { + reorderWidgetInSection("right", index, index - 1) + } + } + } + + NText { + text: modelData + font.pointSize: Style.fontSizeS * scaling + color: Color.mOnPrimary + horizontalAlignment: Text.AlignHCenter + } + + NIconButton { + icon: "chevron_right" + size: 20 * scaling + colorBg: Color.applyOpacity(Color.mOnPrimary, "20") + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + enabled: index < Settings.data.bar.widgets.right.length - 1 + onClicked: { + if (index < Settings.data.bar.widgets.right.length - 1) { + reorderWidgetInSection("right", index, index + 1) + } + } + } + + NIconButton { + icon: "close" + size: 20 * scaling + colorBg: Color.applyOpacity(Color.mOnPrimary, "20") + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + onClicked: { + removeWidgetFromSection("right", index) + } + } + } + } + } + } + } + } + } + } } } } + + // Add Widget Dialog + Rectangle { + id: addWidgetDialog + anchors.fill: parent + color: Color.applyOpacity(Color.mShadow, "80") + visible: false + z: 1000 + + property string targetSection: "" + + function show(section) { + targetSection = section + visible = true + } + + function hide() { + visible = false + targetSection = "" + } + + MouseArea { + anchors.fill: parent + onClicked: addWidgetDialog.hide() + } + + Rectangle { + anchors.centerIn: parent + width: 400 * scaling + height: 500 * scaling + radius: Style.radiusL * scaling + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL * scaling + spacing: Style.marginM * scaling + + NText { + text: "Add Widget to " + (addWidgetDialog.targetSection === "left" ? "Left" : + addWidgetDialog.targetSection === "center" ? "Center" : "Right") + " Section" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + spacing: Style.marginXS * scaling + + model: ListModel { + ListElement { name: "SystemMonitor"; icon: "memory"; description: "System statistics" } + ListElement { name: "ActiveWindow"; icon: "web_asset"; description: "Active window title" } + ListElement { name: "MediaMini"; icon: "music_note"; description: "Media controls" } + ListElement { name: "Workspace"; icon: "dashboard"; description: "Workspace switcher" } + ListElement { name: "ScreenRecorderIndicator"; icon: "videocam"; description: "Recording indicator" } + ListElement { name: "Tray"; icon: "apps"; description: "System tray" } + ListElement { name: "NotificationHistory"; icon: "notifications"; description: "Notification history" } + ListElement { name: "WiFi"; icon: "wifi"; description: "WiFi status" } + ListElement { name: "Bluetooth"; icon: "bluetooth"; description: "Bluetooth status" } + ListElement { name: "Battery"; icon: "battery_full"; description: "Battery status" } + ListElement { name: "Volume"; icon: "volume_up"; description: "Volume control" } + ListElement { name: "Brightness"; icon: "brightness_6"; description: "Brightness control" } + ListElement { name: "Clock"; icon: "schedule"; description: "Clock" } + ListElement { name: "SidePanelToggle"; icon: "widgets"; description: "Side panel toggle" } + } + + delegate: Rectangle { + width: ListView.view.width + height: 48 * scaling + radius: Style.radiusS * scaling + color: mouseArea.containsMouse ? Color.mTertiary : Color.mSurfaceVariant + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginS * scaling + spacing: Style.marginS * scaling + + NIcon { + text: model.icon + color: Color.mOnSurface + font.pointSize: Style.fontSizeM * scaling + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + NText { + text: model.name + font.pointSize: Style.fontSizeS * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: model.description + font.pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + } + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + addWidgetToSection(model.name, addWidgetDialog.targetSection) + addWidgetDialog.hide() + } + } + } + } + + RowLayout { + Layout.fillWidth: true + + Item { Layout.fillWidth: true } + + NIconButton { + icon: "close" + size: 20 * scaling + color: Color.mOnSurface + onClicked: addWidgetDialog.hide() + } + } + } + } + } + + // Helper functions + function addWidgetToSection(widgetName, section) { + console.log("Adding widget", widgetName, "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) + console.log("Widget added. New array:", JSON.stringify(newArray)) + + // Assign the new array + Settings.data.bar.widgets[section] = newArray + } + } + + function removeWidgetFromSection(section, index) { + console.log("Removing widget from section", section, "at index", index) + var sectionArray = Settings.data.bar.widgets[section] + if (sectionArray && index >= 0 && index < sectionArray.length) { + // Create a new array to avoid modifying the original + var newArray = sectionArray.slice() + newArray.splice(index, 1) + console.log("Widget removed. New array:", JSON.stringify(newArray)) + + // Assign the new array + Settings.data.bar.widgets[section] = newArray + } + } + + function reorderWidgetInSection(section, fromIndex, toIndex) { + console.log("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) { + + // Create a new array to avoid modifying the original + var newArray = sectionArray.slice() + var item = newArray[fromIndex] + newArray.splice(fromIndex, 1) + newArray.splice(toIndex, 0, item) + console.log("Widget reordered. New array:", JSON.stringify(newArray)) + + // Assign the new array + Settings.data.bar.widgets[section] = newArray + } + } } diff --git a/Modules/SettingsPanel/Tabs/BrightnessTab.qml b/Modules/SettingsPanel/Tabs/BrightnessTab.qml index e4758c3..3b61e7b 100644 --- a/Modules/SettingsPanel/Tabs/BrightnessTab.qml +++ b/Modules/SettingsPanel/Tabs/BrightnessTab.qml @@ -6,33 +6,34 @@ import qs.Commons import qs.Services import qs.Widgets -ColumnLayout { - id: root - spacing: 0 - +Item { + property real scaling: 1 readonly property string tabIcon: "brightness_6" readonly property string tabLabel: "Brightness" + Layout.fillWidth: true + Layout.fillHeight: true ScrollView { - id: scrollView - Layout.fillWidth: true - Layout.fillHeight: true - padding: Style.marginM * scaling + anchors.fill: parent clip: true - - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.vertical.policy: ScrollBar.AsNeeded + ScrollBar.horizontal.policy: ScrollBar.AsNeeded + contentWidth: parent.width ColumnLayout { - width: scrollView.availableWidth - spacing: 0 - + width: parent.width ColumnLayout { - - width: scrollView.availableWidth spacing: Style.marginL * scaling + Layout.margins: Style.marginL * scaling Layout.fillWidth: true + NText { + text: "Brightness Settings" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + // Bar Visibility Section NToggle { label: "Show Bar Icon" diff --git a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml index 80da36a..c7343b6 100644 --- a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml +++ b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml @@ -163,28 +163,22 @@ ColumnLayout { Layout.bottomMargin: Style.marginL * scaling } - NText { - text: "Predefined Color Schemes" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true - // NText { - // text: "Predefined Color Schemes" - // font.pointSize: Style.fontSizeL * scaling - // font.weight: Style.fontWeightBold - // color: Color.mOnSurface - // Layout.fillWidth: true - // } + NText { + text: "Predefined Color Schemes" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + NText { text: "These color schemes only apply when 'Use Matugen' is disabled. When enabled, Matugen will generate colors based on your wallpaper instead. You can toggle between light and dark themes when using Matugen." font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant + color: Color.mOnSurface Layout.fillWidth: true wrapMode: Text.WordWrap } diff --git a/Modules/SettingsPanel/Tabs/GeneralTab.qml b/Modules/SettingsPanel/Tabs/GeneralTab.qml index 19da93f..1cb1878 100644 --- a/Modules/SettingsPanel/Tabs/GeneralTab.qml +++ b/Modules/SettingsPanel/Tabs/GeneralTab.qml @@ -33,6 +33,13 @@ ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true + NText { + text: "General Settings" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + // Profile section ColumnLayout { spacing: Style.marginS * scaling diff --git a/Modules/SettingsPanel/Tabs/LauncherTab.qml b/Modules/SettingsPanel/Tabs/LauncherTab.qml index 5d9d6aa..ae10ade 100644 --- a/Modules/SettingsPanel/Tabs/LauncherTab.qml +++ b/Modules/SettingsPanel/Tabs/LauncherTab.qml @@ -34,7 +34,7 @@ ColumnLayout { Layout.fillWidth: true NText { - text: "Launcer Options" + text: "Launcher" font.pointSize: Style.fontSizeXXL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface @@ -56,7 +56,7 @@ ColumnLayout { } NText { - text: "Launcer Anchoring" + text: "Launcher Position" font.pointSize: Style.fontSizeXXL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface diff --git a/Modules/SettingsPanel/Tabs/NetworkTab.qml b/Modules/SettingsPanel/Tabs/NetworkTab.qml index f621f79..7029ecb 100644 --- a/Modules/SettingsPanel/Tabs/NetworkTab.qml +++ b/Modules/SettingsPanel/Tabs/NetworkTab.qml @@ -34,6 +34,13 @@ ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true + NText { + text: "Interfaces" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + NToggle { label: "WiFi Enabled" description: "Enable WiFi connectivity." diff --git a/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml b/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml index a02b3a3..836ca6c 100644 --- a/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml +++ b/Modules/SettingsPanel/Tabs/ScreenRecorderTab.qml @@ -33,6 +33,14 @@ ColumnLayout { spacing: Style.marginXS * scaling Layout.fillWidth: true + NText { + text: "Recordings" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.bottomMargin: Style.marginS * scaling + } + // Output Directory ColumnLayout { spacing: Style.marginS * scaling diff --git a/Modules/SettingsPanel/Tabs/TimeWeatherTab.qml b/Modules/SettingsPanel/Tabs/TimeWeatherTab.qml index f3783a5..4e7cd52 100644 --- a/Modules/SettingsPanel/Tabs/TimeWeatherTab.qml +++ b/Modules/SettingsPanel/Tabs/TimeWeatherTab.qml @@ -33,6 +33,14 @@ ColumnLayout { spacing: Style.marginXS * scaling Layout.fillWidth: true + NText { + text: "Location" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.bottomMargin: Style.marginS * scaling + } + // Location section ColumnLayout { spacing: Style.marginM * scaling diff --git a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml index b3112e8..79b7fa2 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperSelectorTab.qml @@ -31,7 +31,7 @@ Item { // Current wallpaper display NText { text: "Current Wallpaper" - font.pointSize: Style.fontSizeL * scaling + font.pointSize: Style.fontSizeXXL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface } diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index 8fe156d..f8da98d 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -34,6 +34,14 @@ ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true + NText { + text: "Directory" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.bottomMargin: Style.marginS * scaling + } + // Wallpaper Settings Category ColumnLayout { spacing: Style.marginS * scaling From 12092ca6f640b303d5d9b783d07e7b089b05a6d8 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 22 Aug 2025 17:00:39 +0200 Subject: [PATCH 02/16] Fix Brightness & Tray --- Commons/Color.qml | 1 + Modules/Bar/Bar.qml | 5 ++++- Modules/Bar/Widgets/Brightness.qml | 2 +- Modules/Bar/Widgets/Tray.qml | 17 +++++++++++------ Modules/SettingsPanel/Tabs/BarTab.qml | 11 ++++++----- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Commons/Color.qml b/Commons/Color.qml index 221716f..f31f1ac 100644 --- a/Commons/Color.qml +++ b/Commons/Color.qml @@ -47,6 +47,7 @@ Singleton { // ----------- function applyOpacity(color, opacity) { // Convert color to string and apply opacity + if (!color) return "transparent" return color.toString().replace("#", "#" + opacity) } diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 61e7940..c99cfda 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -137,13 +137,16 @@ Variants { return null } + console.log(`Attempting to load widget: ${widgetName}.qml`) + // Try to load the widget directly from file const component = Qt.createComponent(`../Bar/Widgets/${widgetName}.qml`) if (component.status === Component.Ready) { + console.log(`Successfully created component for: ${widgetName}.qml`) return component } - console.warn(`Failed to load widget: ${widgetName}.qml`) + console.warn(`Failed to load widget: ${widgetName}.qml, status:`, component.status, "error:", component.errorString()) return null } diff --git a/Modules/Bar/Widgets/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml index 82bacb8..58c8cc6 100644 --- a/Modules/Bar/Widgets/Brightness.qml +++ b/Modules/Bar/Widgets/Brightness.qml @@ -10,7 +10,7 @@ Item { width: pill.width height: pill.height - visible: firstBrightnessReceived && getMonitor() !== null + visible: getMonitor() !== null // Used to avoid opening the pill on Quickshell startup property bool firstBrightnessReceived: false diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index c6ba8c9..9998fe6 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -9,10 +9,14 @@ import qs.Commons import qs.Services import qs.Widgets + + Rectangle { readonly property real itemSize: 24 * scaling visible: Settings.data.bar.showTray && (SystemTray.items.values.length > 0) + + width: tray.width + Style.marginM * scaling * 2 height: Math.round(Style.capsuleHeight * scaling) @@ -95,14 +99,14 @@ Rectangle { return } - if (modelData.hasMenu && modelData.menu && trayMenu) { + if (modelData.hasMenu && modelData.menu && trayMenu.item) { trayPanel.open() // Anchor the menu to the tray icon item (parent) and position it below the icon - const menuX = (width / 2) - (trayMenu.width / 2) + const menuX = (width / 2) - (trayMenu.item.width / 2) const menuY = (Style.barHeight * scaling) - trayMenu.menu = modelData.menu - trayMenu.showAt(parent, menuX, menuY) + trayMenu.item.menu = modelData.menu + trayMenu.item.showAt(parent, menuX, menuY) } else { Logger.log("Tray", "No menu available for", modelData.id, "or trayMenu not set") } @@ -142,7 +146,7 @@ Rectangle { function close() { visible = false - trayMenu.hideMenu() + trayMenu.item.hideMenu() } // Clicking outside of the rectangle to close @@ -151,8 +155,9 @@ Rectangle { onClicked: trayPanel.close() } - TrayMenu { + Loader { id: trayMenu + source: "TrayMenu.qml" } } } diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index d03c333..307db25 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -199,7 +199,7 @@ ColumnLayout { height: 32 * scaling radius: width * 0.5 color: Color.mPrimary - border.color: Color.mPrimaryContainer + border.color: Color.mPrimary border.width: 2 * scaling NIcon { @@ -336,7 +336,7 @@ ColumnLayout { height: 32 * scaling radius: width * 0.5 color: Color.mPrimary - border.color: Color.mPrimaryContainer + border.color: Color.mPrimary border.width: 2 * scaling NIcon { @@ -473,7 +473,7 @@ ColumnLayout { height: 32 * scaling radius: width * 0.5 color: Color.mPrimary - border.color: Color.mPrimaryContainer + border.color: Color.mPrimary border.width: 2 * scaling NIcon { @@ -612,8 +612,9 @@ ColumnLayout { border.width: Math.max(1, Style.borderS * scaling) ColumnLayout { - anchors.fill: parent - anchors.margins: Style.marginL * scaling + Layout.fillWidth: true + Layout.fillHeight: true + Layout.margins: Style.marginL * scaling spacing: Style.marginM * scaling NText { From f441bec32db02024669ba08ba89d11a910f2e474 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 22 Aug 2025 18:13:18 +0200 Subject: [PATCH 03/16] Fix Layout, more changes to modular bar --- Modules/Bar/Bar.qml | 64 +++++- Modules/Bar/Widgets/Bluetooth.qml | 26 ++- Modules/Bar/Widgets/WiFi.qml | 62 +++++- Modules/SettingsPanel/Tabs/BarTab.qml | 293 +++++++------------------- Widgets/NComboBox.qml | 4 +- 5 files changed, 212 insertions(+), 237 deletions(-) diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index c99cfda..462b8c6 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -23,6 +23,10 @@ Variants { implicitHeight: Style.barHeight * scaling color: Color.transparent + Component.onCompleted: { + logWidgetLoadingSummary() + } + // If no bar activated in settings, then show them all visible: modelData ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false @@ -66,9 +70,10 @@ Variants { anchors.verticalCenter: parent.verticalCenter onStatusChanged: { if (status === Loader.Error) { - console.warn(`Failed to load widget: ${modelData}`) + Logger.error("Bar", `Failed to load ${modelData} widget`) + onWidgetFailed() } else if (status === Loader.Ready) { - console.log(`Successfully loaded widget: ${modelData}`) + onWidgetLoaded() } } } @@ -93,9 +98,10 @@ Variants { anchors.verticalCenter: parent.verticalCenter onStatusChanged: { if (status === Loader.Error) { - console.warn(`Failed to load widget: ${modelData}`) + Logger.error("Bar", `Failed to load ${modelData} widget`) + onWidgetFailed() } else if (status === Loader.Ready) { - console.log(`Successfully loaded widget: ${modelData}`) + onWidgetLoaded() } } } @@ -121,9 +127,10 @@ Variants { anchors.verticalCenter: parent.verticalCenter onStatusChanged: { if (status === Loader.Error) { - console.warn(`Failed to load widget: ${modelData}`) + Logger.error("Bar", `Failed to load ${modelData} widget`) + onWidgetFailed() } else if (status === Loader.Ready) { - console.log(`Successfully loaded widget: ${modelData}`) + onWidgetLoaded() } } } @@ -137,19 +144,56 @@ Variants { return null } - console.log(`Attempting to load widget: ${widgetName}.qml`) + const widgetPath = `../Bar/Widgets/${widgetName}.qml` + Logger.log("Bar", `Attempting to load widget from: ${widgetPath}`) // Try to load the widget directly from file - const component = Qt.createComponent(`../Bar/Widgets/${widgetName}.qml`) + const component = Qt.createComponent(widgetPath) if (component.status === Component.Ready) { - console.log(`Successfully created component for: ${widgetName}.qml`) + Logger.log("Bar", `Successfully created component for: ${widgetName}.qml`) return component } - console.warn(`Failed to load widget: ${widgetName}.qml, status:`, component.status, "error:", component.errorString()) + Logger.error("Bar", `Failed to load ${widgetName}.qml widget, status: ${component.status}, error: ${component.errorString()}`) return null } + // Track widget loading status + property int totalWidgets: 0 + property int loadedWidgets: 0 + property int failedWidgets: 0 + + // Log widget loading summary + function logWidgetLoadingSummary() { + const allWidgets = [ + ...Settings.data.bar.widgets.left, + ...Settings.data.bar.widgets.center, + ...Settings.data.bar.widgets.right + ] + + totalWidgets = allWidgets.length + loadedWidgets = 0 + failedWidgets = 0 + + if (totalWidgets > 0) { + Logger.log("Bar", `Attempting to load ${totalWidgets} widgets`) + } + } + + function onWidgetLoaded() { + loadedWidgets++ + if (loadedWidgets + failedWidgets === totalWidgets) { + Logger.log("Bar", `Loaded ${loadedWidgets}/${totalWidgets} widgets`) + } + } + + function onWidgetFailed() { + failedWidgets++ + if (loadedWidgets + failedWidgets === totalWidgets) { + Logger.log("Bar", `Loaded ${loadedWidgets}/${totalWidgets} widgets`) + } + } + diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml index 09c5c60..90b8dee 100644 --- a/Modules/Bar/Widgets/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -7,6 +7,7 @@ import qs.Commons import qs.Services import qs.Widgets + NIconButton { id: root @@ -32,7 +33,30 @@ NIconButton { bluetoothPanel.toggle(screen) } - BluetoothPanel { + Loader { id: bluetoothPanel + source: "BluetoothPanel.qml" + active: false + + property var pendingToggleScreen: null + + onStatusChanged: { + if (status === Loader.Ready && item && pendingToggleScreen !== null) { + item.toggle(pendingToggleScreen) + pendingToggleScreen = null + } + } + + function toggle(screen) { + // Load the panel if it's not already loaded + if (!active) { + active = true + pendingToggleScreen = screen + } else if (status === Loader.Ready && item) { + item.toggle(screen) + } else { + pendingToggleScreen = screen + } + } } } diff --git a/Modules/Bar/Widgets/WiFi.qml b/Modules/Bar/Widgets/WiFi.qml index cdae9be..61a6a13 100644 --- a/Modules/Bar/Widgets/WiFi.qml +++ b/Modules/Bar/Widgets/WiFi.qml @@ -7,34 +7,76 @@ import qs.Commons import qs.Services import qs.Widgets + NIconButton { id: root sizeMultiplier: 0.8 + Component.onCompleted: { + Logger.log("WiFi", "Widget component completed") + Logger.log("WiFi", "NetworkService available:", !!NetworkService) + if (NetworkService) { + Logger.log("WiFi", "NetworkService.networks available:", !!NetworkService.networks) + } + } + colorBg: Color.mSurfaceVariant colorFg: Color.mOnSurface colorBorder: Color.transparent colorBorderHover: Color.transparent icon: { - let connected = false - let signalStrength = 0 - for (const net in NetworkService.networks) { - if (NetworkService.networks[net].connected) { - connected = true - signalStrength = NetworkService.networks[net].signal - break + try { + let connected = false + let signalStrength = 0 + for (const net in NetworkService.networks) { + if (NetworkService.networks[net].connected) { + connected = true + signalStrength = NetworkService.networks[net].signal + break + } } + return connected ? NetworkService.signalIcon(signalStrength) : "wifi" + } catch (error) { + Logger.error("WiFi", "Error getting icon:", error) + return "wifi" } - return connected ? NetworkService.signalIcon(signalStrength) : "wifi" } tooltipText: "WiFi Networks" onClicked: { - wifiPanel.toggle(screen) + try { + Logger.log("WiFi", "Button clicked, toggling panel") + wifiPanel.toggle(screen) + } catch (error) { + Logger.error("WiFi", "Error toggling panel:", error) + } } - WiFiPanel { + Loader { id: wifiPanel + source: "WiFiPanel.qml" + active: false + + property var pendingToggleScreen: null + + onStatusChanged: { + if (status === Loader.Ready && item && pendingToggleScreen !== null) { + item.toggle(pendingToggleScreen) + pendingToggleScreen = null + } + } + + function toggle(screen) { + // Load the panel if it's not already loaded + if (!active) { + active = true + pendingToggleScreen = screen + } else if (status === Loader.Ready && item) { + item.toggle(screen) + } else { + pendingToggleScreen = screen + } + } } } diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index 307db25..a44e908 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -166,15 +166,15 @@ ColumnLayout { Layout.fillWidth: true Layout.minimumHeight: { var widgetCount = Settings.data.bar.widgets.left.length - if (widgetCount === 0) return 120 * scaling + if (widgetCount === 0) return 140 * scaling var availableWidth = scrollView.availableWidth - (Style.marginM * scaling * 2) // Card margins var avgWidgetWidth = 150 * scaling // Estimated widget width including spacing var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) var rows = Math.ceil(widgetCount / widgetsPerRow) - // Header (40) + spacing (16) + (rows * widget height) + (rows-1 * spacing) + bottom margin (16) - return (40 + 16 + (rows * 48) + ((rows - 1) * Style.marginS) + 16) * scaling + // Header (50) + spacing (20) + (rows * widget height) + (rows-1 * spacing) + bottom margin (20) + return (50 + 20 + (rows * 48) + ((rows - 1) * Style.marginS) + 20) * scaling } ColumnLayout { @@ -194,28 +194,14 @@ ColumnLayout { Item { Layout.fillWidth: true } - Rectangle { - width: 32 * scaling - height: 32 * scaling - radius: width * 0.5 - color: Color.mPrimary - border.color: Color.mPrimary - border.width: 2 * scaling - - NIcon { - anchors.centerIn: parent - text: "add" - color: Color.mOnPrimary - font.pointSize: Style.fontSizeM * scaling - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - addWidgetDialog.show("left") - } + NComboBox { + width: 120 * scaling + model: availableWidgets + label: "" + description: "" + placeholder: "Add widget to left section" + onSelected: key => { + addWidgetToSection(key, "left") } } } @@ -224,7 +210,7 @@ ColumnLayout { id: leftWidgetsFlow Layout.fillWidth: true Layout.fillHeight: true - Layout.minimumHeight: 52 * scaling + Layout.minimumHeight: 65 * scaling spacing: Style.marginS * scaling flow: Flow.LeftToRight @@ -303,15 +289,15 @@ ColumnLayout { Layout.fillWidth: true Layout.minimumHeight: { var widgetCount = Settings.data.bar.widgets.center.length - if (widgetCount === 0) return 120 * scaling + if (widgetCount === 0) return 140 * scaling var availableWidth = scrollView.availableWidth - (Style.marginM * scaling * 2) // Card margins var avgWidgetWidth = 150 * scaling // Estimated widget width including spacing var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) var rows = Math.ceil(widgetCount / widgetsPerRow) - // Header (40) + spacing (16) + (rows * widget height) + (rows-1 * spacing) + bottom margin (16) - return (40 + 16 + (rows * 48) + ((rows - 1) * Style.marginS) + 16) * scaling + // Header (50) + spacing (20) + (rows * widget height) + (rows-1 * spacing) + bottom margin (20) + return (50 + 20 + (rows * 48) + ((rows - 1) * Style.marginS) + 20) * scaling } ColumnLayout { @@ -331,28 +317,14 @@ ColumnLayout { Item { Layout.fillWidth: true } - Rectangle { - width: 32 * scaling - height: 32 * scaling - radius: width * 0.5 - color: Color.mPrimary - border.color: Color.mPrimary - border.width: 2 * scaling - - NIcon { - anchors.centerIn: parent - text: "add" - color: Color.mOnPrimary - font.pointSize: Style.fontSizeM * scaling - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - addWidgetDialog.show("center") - } + NComboBox { + width: 120 * scaling + model: availableWidgets + label: "" + description: "" + placeholder: "Add widget to center section" + onSelected: key => { + addWidgetToSection(key, "center") } } } @@ -361,7 +333,7 @@ ColumnLayout { id: centerWidgetsFlow Layout.fillWidth: true Layout.fillHeight: true - Layout.minimumHeight: 52 * scaling + Layout.minimumHeight: 65 * scaling spacing: Style.marginS * scaling flow: Flow.LeftToRight @@ -440,15 +412,15 @@ ColumnLayout { Layout.fillWidth: true Layout.minimumHeight: { var widgetCount = Settings.data.bar.widgets.right.length - if (widgetCount === 0) return 120 * scaling + if (widgetCount === 0) return 140 * scaling var availableWidth = scrollView.availableWidth - (Style.marginM * scaling * 2) // Card margins var avgWidgetWidth = 150 * scaling // Estimated widget width including spacing var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) var rows = Math.ceil(widgetCount / widgetsPerRow) - // Header (40) + spacing (16) + (rows * widget height) + (rows-1 * spacing) + bottom margin (16) - return (40 + 16 + (rows * 48) + ((rows - 1) * Style.marginS) + 16) * scaling + // Header (50) + spacing (20) + (rows * widget height) + (rows-1 * spacing) + bottom margin (20) + return (50 + 20 + (rows * 48) + ((rows - 1) * Style.marginS) + 20) * scaling } ColumnLayout { @@ -468,28 +440,14 @@ ColumnLayout { Item { Layout.fillWidth: true } - Rectangle { - width: 32 * scaling - height: 32 * scaling - radius: width * 0.5 - color: Color.mPrimary - border.color: Color.mPrimary - border.width: 2 * scaling - - NIcon { - anchors.centerIn: parent - text: "add" - color: Color.mOnPrimary - font.pointSize: Style.fontSizeM * scaling - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - addWidgetDialog.show("right") - } + NComboBox { + width: 120 * scaling + model: availableWidgets + label: "" + description: "" + placeholder: "Add widget to right section" + onSelected: key => { + addWidgetToSection(key, "right") } } } @@ -498,7 +456,7 @@ ColumnLayout { id: rightWidgetsFlow Layout.fillWidth: true Layout.fillHeight: true - Layout.minimumHeight: 52 * scaling + Layout.minimumHeight: 65 * scaling spacing: Style.marginS * scaling flow: Flow.LeftToRight @@ -577,144 +535,7 @@ ColumnLayout { } } - // Add Widget Dialog - Rectangle { - id: addWidgetDialog - anchors.fill: parent - color: Color.applyOpacity(Color.mShadow, "80") - visible: false - z: 1000 - property string targetSection: "" - - function show(section) { - targetSection = section - visible = true - } - - function hide() { - visible = false - targetSection = "" - } - - MouseArea { - anchors.fill: parent - onClicked: addWidgetDialog.hide() - } - - Rectangle { - anchors.centerIn: parent - width: 400 * scaling - height: 500 * scaling - radius: Style.radiusL * scaling - color: Color.mSurface - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - - ColumnLayout { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.margins: Style.marginL * scaling - spacing: Style.marginM * scaling - - NText { - text: "Add Widget to " + (addWidgetDialog.targetSection === "left" ? "Left" : - addWidgetDialog.targetSection === "center" ? "Center" : "Right") + " Section" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - Layout.fillWidth: true - } - - ListView { - Layout.fillWidth: true - Layout.fillHeight: true - clip: true - spacing: Style.marginXS * scaling - - model: ListModel { - ListElement { name: "SystemMonitor"; icon: "memory"; description: "System statistics" } - ListElement { name: "ActiveWindow"; icon: "web_asset"; description: "Active window title" } - ListElement { name: "MediaMini"; icon: "music_note"; description: "Media controls" } - ListElement { name: "Workspace"; icon: "dashboard"; description: "Workspace switcher" } - ListElement { name: "ScreenRecorderIndicator"; icon: "videocam"; description: "Recording indicator" } - ListElement { name: "Tray"; icon: "apps"; description: "System tray" } - ListElement { name: "NotificationHistory"; icon: "notifications"; description: "Notification history" } - ListElement { name: "WiFi"; icon: "wifi"; description: "WiFi status" } - ListElement { name: "Bluetooth"; icon: "bluetooth"; description: "Bluetooth status" } - ListElement { name: "Battery"; icon: "battery_full"; description: "Battery status" } - ListElement { name: "Volume"; icon: "volume_up"; description: "Volume control" } - ListElement { name: "Brightness"; icon: "brightness_6"; description: "Brightness control" } - ListElement { name: "Clock"; icon: "schedule"; description: "Clock" } - ListElement { name: "SidePanelToggle"; icon: "widgets"; description: "Side panel toggle" } - } - - delegate: Rectangle { - width: ListView.view.width - height: 48 * scaling - radius: Style.radiusS * scaling - color: mouseArea.containsMouse ? Color.mTertiary : Color.mSurfaceVariant - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - - RowLayout { - anchors.fill: parent - anchors.margins: Style.marginS * scaling - spacing: Style.marginS * scaling - - NIcon { - text: model.icon - color: Color.mOnSurface - font.pointSize: Style.fontSizeM * scaling - } - - ColumnLayout { - Layout.fillWidth: true - spacing: 0 - - NText { - text: model.name - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - NText { - text: model.description - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - } - } - } - - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - addWidgetToSection(model.name, addWidgetDialog.targetSection) - addWidgetDialog.hide() - } - } - } - } - - RowLayout { - Layout.fillWidth: true - - Item { Layout.fillWidth: true } - - NIconButton { - icon: "close" - size: 20 * scaling - color: Color.mOnSurface - onClicked: addWidgetDialog.hide() - } - } - } - } - } // Helper functions function addWidgetToSection(widgetName, section) { @@ -762,4 +583,46 @@ ColumnLayout { Settings.data.bar.widgets[section] = newArray } } + + // Widget list for adding widgets + ListModel { + id: availableWidgets + ListElement { key: "SystemMonitor"; name: "SystemMonitor" } + ListElement { key: "ActiveWindow"; name: "ActiveWindow" } + ListElement { key: "MediaMini"; name: "MediaMini" } + ListElement { key: "Workspace"; name: "Workspace" } + ListElement { key: "ScreenRecorderIndicator"; name: "ScreenRecorderIndicator" } + ListElement { key: "Tray"; name: "Tray" } + ListElement { key: "NotificationHistory"; name: "NotificationHistory" } + ListElement { key: "WiFi"; name: "WiFi" } + ListElement { key: "Bluetooth"; name: "Bluetooth" } + ListElement { key: "Battery"; name: "Battery" } + ListElement { key: "Volume"; name: "Volume" } + ListElement { key: "Brightness"; name: "Brightness" } + ListElement { key: "Clock"; name: "Clock" } + ListElement { key: "SidePanelToggle"; name: "SidePanelToggle" } + } + + + + // Helper function to get widget icons + function getWidgetIcon(widgetKey) { + switch(widgetKey) { + case "SystemMonitor": return "memory" + case "ActiveWindow": return "web_asset" + case "MediaMini": return "music_note" + case "Workspace": return "dashboard" + case "ScreenRecorderIndicator": return "videocam" + case "Tray": return "apps" + case "NotificationHistory": return "notifications" + case "WiFi": return "wifi" + case "Bluetooth": return "bluetooth" + case "Battery": return "battery_full" + case "Volume": return "volume_up" + case "Brightness": return "brightness_6" + case "Clock": return "schedule" + case "SidePanelToggle": return "widgets" + default: return "widgets" + } + } } diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml index 9ec2aaf..c82abb0 100644 --- a/Widgets/NComboBox.qml +++ b/Widgets/NComboBox.qml @@ -16,6 +16,7 @@ ColumnLayout { } property string currentKey: '' + property string placeholder: "" signal selected(string key) @@ -61,8 +62,9 @@ ColumnLayout { font.pointSize: Style.fontSizeM * scaling verticalAlignment: Text.AlignVCenter elide: Text.ElideRight + color: (combo.currentIndex >= 0 && combo.currentIndex < root.model.count) ? Color.mOnSurface : Color.mOnSurfaceVariant text: (combo.currentIndex >= 0 && combo.currentIndex < root.model.count) ? root.model.get( - combo.currentIndex).name : "" + combo.currentIndex).name : root.placeholder } indicator: NIcon { From 8bb6da5e0ddd8530b082be097adf67720b6ff315 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 22 Aug 2025 12:25:28 -0400 Subject: [PATCH 04/16] Fix BT and WiFi panels --- Modules/Bar/Widgets/Bluetooth.qml | 27 ------------------ Modules/Bar/Widgets/WiFi.qml | 28 ------------------- .../BluetoothPanel.qml | 0 .../{Bar/Widgets => WiFiPanel}/WiFiPanel.qml | 0 shell.qml | 11 +++++++- 5 files changed, 10 insertions(+), 56 deletions(-) rename Modules/{Bar/Widgets => BluetoothPanel}/BluetoothPanel.qml (100%) rename Modules/{Bar/Widgets => WiFiPanel}/WiFiPanel.qml (100%) diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml index 90b8dee..35ff4d5 100644 --- a/Modules/Bar/Widgets/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -32,31 +32,4 @@ NIconButton { onClicked: { bluetoothPanel.toggle(screen) } - - Loader { - id: bluetoothPanel - source: "BluetoothPanel.qml" - active: false - - property var pendingToggleScreen: null - - onStatusChanged: { - if (status === Loader.Ready && item && pendingToggleScreen !== null) { - item.toggle(pendingToggleScreen) - pendingToggleScreen = null - } - } - - function toggle(screen) { - // Load the panel if it's not already loaded - if (!active) { - active = true - pendingToggleScreen = screen - } else if (status === Loader.Ready && item) { - item.toggle(screen) - } else { - pendingToggleScreen = screen - } - } - } } diff --git a/Modules/Bar/Widgets/WiFi.qml b/Modules/Bar/Widgets/WiFi.qml index 61a6a13..83b4689 100644 --- a/Modules/Bar/Widgets/WiFi.qml +++ b/Modules/Bar/Widgets/WiFi.qml @@ -7,7 +7,6 @@ import qs.Commons import qs.Services import qs.Widgets - NIconButton { id: root @@ -52,31 +51,4 @@ NIconButton { Logger.error("WiFi", "Error toggling panel:", error) } } - - Loader { - id: wifiPanel - source: "WiFiPanel.qml" - active: false - - property var pendingToggleScreen: null - - onStatusChanged: { - if (status === Loader.Ready && item && pendingToggleScreen !== null) { - item.toggle(pendingToggleScreen) - pendingToggleScreen = null - } - } - - function toggle(screen) { - // Load the panel if it's not already loaded - if (!active) { - active = true - pendingToggleScreen = screen - } else if (status === Loader.Ready && item) { - item.toggle(screen) - } else { - pendingToggleScreen = screen - } - } - } } diff --git a/Modules/Bar/Widgets/BluetoothPanel.qml b/Modules/BluetoothPanel/BluetoothPanel.qml similarity index 100% rename from Modules/Bar/Widgets/BluetoothPanel.qml rename to Modules/BluetoothPanel/BluetoothPanel.qml diff --git a/Modules/Bar/Widgets/WiFiPanel.qml b/Modules/WiFiPanel/WiFiPanel.qml similarity index 100% rename from Modules/Bar/Widgets/WiFiPanel.qml rename to Modules/WiFiPanel/WiFiPanel.qml diff --git a/shell.qml b/shell.qml index bd52508..8ce8361 100644 --- a/shell.qml +++ b/shell.qml @@ -16,6 +16,7 @@ import qs.Commons import qs.Modules.Launcher import qs.Modules.Background import qs.Modules.Bar +import qs.Modules.BluetoothPanel import qs.Modules.Calendar import qs.Modules.Dock import qs.Modules.IPC @@ -25,7 +26,7 @@ import qs.Modules.SettingsPanel import qs.Modules.PowerPanel import qs.Modules.SidePanel import qs.Modules.Toast - +import qs.Modules.WiFiPanel import qs.Services import qs.Widgets @@ -70,6 +71,14 @@ ShellRoot { id: powerPanel } + WiFiPanel { + id: wifiPanel + } + + BluetoothPanel { + id: bluetoothPanel + } + ToastManager {} IPCManager {} From c70b7004c638de0ae6c645c82549bf7ff10f04f1 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 22 Aug 2025 18:49:12 +0200 Subject: [PATCH 05/16] More fixes for BarTab --- Modules/SettingsPanel/Tabs/BarTab.qml | 118 ++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 15 deletions(-) diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index a44e908..525acb7 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Qt.labs.folderlistmodel import qs.Commons import qs.Services import qs.Widgets @@ -584,23 +585,110 @@ ColumnLayout { } } - // Widget list for adding widgets + // Dynamic widget discovery using FolderListModel + FolderListModel { + id: widgetFolderModel + folder: Qt.resolvedUrl("../../Bar/Widgets/") + nameFilters: ["*.qml"] + showDirs: false + showFiles: true + } + ListModel { id: availableWidgets - ListElement { key: "SystemMonitor"; name: "SystemMonitor" } - ListElement { key: "ActiveWindow"; name: "ActiveWindow" } - ListElement { key: "MediaMini"; name: "MediaMini" } - ListElement { key: "Workspace"; name: "Workspace" } - ListElement { key: "ScreenRecorderIndicator"; name: "ScreenRecorderIndicator" } - ListElement { key: "Tray"; name: "Tray" } - ListElement { key: "NotificationHistory"; name: "NotificationHistory" } - ListElement { key: "WiFi"; name: "WiFi" } - ListElement { key: "Bluetooth"; name: "Bluetooth" } - ListElement { key: "Battery"; name: "Battery" } - ListElement { key: "Volume"; name: "Volume" } - ListElement { key: "Brightness"; name: "Brightness" } - ListElement { key: "Clock"; name: "Clock" } - ListElement { key: "SidePanelToggle"; name: "SidePanelToggle" } + } + + Component.onCompleted: { + discoverWidgets() + } + + // Automatically discover available widgets from the Widgets directory + function discoverWidgets() { + console.log("Discovering widgets...") + console.log("FolderListModel count:", widgetFolderModel.count) + console.log("FolderListModel folder:", widgetFolderModel.folder) + + availableWidgets.clear() + + // Process each .qml file found in the directory + for (let i = 0; i < widgetFolderModel.count; i++) { + const fileName = widgetFolderModel.get(i, "fileName") + console.log("Found file:", fileName) + const widgetName = fileName.replace('.qml', '') + + // Skip TrayMenu as it's not a standalone widget + if (widgetName !== 'TrayMenu') { + console.log("Adding widget:", widgetName) + availableWidgets.append({ + key: widgetName, + name: widgetName, + icon: getDefaultIcon(widgetName) + }) + } + } + + console.log("Total widgets added:", availableWidgets.count) + + // If FolderListModel didn't find anything, use fallback + if (availableWidgets.count === 0) { + console.log("FolderListModel failed, using fallback list") + const fallbackWidgets = [ + "ActiveWindow", "Battery", "Bluetooth", "Brightness", "Clock", + "MediaMini", "NotificationHistory", "ScreenRecorderIndicator", + "SidePanelToggle", "SystemMonitor", "Tray", "Volume", "WiFi", "Workspace" + ] + + fallbackWidgets.forEach(widgetName => { + availableWidgets.append({ + key: widgetName, + name: widgetName, + icon: getDefaultIcon(widgetName) + }) + }) + } + + // Sort alphabetically by name + sortWidgets() + } + + // Sort widgets alphabetically + function sortWidgets() { + const widgets = [] + for (let i = 0; i < availableWidgets.count; i++) { + widgets.push({ + key: availableWidgets.get(i).key, + name: availableWidgets.get(i).name, + icon: availableWidgets.get(i).icon + }) + } + + widgets.sort((a, b) => a.name.localeCompare(b.name)) + + availableWidgets.clear() + widgets.forEach(widget => { + availableWidgets.append(widget) + }) + } + + // Get default icon for widget (can be overridden in widget files) + function getDefaultIcon(widgetName) { + const iconMap = { + "ActiveWindow": "web_asset", + "Battery": "battery_full", + "Bluetooth": "bluetooth", + "Brightness": "brightness_6", + "Clock": "schedule", + "MediaMini": "music_note", + "NotificationHistory": "notifications", + "ScreenRecorderIndicator": "videocam", + "SidePanelToggle": "widgets", + "SystemMonitor": "memory", + "Tray": "apps", + "Volume": "volume_up", + "WiFi": "wifi", + "Workspace": "dashboard" + } + return iconMap[widgetName] || "widgets" } From b5ea306b92dd41576cc9ac85b1084eca8b5f3cac Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 22 Aug 2025 18:57:04 +0200 Subject: [PATCH 06/16] Fix Batter Symbol visibility --- Modules/Bar/Widgets/Battery.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml index ef09f18..8349da5 100644 --- a/Modules/Bar/Widgets/Battery.qml +++ b/Modules/Bar/Widgets/Battery.qml @@ -22,8 +22,6 @@ NPill { // Choose icon based on charge and charging state function batteryIcon() { - if (!show) - return "" if (charging) return "battery_android_bolt" From 68181a4df67d7fd137d3fadee1398adb8073854c Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 22 Aug 2025 19:01:02 +0200 Subject: [PATCH 07/16] Move widget logic into it's own file (Commons/WidgetLoader.qml) --- Commons/WidgetLoader.qml | 70 ++++++++++++++++++++++++++++++++ Modules/Bar/Bar.qml | 88 ++++++++++++---------------------------- 2 files changed, 97 insertions(+), 61 deletions(-) create mode 100644 Commons/WidgetLoader.qml diff --git a/Commons/WidgetLoader.qml b/Commons/WidgetLoader.qml new file mode 100644 index 0000000..fda9635 --- /dev/null +++ b/Commons/WidgetLoader.qml @@ -0,0 +1,70 @@ +import QtQuick +import qs.Commons + +QtObject { + id: root + + // Signal emitted when widget loading status changes + signal widgetLoaded(string widgetName) + signal widgetFailed(string widgetName, string error) + signal loadingComplete(int total, int loaded, int failed) + + // Properties to track loading status + property int totalWidgets: 0 + property int loadedWidgets: 0 + property int failedWidgets: 0 + + // Auto-discover widget components + function getWidgetComponent(widgetName) { + if (!widgetName || widgetName.trim() === "") { + return null + } + + const widgetPath = `../Modules/Bar/Widgets/${widgetName}.qml` + Logger.log("WidgetLoader", `Attempting to load widget from: ${widgetPath}`) + + // Try to load the widget directly from file + const component = Qt.createComponent(widgetPath) + if (component.status === Component.Ready) { + Logger.log("WidgetLoader", `Successfully created component for: ${widgetName}.qml`) + return component + } + + const errorMsg = `Failed to load ${widgetName}.qml widget, status: ${component.status}, error: ${component.errorString()}` + Logger.error("WidgetLoader", errorMsg) + return null + } + + // Initialize loading tracking + function initializeLoading(widgetList) { + totalWidgets = widgetList.length + loadedWidgets = 0 + failedWidgets = 0 + + if (totalWidgets > 0) { + Logger.log("WidgetLoader", `Attempting to load ${totalWidgets} widgets`) + } + } + + // Track widget loading success + function onWidgetLoaded(widgetName) { + loadedWidgets++ + widgetLoaded(widgetName) + + if (loadedWidgets + failedWidgets === totalWidgets) { + Logger.log("WidgetLoader", `Loaded ${loadedWidgets}/${totalWidgets} widgets`) + loadingComplete(totalWidgets, loadedWidgets, failedWidgets) + } + } + + // Track widget loading failure + function onWidgetFailed(widgetName, error) { + failedWidgets++ + widgetFailed(widgetName, error) + + if (loadedWidgets + failedWidgets === totalWidgets) { + Logger.log("WidgetLoader", `Loaded ${loadedWidgets}/${totalWidgets} widgets`) + loadingComplete(totalWidgets, loadedWidgets, failedWidgets) + } + } +} diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 462b8c6..3e96aa0 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -23,9 +23,7 @@ Variants { implicitHeight: Style.barHeight * scaling color: Color.transparent - Component.onCompleted: { - logWidgetLoadingSummary() - } + // If no bar activated in settings, then show them all visible: modelData ? (Settings.data.bar.monitors.includes(modelData.name) @@ -64,16 +62,15 @@ Variants { Repeater { model: Settings.data.bar.widgets.left delegate: Loader { - id: widgetLoader - sourceComponent: getWidgetComponent(modelData) + id: leftWidgetLoader + sourceComponent: widgetLoader.getWidgetComponent(modelData) active: true anchors.verticalCenter: parent.verticalCenter onStatusChanged: { if (status === Loader.Error) { - Logger.error("Bar", `Failed to load ${modelData} widget`) - onWidgetFailed() + widgetLoader.onWidgetFailed(modelData, "Loader error") } else if (status === Loader.Ready) { - onWidgetLoaded() + widgetLoader.onWidgetLoaded(modelData) } } } @@ -92,16 +89,15 @@ Variants { Repeater { model: Settings.data.bar.widgets.center delegate: Loader { - id: widgetLoader - sourceComponent: getWidgetComponent(modelData) + id: centerWidgetLoader + sourceComponent: widgetLoader.getWidgetComponent(modelData) active: true anchors.verticalCenter: parent.verticalCenter onStatusChanged: { if (status === Loader.Error) { - Logger.error("Bar", `Failed to load ${modelData} widget`) - onWidgetFailed() + widgetLoader.onWidgetFailed(modelData, "Loader error") } else if (status === Loader.Ready) { - onWidgetLoaded() + widgetLoader.onWidgetLoaded(modelData) } } } @@ -121,16 +117,15 @@ Variants { Repeater { model: Settings.data.bar.widgets.right delegate: Loader { - id: widgetLoader - sourceComponent: getWidgetComponent(modelData) + id: rightWidgetLoader + sourceComponent: widgetLoader.getWidgetComponent(modelData) active: true anchors.verticalCenter: parent.verticalCenter onStatusChanged: { if (status === Loader.Error) { - Logger.error("Bar", `Failed to load ${modelData} widget`) - onWidgetFailed() + widgetLoader.onWidgetFailed(modelData, "Loader error") } else if (status === Loader.Ready) { - onWidgetLoaded() + widgetLoader.onWidgetLoaded(modelData) } } } @@ -138,60 +133,31 @@ Variants { } } - // Auto-discover widget components - function getWidgetComponent(widgetName) { - if (!widgetName || widgetName.trim() === "") { - return null + // Widget loader instance + WidgetLoader { + id: widgetLoader + + onWidgetLoaded: function(widgetName) { + Logger.log("Bar", `Widget loaded: ${widgetName}`) } - const widgetPath = `../Bar/Widgets/${widgetName}.qml` - Logger.log("Bar", `Attempting to load widget from: ${widgetPath}`) - - // Try to load the widget directly from file - const component = Qt.createComponent(widgetPath) - if (component.status === Component.Ready) { - Logger.log("Bar", `Successfully created component for: ${widgetName}.qml`) - return component + onWidgetFailed: function(widgetName, error) { + Logger.error("Bar", `Widget failed: ${widgetName} - ${error}`) } - Logger.error("Bar", `Failed to load ${widgetName}.qml widget, status: ${component.status}, error: ${component.errorString()}`) - return null + onLoadingComplete: function(total, loaded, failed) { + Logger.log("Bar", `Widget loading complete: ${loaded}/${total} loaded, ${failed} failed`) + } } - // Track widget loading status - property int totalWidgets: 0 - property int loadedWidgets: 0 - property int failedWidgets: 0 - - // Log widget loading summary - function logWidgetLoadingSummary() { + // Initialize widget loading tracking + Component.onCompleted: { const allWidgets = [ ...Settings.data.bar.widgets.left, ...Settings.data.bar.widgets.center, ...Settings.data.bar.widgets.right ] - - totalWidgets = allWidgets.length - loadedWidgets = 0 - failedWidgets = 0 - - if (totalWidgets > 0) { - Logger.log("Bar", `Attempting to load ${totalWidgets} widgets`) - } - } - - function onWidgetLoaded() { - loadedWidgets++ - if (loadedWidgets + failedWidgets === totalWidgets) { - Logger.log("Bar", `Loaded ${loadedWidgets}/${totalWidgets} widgets`) - } - } - - function onWidgetFailed() { - failedWidgets++ - if (loadedWidgets + failedWidgets === totalWidgets) { - Logger.log("Bar", `Loaded ${loadedWidgets}/${totalWidgets} widgets`) - } + widgetLoader.initializeLoading(allWidgets) } From 566e3e2aa79daec6f396c3e9bea2764cb9517089 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 22 Aug 2025 19:27:16 +0200 Subject: [PATCH 08/16] Move more things to WidgetLoader.qml --- Commons/WidgetLoader.qml | 37 ++++++-- Modules/Bar/Bar.qml | 8 -- Modules/SettingsPanel/Tabs/BarTab.qml | 126 +++----------------------- Widgets/NComboBox.qml | 2 + 4 files changed, 46 insertions(+), 127 deletions(-) diff --git a/Commons/WidgetLoader.qml b/Commons/WidgetLoader.qml index fda9635..635fe04 100644 --- a/Commons/WidgetLoader.qml +++ b/Commons/WidgetLoader.qml @@ -21,12 +21,10 @@ QtObject { } const widgetPath = `../Modules/Bar/Widgets/${widgetName}.qml` - Logger.log("WidgetLoader", `Attempting to load widget from: ${widgetPath}`) // Try to load the widget directly from file const component = Qt.createComponent(widgetPath) if (component.status === Component.Ready) { - Logger.log("WidgetLoader", `Successfully created component for: ${widgetName}.qml`) return component } @@ -40,10 +38,6 @@ QtObject { totalWidgets = widgetList.length loadedWidgets = 0 failedWidgets = 0 - - if (totalWidgets > 0) { - Logger.log("WidgetLoader", `Attempting to load ${totalWidgets} widgets`) - } } // Track widget loading success @@ -52,7 +46,7 @@ QtObject { widgetLoaded(widgetName) if (loadedWidgets + failedWidgets === totalWidgets) { - Logger.log("WidgetLoader", `Loaded ${loadedWidgets}/${totalWidgets} widgets`) + Logger.log("WidgetLoader", `Loaded ${loadedWidgets} widgets`) loadingComplete(totalWidgets, loadedWidgets, failedWidgets) } } @@ -63,8 +57,35 @@ QtObject { widgetFailed(widgetName, error) if (loadedWidgets + failedWidgets === totalWidgets) { - Logger.log("WidgetLoader", `Loaded ${loadedWidgets}/${totalWidgets} widgets`) loadingComplete(totalWidgets, loadedWidgets, failedWidgets) } } + + // This is where you should add your Modules/Bar/Widgets/ + // so it gets registered in the BarTab + function discoverAvailableWidgets() { + const widgetFiles = [ + "ActiveWindow", "Battery", "Bluetooth", "Brightness", "Clock", + "MediaMini", "NotificationHistory", "ScreenRecorderIndicator", + "SidePanelToggle", "SystemMonitor", "Tray", "Volume", "WiFi", "Workspace" + ] + + const availableWidgets = [] + + widgetFiles.forEach(widgetName => { + // Test if the widget can be loaded + const component = getWidgetComponent(widgetName) + if (component) { + availableWidgets.push({ + key: widgetName, + name: widgetName + }) + } + }) + + // Sort alphabetically + availableWidgets.sort((a, b) => a.name.localeCompare(b.name)) + + return availableWidgets + } } diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 3e96aa0..7e23d0e 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -137,17 +137,9 @@ Variants { WidgetLoader { id: widgetLoader - onWidgetLoaded: function(widgetName) { - Logger.log("Bar", `Widget loaded: ${widgetName}`) - } - onWidgetFailed: function(widgetName, error) { Logger.error("Bar", `Widget failed: ${widgetName} - ${error}`) } - - onLoadingComplete: function(total, loaded, failed) { - Logger.log("Bar", `Widget loading complete: ${loaded}/${total} loaded, ${failed} failed`) - } } // Initialize widget loading tracking diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index 525acb7..ebb9f2f 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -1,7 +1,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -import Qt.labs.folderlistmodel import qs.Commons import qs.Services import qs.Widgets @@ -196,6 +195,7 @@ ColumnLayout { Item { Layout.fillWidth: true } NComboBox { + id: leftComboBox width: 120 * scaling model: availableWidgets label: "" @@ -319,6 +319,7 @@ ColumnLayout { Item { Layout.fillWidth: true } NComboBox { + id: centerComboBox width: 120 * scaling model: availableWidgets label: "" @@ -326,6 +327,7 @@ ColumnLayout { placeholder: "Add widget to center section" onSelected: key => { addWidgetToSection(key, "center") + reset() // Reset selection } } } @@ -442,6 +444,7 @@ ColumnLayout { Item { Layout.fillWidth: true } NComboBox { + id: rightComboBox width: 120 * scaling model: availableWidgets label: "" @@ -449,6 +452,7 @@ ColumnLayout { placeholder: "Add widget to right section" onSelected: key => { addWidgetToSection(key, "right") + reset() // Reset selection } } } @@ -585,13 +589,9 @@ ColumnLayout { } } - // Dynamic widget discovery using FolderListModel - FolderListModel { - id: widgetFolderModel - folder: Qt.resolvedUrl("../../Bar/Widgets/") - nameFilters: ["*.qml"] - showDirs: false - showFiles: true + // Widget loader for discovering available widgets + WidgetLoader { + id: widgetLoader } ListModel { @@ -602,115 +602,19 @@ ColumnLayout { discoverWidgets() } - // Automatically discover available widgets from the Widgets directory + // Automatically discover available widgets using WidgetLoader function discoverWidgets() { - console.log("Discovering widgets...") - console.log("FolderListModel count:", widgetFolderModel.count) - console.log("FolderListModel folder:", widgetFolderModel.folder) - availableWidgets.clear() - // Process each .qml file found in the directory - for (let i = 0; i < widgetFolderModel.count; i++) { - const fileName = widgetFolderModel.get(i, "fileName") - console.log("Found file:", fileName) - const widgetName = fileName.replace('.qml', '') - - // Skip TrayMenu as it's not a standalone widget - if (widgetName !== 'TrayMenu') { - console.log("Adding widget:", widgetName) - availableWidgets.append({ - key: widgetName, - name: widgetName, - icon: getDefaultIcon(widgetName) - }) - } - } + // Use WidgetLoader to discover available widgets + const discoveredWidgets = widgetLoader.discoverAvailableWidgets() - console.log("Total widgets added:", availableWidgets.count) - - // If FolderListModel didn't find anything, use fallback - if (availableWidgets.count === 0) { - console.log("FolderListModel failed, using fallback list") - const fallbackWidgets = [ - "ActiveWindow", "Battery", "Bluetooth", "Brightness", "Clock", - "MediaMini", "NotificationHistory", "ScreenRecorderIndicator", - "SidePanelToggle", "SystemMonitor", "Tray", "Volume", "WiFi", "Workspace" - ] - - fallbackWidgets.forEach(widgetName => { - availableWidgets.append({ - key: widgetName, - name: widgetName, - icon: getDefaultIcon(widgetName) - }) - }) - } - - // Sort alphabetically by name - sortWidgets() - } - - // Sort widgets alphabetically - function sortWidgets() { - const widgets = [] - for (let i = 0; i < availableWidgets.count; i++) { - widgets.push({ - key: availableWidgets.get(i).key, - name: availableWidgets.get(i).name, - icon: availableWidgets.get(i).icon - }) - } - - widgets.sort((a, b) => a.name.localeCompare(b.name)) - - availableWidgets.clear() - widgets.forEach(widget => { + // Add discovered widgets to the ListModel + discoveredWidgets.forEach(widget => { availableWidgets.append(widget) }) - } - - // Get default icon for widget (can be overridden in widget files) - function getDefaultIcon(widgetName) { - const iconMap = { - "ActiveWindow": "web_asset", - "Battery": "battery_full", - "Bluetooth": "bluetooth", - "Brightness": "brightness_6", - "Clock": "schedule", - "MediaMini": "music_note", - "NotificationHistory": "notifications", - "ScreenRecorderIndicator": "videocam", - "SidePanelToggle": "widgets", - "SystemMonitor": "memory", - "Tray": "apps", - "Volume": "volume_up", - "WiFi": "wifi", - "Workspace": "dashboard" - } - return iconMap[widgetName] || "widgets" + + } - - - // Helper function to get widget icons - function getWidgetIcon(widgetKey) { - switch(widgetKey) { - case "SystemMonitor": return "memory" - case "ActiveWindow": return "web_asset" - case "MediaMini": return "music_note" - case "Workspace": return "dashboard" - case "ScreenRecorderIndicator": return "videocam" - case "Tray": return "apps" - case "NotificationHistory": return "notifications" - case "WiFi": return "wifi" - case "Bluetooth": return "bluetooth" - case "Battery": return "battery_full" - case "Volume": return "volume_up" - case "Brightness": return "brightness_6" - case "Clock": return "schedule" - case "SidePanelToggle": return "widgets" - default: return "widgets" - } - } } diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml index c82abb0..21dc986 100644 --- a/Widgets/NComboBox.qml +++ b/Widgets/NComboBox.qml @@ -37,6 +37,8 @@ ColumnLayout { return -1 } + + ComboBox { id: combo Layout.fillWidth: true From ce9ab7f90f166e4966bf94d8ee0e73d6e4a1f9f6 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 22 Aug 2025 19:57:29 +0200 Subject: [PATCH 09/16] Formatting --- Commons/Color.qml | 3 +- Commons/Settings.qml | 2 +- Commons/WidgetLoader.qml | 45 +++++++-------- Modules/Bar/Bar.qml | 16 +----- Modules/Bar/Widgets/Bluetooth.qml | 1 - Modules/Bar/Widgets/Tray.qml | 3 - Modules/SettingsPanel/Tabs/BarTab.qml | 80 ++++++++++++++------------- Widgets/NComboBox.qml | 9 ++- 8 files changed, 73 insertions(+), 86 deletions(-) diff --git a/Commons/Color.qml b/Commons/Color.qml index f31f1ac..dc1d554 100644 --- a/Commons/Color.qml +++ b/Commons/Color.qml @@ -47,7 +47,8 @@ Singleton { // ----------- function applyOpacity(color, opacity) { // Convert color to string and apply opacity - if (!color) return "transparent" + if (!color) + return "transparent" return color.toString().replace("#", "#" + opacity) } diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 0ffe01c..77aaaaf 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -133,7 +133,7 @@ Singleton { // Widget configuration for modular bar system property JsonObject widgets - + widgets: JsonObject { property list left: ["SystemMonitor", "ActiveWindow", "MediaMini"] property list center: ["Workspace"] diff --git a/Commons/WidgetLoader.qml b/Commons/WidgetLoader.qml index 635fe04..ce44431 100644 --- a/Commons/WidgetLoader.qml +++ b/Commons/WidgetLoader.qml @@ -19,16 +19,17 @@ QtObject { if (!widgetName || widgetName.trim() === "") { return null } - + const widgetPath = `../Modules/Bar/Widgets/${widgetName}.qml` - + // Try to load the widget directly from file const component = Qt.createComponent(widgetPath) if (component.status === Component.Ready) { return component } - - const errorMsg = `Failed to load ${widgetName}.qml widget, status: ${component.status}, error: ${component.errorString()}` + + const errorMsg = `Failed to load ${widgetName}.qml widget, status: ${component.status}, error: ${component.errorString( + )}` Logger.error("WidgetLoader", errorMsg) return null } @@ -44,7 +45,7 @@ QtObject { function onWidgetLoaded(widgetName) { loadedWidgets++ widgetLoaded(widgetName) - + if (loadedWidgets + failedWidgets === totalWidgets) { Logger.log("WidgetLoader", `Loaded ${loadedWidgets} widgets`) loadingComplete(totalWidgets, loadedWidgets, failedWidgets) @@ -55,7 +56,7 @@ QtObject { function onWidgetFailed(widgetName, error) { failedWidgets++ widgetFailed(widgetName, error) - + if (loadedWidgets + failedWidgets === totalWidgets) { loadingComplete(totalWidgets, loadedWidgets, failedWidgets) } @@ -64,28 +65,24 @@ QtObject { // This is where you should add your Modules/Bar/Widgets/ // so it gets registered in the BarTab function discoverAvailableWidgets() { - const widgetFiles = [ - "ActiveWindow", "Battery", "Bluetooth", "Brightness", "Clock", - "MediaMini", "NotificationHistory", "ScreenRecorderIndicator", - "SidePanelToggle", "SystemMonitor", "Tray", "Volume", "WiFi", "Workspace" - ] - + const widgetFiles = ["ActiveWindow", "Battery", "Bluetooth", "Brightness", "Clock", "MediaMini", "NotificationHistory", "ScreenRecorderIndicator", "SidePanelToggle", "SystemMonitor", "Tray", "Volume", "WiFi", "Workspace"] + const availableWidgets = [] - + widgetFiles.forEach(widgetName => { - // Test if the widget can be loaded - const component = getWidgetComponent(widgetName) - if (component) { - availableWidgets.push({ - key: widgetName, - name: widgetName - }) - } - }) - + // Test if the widget can be loaded + const component = getWidgetComponent(widgetName) + if (component) { + availableWidgets.push({ + "key": widgetName, + "name": widgetName + }) + } + }) + // Sort alphabetically availableWidgets.sort((a, b) => a.name.localeCompare(b.name)) - + return availableWidgets } } diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 7e23d0e..ae0ad9f 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -23,8 +23,6 @@ Variants { implicitHeight: Style.barHeight * scaling color: Color.transparent - - // If no bar activated in settings, then show them all visible: modelData ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false @@ -136,24 +134,16 @@ Variants { // Widget loader instance WidgetLoader { id: widgetLoader - - onWidgetFailed: function(widgetName, error) { + + onWidgetFailed: function (widgetName, error) { Logger.error("Bar", `Widget failed: ${widgetName} - ${error}`) } } // Initialize widget loading tracking Component.onCompleted: { - const allWidgets = [ - ...Settings.data.bar.widgets.left, - ...Settings.data.bar.widgets.center, - ...Settings.data.bar.widgets.right - ] + const allWidgets = [...Settings.data.bar.widgets.left, ...Settings.data.bar.widgets.center, ...Settings.data.bar.widgets.right] widgetLoader.initializeLoading(allWidgets) } - - - - } } diff --git a/Modules/Bar/Widgets/Bluetooth.qml b/Modules/Bar/Widgets/Bluetooth.qml index 35ff4d5..bbe35f1 100644 --- a/Modules/Bar/Widgets/Bluetooth.qml +++ b/Modules/Bar/Widgets/Bluetooth.qml @@ -7,7 +7,6 @@ import qs.Commons import qs.Services import qs.Widgets - NIconButton { id: root diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 9998fe6..d85a174 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -9,13 +9,10 @@ import qs.Commons import qs.Services import qs.Widgets - - Rectangle { readonly property real itemSize: 24 * scaling visible: Settings.data.bar.showTray && (SystemTray.items.values.length > 0) - width: tray.width + Style.marginM * scaling * 2 diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index ebb9f2f..c751dcb 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -166,13 +166,14 @@ ColumnLayout { Layout.fillWidth: true Layout.minimumHeight: { var widgetCount = Settings.data.bar.widgets.left.length - if (widgetCount === 0) return 140 * scaling - + if (widgetCount === 0) + return 140 * scaling + var availableWidth = scrollView.availableWidth - (Style.marginM * scaling * 2) // Card margins var avgWidgetWidth = 150 * scaling // Estimated widget width including spacing var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) var rows = Math.ceil(widgetCount / widgetsPerRow) - + // Header (50) + spacing (20) + (rows * widget height) + (rows-1 * spacing) + bottom margin (20) return (50 + 20 + (rows * 48) + ((rows - 1) * Style.marginS) + 20) * scaling } @@ -192,7 +193,9 @@ ColumnLayout { color: Color.mOnSurface } - Item { Layout.fillWidth: true } + Item { + Layout.fillWidth: true + } NComboBox { id: leftComboBox @@ -202,8 +205,8 @@ ColumnLayout { description: "" placeholder: "Add widget to left section" onSelected: key => { - addWidgetToSection(key, "left") - } + addWidgetToSection(key, "left") + } } } @@ -290,13 +293,14 @@ ColumnLayout { Layout.fillWidth: true Layout.minimumHeight: { var widgetCount = Settings.data.bar.widgets.center.length - if (widgetCount === 0) return 140 * scaling - + if (widgetCount === 0) + return 140 * scaling + var availableWidth = scrollView.availableWidth - (Style.marginM * scaling * 2) // Card margins var avgWidgetWidth = 150 * scaling // Estimated widget width including spacing var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) var rows = Math.ceil(widgetCount / widgetsPerRow) - + // Header (50) + spacing (20) + (rows * widget height) + (rows-1 * spacing) + bottom margin (20) return (50 + 20 + (rows * 48) + ((rows - 1) * Style.marginS) + 20) * scaling } @@ -316,7 +320,9 @@ ColumnLayout { color: Color.mOnSurface } - Item { Layout.fillWidth: true } + Item { + Layout.fillWidth: true + } NComboBox { id: centerComboBox @@ -326,9 +332,9 @@ ColumnLayout { description: "" placeholder: "Add widget to center section" onSelected: key => { - addWidgetToSection(key, "center") - reset() // Reset selection - } + addWidgetToSection(key, "center") + reset() // Reset selection + } } } @@ -415,13 +421,14 @@ ColumnLayout { Layout.fillWidth: true Layout.minimumHeight: { var widgetCount = Settings.data.bar.widgets.right.length - if (widgetCount === 0) return 140 * scaling - + if (widgetCount === 0) + return 140 * scaling + var availableWidth = scrollView.availableWidth - (Style.marginM * scaling * 2) // Card margins var avgWidgetWidth = 150 * scaling // Estimated widget width including spacing var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) var rows = Math.ceil(widgetCount / widgetsPerRow) - + // Header (50) + spacing (20) + (rows * widget height) + (rows-1 * spacing) + bottom margin (20) return (50 + 20 + (rows * 48) + ((rows - 1) * Style.marginS) + 20) * scaling } @@ -441,7 +448,9 @@ ColumnLayout { color: Color.mOnSurface } - Item { Layout.fillWidth: true } + Item { + Layout.fillWidth: true + } NComboBox { id: rightComboBox @@ -451,9 +460,9 @@ ColumnLayout { description: "" placeholder: "Add widget to right section" onSelected: key => { - addWidgetToSection(key, "right") - reset() // Reset selection - } + addWidgetToSection(key, "right") + reset() // Reset selection + } } } @@ -540,8 +549,6 @@ ColumnLayout { } } - - // Helper functions function addWidgetToSection(widgetName, section) { console.log("Adding widget", widgetName, "to section", section) @@ -551,7 +558,7 @@ ColumnLayout { var newArray = sectionArray.slice() newArray.push(widgetName) console.log("Widget added. New array:", JSON.stringify(newArray)) - + // Assign the new array Settings.data.bar.widgets[section] = newArray } @@ -565,7 +572,7 @@ ColumnLayout { var newArray = sectionArray.slice() newArray.splice(index, 1) console.log("Widget removed. New array:", JSON.stringify(newArray)) - + // Assign the new array Settings.data.bar.widgets[section] = newArray } @@ -574,16 +581,16 @@ ColumnLayout { function reorderWidgetInSection(section, fromIndex, toIndex) { console.log("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 (sectionArray && fromIndex >= 0 && fromIndex < sectionArray.length && toIndex >= 0 + && toIndex < sectionArray.length) { + // Create a new array to avoid modifying the original var newArray = sectionArray.slice() var item = newArray[fromIndex] newArray.splice(fromIndex, 1) newArray.splice(toIndex, 0, item) console.log("Widget reordered. New array:", JSON.stringify(newArray)) - + // Assign the new array Settings.data.bar.widgets[section] = newArray } @@ -593,28 +600,25 @@ ColumnLayout { WidgetLoader { id: widgetLoader } - + ListModel { id: availableWidgets } - + Component.onCompleted: { discoverWidgets() } - + // Automatically discover available widgets using WidgetLoader function discoverWidgets() { availableWidgets.clear() - + // Use WidgetLoader to discover available widgets const discoveredWidgets = widgetLoader.discoverAvailableWidgets() - + // Add discovered widgets to the ListModel discoveredWidgets.forEach(widget => { - availableWidgets.append(widget) - }) - - + availableWidgets.append(widget) + }) } - } diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml index 21dc986..1cb33bb 100644 --- a/Widgets/NComboBox.qml +++ b/Widgets/NComboBox.qml @@ -37,8 +37,6 @@ ColumnLayout { return -1 } - - ComboBox { id: combo Layout.fillWidth: true @@ -64,9 +62,10 @@ ColumnLayout { font.pointSize: Style.fontSizeM * scaling verticalAlignment: Text.AlignVCenter elide: Text.ElideRight - color: (combo.currentIndex >= 0 && combo.currentIndex < root.model.count) ? Color.mOnSurface : Color.mOnSurfaceVariant - text: (combo.currentIndex >= 0 && combo.currentIndex < root.model.count) ? root.model.get( - combo.currentIndex).name : root.placeholder + color: (combo.currentIndex >= 0 + && combo.currentIndex < root.model.count) ? Color.mOnSurface : Color.mOnSurfaceVariant + text: (combo.currentIndex >= 0 + && combo.currentIndex < root.model.count) ? root.model.get(combo.currentIndex).name : root.placeholder } indicator: NIcon { From b6379da96c3b6388830c6aee459373eb478e7e77 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 22 Aug 2025 13:59:49 -0400 Subject: [PATCH 10/16] Introducing fragment shaders for image rounding. --- Modules/Bar/MediaMini.qml | 8 +-- Modules/LockScreen/LockScreen.qml | 3 +- Modules/SettingsPanel/Tabs/AboutTab.qml | 5 +- Modules/SettingsPanel/Tabs/GeneralTab.qml | 4 +- Modules/SidePanel/Cards/MediaCard.qml | 3 +- Modules/SidePanel/Cards/ProfileCard.qml | 2 +- Shaders/frag/circled_image.frag | 30 +++++++++ Shaders/frag/rounded_image.frag | 56 +++++++++++++++++ Shaders/qsb/circled_image.frag.qsb | Bin 0 -> 1717 bytes Shaders/qsb/rounded_image.frag.qsb | Bin 0 -> 2767 bytes Widgets/NImageCircled.qml | 73 ++++++++++++++++++++++ Widgets/NImageRounded.qml | 40 +++++++----- 12 files changed, 193 insertions(+), 31 deletions(-) create mode 100644 Shaders/frag/circled_image.frag create mode 100644 Shaders/frag/rounded_image.frag create mode 100644 Shaders/qsb/circled_image.frag.qsb create mode 100644 Shaders/qsb/rounded_image.frag.qsb create mode 100644 Widgets/NImageCircled.qml diff --git a/Modules/Bar/MediaMini.qml b/Modules/Bar/MediaMini.qml index a061031..be07e87 100644 --- a/Modules/Bar/MediaMini.qml +++ b/Modules/Bar/MediaMini.qml @@ -109,14 +109,14 @@ Row { visible: Settings.data.audio.showMiniplayerAlbumArt Rectangle { - width: 16 * scaling - height: 16 * scaling + width: 18 * scaling + height: 18 * scaling radius: width * 0.5 color: Color.transparent antialiasing: true clip: true - NImageRounded { + NImageCircled { id: trackArt visible: MediaService.trackArtUrl.toString() !== "" anchors.fill: parent @@ -126,8 +126,6 @@ Row { fallbackIcon: MediaService.isPlaying ? "pause" : "play_arrow" borderWidth: 0 border.color: Color.transparent - imageRadius: width - antialiasing: true } // Fallback icon when no album art available diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index b980fa3..5f41bd7 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -465,13 +465,12 @@ Loader { } } - NImageRounded { + NImageCircled { anchors.centerIn: parent width: 100 * scaling height: 100 * scaling imagePath: Settings.data.general.avatarImage fallbackIcon: "person" - imageRadius: width * 0.5 } // Hover animation diff --git a/Modules/SettingsPanel/Tabs/AboutTab.qml b/Modules/SettingsPanel/Tabs/AboutTab.qml index a7b7b3d..b76c3d9 100644 --- a/Modules/SettingsPanel/Tabs/AboutTab.qml +++ b/Modules/SettingsPanel/Tabs/AboutTab.qml @@ -211,14 +211,13 @@ ColumnLayout { Layout.preferredWidth: Style.baseWidgetSize * 2 * scaling Layout.preferredHeight: Style.baseWidgetSize * 2 * scaling - NImageRounded { + NImageCircled { imagePath: modelData.avatar_url || "" anchors.fill: parent anchors.margins: Style.marginXS * scaling fallbackIcon: "person" borderColor: Color.mPrimary - borderWidth: Math.max(1, Style.borderL * scaling) - imageRadius: width * 0.5 + borderWidth: Math.max(1, Style.borderM * scaling) } } diff --git a/Modules/SettingsPanel/Tabs/GeneralTab.qml b/Modules/SettingsPanel/Tabs/GeneralTab.qml index 19da93f..f05cfa3 100644 --- a/Modules/SettingsPanel/Tabs/GeneralTab.qml +++ b/Modules/SettingsPanel/Tabs/GeneralTab.qml @@ -44,13 +44,13 @@ ColumnLayout { spacing: Style.marginL * scaling // Avatar preview - NImageRounded { + NImageCircled { width: 64 * scaling height: 64 * scaling imagePath: Settings.data.general.avatarImage fallbackIcon: "person" borderColor: Color.mPrimary - borderWidth: Math.max(1, Style.borderM) + borderWidth: Math.max(1, Style.borderM * scaling) } NTextInput { diff --git a/Modules/SidePanel/Cards/MediaCard.qml b/Modules/SidePanel/Cards/MediaCard.qml index 7809a5f..aad0839 100644 --- a/Modules/SidePanel/Cards/MediaCard.qml +++ b/Modules/SidePanel/Cards/MediaCard.qml @@ -164,7 +164,7 @@ NBox { border.width: Math.max(1, Style.borderS * scaling) clip: true - NImageRounded { + NImageCircled { id: trackArt visible: MediaService.trackArtUrl.toString() !== "" @@ -174,7 +174,6 @@ NBox { fallbackIcon: "music_note" borderColor: Color.mOutline borderWidth: Math.max(1, Style.borderS * scaling) - imageRadius: width * 0.5 } // Fallback icon when no album art available diff --git a/Modules/SidePanel/Cards/ProfileCard.qml b/Modules/SidePanel/Cards/ProfileCard.qml index b9b7df9..7374159 100644 --- a/Modules/SidePanel/Cards/ProfileCard.qml +++ b/Modules/SidePanel/Cards/ProfileCard.qml @@ -28,7 +28,7 @@ NBox { anchors.margins: Style.marginM * scaling spacing: Style.marginM * scaling - NImageRounded { + NImageCircled { width: Style.baseWidgetSize * 1.25 * scaling height: Style.baseWidgetSize * 1.25 * scaling imagePath: Settings.data.general.avatarImage diff --git a/Shaders/frag/circled_image.frag b/Shaders/frag/circled_image.frag new file mode 100644 index 0000000..308a9c5 --- /dev/null +++ b/Shaders/frag/circled_image.frag @@ -0,0 +1,30 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float imageOpacity; +} ubuf; + +void main() { + // Center coordinates around (0, 0) + vec2 uv = qt_TexCoord0 - 0.5; + + // Calculate distance from center + float distance = length(uv); + + // Create circular mask - anything beyond radius 0.5 is transparent + float mask = 1.0 - smoothstep(0.48, 0.52, distance); + + // Sample the texture + vec4 color = texture(source, qt_TexCoord0); + + // Apply the circular mask and opacity + float finalAlpha = color.a * mask * ubuf.imageOpacity * ubuf.qt_Opacity; + fragColor = vec4(color.rgb * finalAlpha, finalAlpha); +} \ No newline at end of file diff --git a/Shaders/frag/rounded_image.frag b/Shaders/frag/rounded_image.frag new file mode 100644 index 0000000..9d493b2 --- /dev/null +++ b/Shaders/frag/rounded_image.frag @@ -0,0 +1,56 @@ +#version 450 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + // Custom properties with non-conflicting names + float itemWidth; + float itemHeight; + float cornerRadius; + float imageOpacity; +} ubuf; + +// Function to calculate the signed distance from a point to a rounded box +float roundedBoxSDF(vec2 centerPos, vec2 boxSize, float radius) { + vec2 d = abs(centerPos) - boxSize + radius; + return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius; +} + +void main() { + // Get size from uniforms + vec2 itemSize = vec2(ubuf.itemWidth, ubuf.itemHeight); + float cornerRadius = ubuf.cornerRadius; + float itemOpacity = ubuf.imageOpacity; + + // Normalize coordinates to [-0.5, 0.5] range + vec2 uv = qt_TexCoord0 - 0.5; + + // Scale by aspect ratio to maintain uniform rounding + vec2 aspectRatio = itemSize / max(itemSize.x, itemSize.y); + uv *= aspectRatio; + + // Calculate half size in normalized space + vec2 halfSize = 0.5 * aspectRatio; + + // Normalize the corner radius + float normalizedRadius = cornerRadius / max(itemSize.x, itemSize.y); + + // Calculate distance to rounded rectangle + float distance = roundedBoxSDF(uv, halfSize, normalizedRadius); + + // Create smooth alpha mask + float smoothedAlpha = 1.0 - smoothstep(0.0, fwidth(distance), distance); + + // Sample the texture + vec4 color = texture(source, qt_TexCoord0); + + // Apply the rounded mask and opacity + // Make sure areas outside the rounded rect are completely transparent + float finalAlpha = color.a * smoothedAlpha * itemOpacity * ubuf.qt_Opacity; + fragColor = vec4(color.rgb * finalAlpha, finalAlpha); +} \ No newline at end of file diff --git a/Shaders/qsb/circled_image.frag.qsb b/Shaders/qsb/circled_image.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..37a99ef08dbbc9febf0363349dbb46c1cdf5b81a GIT binary patch literal 1717 zcmV;m21@w=02g?8ob6caZ_`#3Kh2{pZU^IC#(>K@z%8k9Lef`AAEYplhRSHgCPiK3 zI(F)j*ulOo=}@If)4p!s_P6Zw{-;U%GHp87$LWn57+W>Ord=tG&+k0$dEV;=0FD7b z1^|WtU;>^4hdOM61zo6u2Ojv)0385Q006(!f&mK+oB|tsFrX=vTS7fD|EH?Muni&D z05A$+@A-iwl@=RB9zFsCIQZZ|7XZdcVnvdEKYlnvD(lbz159v$!yawA5_^|i6W~Bw z1Y!+TTzi$G9MU)h;6VsJm|y{bO7SD_$ZMFs8xo|aQ;{qkOEE1X)_z2p7jKD9`9!OsW5T7B5{;0=K z!wfai1IUPHTvo;!xhQ_>SLU^u8{vs z%HLVYz;((6uA4;Pppl$_4CH8U^yf0wBu73(dCY)D`J16VkiJD2?xE8jan6v=OF1vl z^RUF9C3)m8l0O&8KGJ2vFlTp27wK8Z3S03ABlat#L_e0PzQ?3`V!ci#=rapKl2}L2}4jg0%4Agk<9s`85v1B9_w>|6Sst&Pl>i2W2M6 z_i-2%^aaw%Q;(e|8T0}9A5bq|AbV#BLwi?fj;=~RO;TK+LssPeW0IL9{Y!+cLRREO zrCi;Re4HXYrl%>t-;qx@NoJbtU@zRJ{>e*vMZ#mf66qEty*bjGmGtHbkM$Nvcb;On zMi|Otj1`Ju9Y#f8EfH3c<}4-IU#5Qe9;rBnN^bcjC{B&U1%{@C2UB-)(FG> z9>a+6?;hz_iN8v5l!^Bn48eSK8f564AELX2j)>Hkle?B5IG)S$x~_D5%X9>_<1}|V z%x-xGFCT&(mzNbK;C^UwwjJ8aONBAU+6FJO=e+*N;J(u>3rtu*aJ^%g4&Reer)@N? z9==~zl+bl-&u^nm7H)^O1VuXsM!VCp{K6UwywEqTvZCx7{+{DD*{)?4aHCDDTlGA@ zp(D?88Vn;>r^rVk74EW1pD3FV3{}@kQ?iHkh7)kZH7!qk>73UUk%`X(@^7+}@3q`$HSeTukE9IE3mGTi&UntC$^0#N_@`BOZ zkM`KL%pxmd^et8qK9R-l-q&ne+AJqMPnDH@MM-oXzQxf>I z`oE9<)qV7%)JMgGJ}SKnedM@(X3+B|Iy>=tu|FJnMdd+yioClfmWts zZilvQ`J&v5%(t3Oz%5_h*5e}i-ivzM_{#I6fQsFs@UGr#GPcM%hWX61?ZD!y*&lu^ zN`WL~i-%?9OeCeXJ&dQUD4X%+a3}ImBaZujrbnEl2>X1{XE?89S5p~iz2)l7>D zsU}Kz)Vb^HwjTJpxr@AeiE|MS_JbX>C)HY|3hZE>Sj4|t>HY%eJX zc;K`fxA2d)v!p0tfbX@g(YAt)VOmUR4SMnJ9YaUD=Ey91uVX~78njwX)LPZ2wzdXn zI__)6yjBYX%U4^T=^m(~>9*xcTSrf7Y>+uFtJUIaZ*Aq6>A5%}A_><;kFr`VI=6*i zx!&7yBRUX!=V7fDUsRD?eDp=^jdrJn5fUn7;^sT>4nGz4a=(1&JaOE-ob6iudmF_O9)BApE#HuoLN_s_rywhe>+oTtBqSyuZNSDhB;bbQ ztXoM3(w%a*=g5ITfTlnxv_JL#>R;3TLg_Pm@5-yQ>;x#K^zl8CL;O<7Y(@BzynmLm^^A|<(5{@%->OU zoYpC&IuT7$+IwELP~&=l%)^(-BSkR<)Fq;QK%BPF_nC)>pt49E@~B1uDcZ!Kr;T^{ z>j4du)TYZ6kWY%5P+8bX<)NO+1(Fm{gPNqEbYUl@160#N3Q1B-%jA(y0i~2c@zTzU z0kw6c<-nOO?WAbOd_N@=QA#n@ND`5Q1!CFvzIeI#^0!#~pBx#Unqd3;Xp;U4?)@~Z zN8Cr_`v3EnRxXtj<(FqqaN^O=X{6`zy)>bx#dq5aLc)sN)+;`u`4en|MyQOey+EV- znXfTTkM-yYWQxt3!vBUh1m0;R3@G?C^p{RBYHL7U3O{v3xtLl~dw8Ef9Z!SlE!e+KJn=y~{K=u=ov);|l|6BwWA1#3LEdoG*) zDMJUEymgB8Bk1y+57Iq!4Zh@eqoE(85nWyx$NF3|_=+(;(<&G5&D?x$!5-`X0kRVG znZ6Akug@Rh57W=kn2wbxG0giX4eMC2F6YJ5RxCJ2=5q8&i)Q?p9DOlIzn0U#lB3_w z;k_JP%i(2?Zdx?2MJFfkf_{<4bgs-Izc`1gSkDvS8_cu7JY!*=1AmFew7ut{$NZBt zqT}|wW#a|dn73?H!Dsv&XoEiu{3#3nBKV9y37Ta*{!7SH^*hi{SUy|=-(X$_=8%PX1^oRqq3K2Brvv?0EI*eZ=le78UWG49@be;g z#?NKsm7!OVcekOx3f{6c&s7WmI`qE+{xW#yfPW18>j206f6=g}dF^>Q@NPeZ5`OwOHoqF^wy*9N^nD#;90DFEh01;;ftzpy!FKCsTRew(JP9lHOhfvHp^J5 zG!BK+l3_zNownC?{A@g@Qmzz>tkn*}Oi=EYgkRn)mV}mxX*e-oxLGJ{M1e0jYn(ox zINUtEsAg6dsdZLbyF zkbW-;eL*=UcJad+uj9pDo8z*PXS&BlwdamKbS9k<)oo7LDAx~o%hz}&{W+}lOJ)%B zFw?idNjr*EQ~EEqI!#Yh#qlyPM#fE)>~Id2MEyFaiqjjaSlSwPYm&2~7PX>SRE3gV zmBw;r!O1pYsc*IW9HB<6cSJD~Wq0ajztP&yx5zt0aE2 zRT5A9pOnNP9Q1-1r7CN0_1J5i<-qN|H154L?!7erFfWb&?N`R$rv4tKaTrJ7<`=a$ zYU^peE@NGG_}jY?Bud84dd0kk#L;!P?Y$Gl*_>v(v)VhiS1-k!=yL^+tGbrLwWr50oJV=ox}6T9~bRa^ZD>X7Mzo) z&~_*lM`R@{;Va5hQn(q&MP0YS34;rX@>EdEe96~%Th?Fb(bra%E-t_Bo{ggzD$ZT@NaN zx#Lkqrd95CcRQ!3lq>aex4X+JMSb8g1y9xOJF9?O)@en?arzUul+V#SomaSbe_qj- zn-+BNiAAM+f~Vex+wsI=mqUV{jUA3FV0yO$i~4`~!~#R+&n#^A|JtcVeOm{r-&G63 z)-z2gt0CpAusFGr@DwV%^vK_@T!d{_m@lK zB(`wI7%+5ZA<{$!vN`3xd2ZEQtK9S(xM?@tQT#rEYy9h}PV6<>o~X6Fup#`wYlKn4 zRXu2gQ7nCNc=~cXNRl9IOn2f)MQXDnlf#9=R1nr$sV~I~ZK=GLn<&2(te>>_NznF> z@jvC}$wDDz6C(85GU<3VDYVw0f V)?6pu6>I*2BCn&be*?wwtCWWGn>qjh literal 0 HcmV?d00001 diff --git a/Widgets/NImageCircled.qml b/Widgets/NImageCircled.qml new file mode 100644 index 0000000..7220a52 --- /dev/null +++ b/Widgets/NImageCircled.qml @@ -0,0 +1,73 @@ +import QtQuick +import QtQuick.Effects +import Quickshell +import Quickshell.Widgets +import qs.Commons +import qs.Services + +Rectangle { + id: root + + property string imagePath: "" + property string fallbackIcon: "" + property color borderColor: Color.transparent + property real borderWidth: 0 + + color: Color.transparent + radius: parent.width * 0.5 + anchors.margins: Style.marginXXS * scaling + + Rectangle { + color: Color.transparent + anchors.fill: parent + + Image { + id: img + anchors.fill: parent + source: imagePath + visible: false // Hide since we're using it as shader source + mipmap: true + smooth: true + asynchronous: true + antialiasing: true + fillMode: Image.PreserveAspectCrop + } + + ShaderEffect { + anchors.fill: parent + + property var source: ShaderEffectSource { + sourceItem: img + hideSource: true + live: true + recursive: false + format: ShaderEffectSource.RGBA + } + + property real imageOpacity: root.opacity + fragmentShader: "file:Shaders/qsb/circled_image.frag.qsb" + supportsAtlasTextures: false + blending: true + } + + // Fallback icon + NIcon { + anchors.centerIn: parent + text: fallbackIcon + font.pointSize: Style.fontSizeXXL * scaling + visible: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "") + z: 0 + } + } + + //Border + Rectangle { + anchors.fill: parent + radius: parent.radius + color: Color.transparent + border.color: parent.borderColor + border.width: parent.borderWidth + antialiasing: true + z: 10 + } +} diff --git a/Widgets/NImageRounded.qml b/Widgets/NImageRounded.qml index c7fbde9..18f047f 100644 --- a/Widgets/NImageRounded.qml +++ b/Widgets/NImageRounded.qml @@ -23,13 +23,12 @@ Rectangle { Rectangle { color: Color.transparent anchors.fill: parent - anchors.margins: borderWidth Image { id: img anchors.fill: parent source: imagePath - visible: false + visible: false // Hide since we're using it as shader source mipmap: true smooth: true asynchronous: true @@ -37,24 +36,33 @@ Rectangle { fillMode: Image.PreserveAspectCrop } - MultiEffect { + ShaderEffect { anchors.fill: parent - source: img - maskEnabled: true - maskSource: mask - maskSpreadAtMax: 0.75 - visible: imagePath !== "" - } - Item { - id: mask - anchors.fill: parent - layer.enabled: true - visible: false + property var source: ShaderEffectSource { + sourceItem: img + hideSource: true + live: true + recursive: false + format: ShaderEffectSource.RGBA + } + + // Use custom property names to avoid conflicts with final properties + property real itemWidth: root.width + property real itemHeight: root.height + property real cornerRadius: root.radius + property real imageOpacity: root.opacity + fragmentShader: "file:Shaders/qsb/rounded_image.frag.qsb" + + // Qt6 specific properties - ensure proper blending + supportsAtlasTextures: false + blending: true + // Make sure the background is transparent Rectangle { + id: background anchors.fill: parent - radius: scaledRadius - antialiasing: true + color: "transparent" + z: -1 } } From 931e499d67c8f6ff4c849d13582206acc980cea5 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 22 Aug 2025 20:07:06 +0200 Subject: [PATCH 11/16] Remove some Settings entries --- Commons/Settings.qml | 7 +------ Modules/Bar/Widgets/MediaMini.qml | 3 ++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 77aaaaf..dcb664a 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -120,13 +120,8 @@ Singleton { bar: JsonObject { property string position: "top" // Possible values: "top", "bottom" - property bool showActiveWindow: true property bool showActiveWindowIcon: true - property bool showSystemInfo: false - property bool showMedia: false - property bool showBrightness: true - property bool showNotificationsHistory: true - property bool showTray: true + property bool alwaysShowBatteryPercentage: false property real backgroundOpacity: 1.0 property list monitors: [] diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index a061031..7f51917 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -11,7 +11,8 @@ Row { id: root anchors.verticalCenter: parent.verticalCenter spacing: Style.marginS * scaling - visible: Settings.data.bar.showMedia && (MediaService.canPlay || MediaService.canPause) + visible: MediaService.currentPlayer !== null + width: MediaService.currentPlayer !== null ? implicitWidth : 0 function getTitle() { return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "") From 377ed4a6270f0198e77bdda0c3725d32e1746f29 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 22 Aug 2025 14:31:44 -0400 Subject: [PATCH 12/16] Fix shaders path --- Widgets/NImageCircled.qml | 2 +- Widgets/NImageRounded.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Widgets/NImageCircled.qml b/Widgets/NImageCircled.qml index 7220a52..7279c08 100644 --- a/Widgets/NImageCircled.qml +++ b/Widgets/NImageCircled.qml @@ -45,7 +45,7 @@ Rectangle { } property real imageOpacity: root.opacity - fragmentShader: "file:Shaders/qsb/circled_image.frag.qsb" + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/circled_image.frag.qsb") supportsAtlasTextures: false blending: true } diff --git a/Widgets/NImageRounded.qml b/Widgets/NImageRounded.qml index 18f047f..14ff263 100644 --- a/Widgets/NImageRounded.qml +++ b/Widgets/NImageRounded.qml @@ -52,7 +52,7 @@ Rectangle { property real itemHeight: root.height property real cornerRadius: root.radius property real imageOpacity: root.opacity - fragmentShader: "file:Shaders/qsb/rounded_image.frag.qsb" + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/rounded_image.frag.qsb") // Qt6 specific properties - ensure proper blending supportsAtlasTextures: false From 3b256aa50da9b18944a73d4c39913984f43c0911 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 22 Aug 2025 14:41:46 -0400 Subject: [PATCH 13/16] Better Bar settings layout --- Modules/SettingsPanel/Tabs/BarTab.qml | 60 ++++++++++++++------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index c751dcb..fe162d8 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -33,12 +33,6 @@ ColumnLayout { spacing: Style.marginL * scaling Layout.fillWidth: true - NText { - text: "Bar & Widgets" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } ColumnLayout { spacing: Style.marginXXS * scaling @@ -78,25 +72,7 @@ ColumnLayout { } } - NToggle { - label: "Show Active Window's Icon" - description: "Display the app icon next to the title of the currently focused window." - checked: Settings.data.bar.showActiveWindowIcon - onToggled: checked => { - Settings.data.bar.showActiveWindowIcon = checked - } - } - - NToggle { - label: "Show Battery Percentage" - description: "Show battery percentage at all times." - checked: Settings.data.bar.alwaysShowBatteryPercentage - onToggled: checked => { - Settings.data.bar.alwaysShowBatteryPercentage = checked - } - } - - ColumnLayout { + ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true @@ -135,20 +111,46 @@ ColumnLayout { } } - // Widget Management Section + + NToggle { + label: "Show Active Window's Icon" + description: "Display the app icon next to the title of the currently focused window." + checked: Settings.data.bar.showActiveWindowIcon + onToggled: checked => { + Settings.data.bar.showActiveWindowIcon = checked + } + } + + NToggle { + label: "Show Battery Percentage" + description: "Show battery percentage at all times." + checked: Settings.data.bar.alwaysShowBatteryPercentage + onToggled: checked => { + Settings.data.bar.alwaysShowBatteryPercentage = checked + } + } + + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginL * scaling + Layout.bottomMargin: Style.marginL * scaling + } + + // Widgets Management Section ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true NText { - text: "Widget Management" + text: "Widgets Positioning" font.pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold color: Color.mOnSurface } NText { - text: "Configure which widgets appear in each section of the bar. Use the arrow buttons to reorder widgets, or the add/remove buttons to manage them." + text: "Add, remove, or reorder widgets in each section of the bar using the control buttons." font.pointSize: Style.fontSizeXS * scaling color: Color.mOnSurfaceVariant wrapMode: Text.WordWrap @@ -159,6 +161,7 @@ ColumnLayout { ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true + Layout.topMargin: Style.marginM * scaling spacing: Style.marginM * scaling // Left Section @@ -206,6 +209,7 @@ ColumnLayout { placeholder: "Add widget to left section" onSelected: key => { addWidgetToSection(key, "left") + reset() // Reset selection } } } From 5994bd7929c2e96a451982409a97acca937dfc8f Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 22 Aug 2025 14:50:14 -0400 Subject: [PATCH 14/16] Fixed warnings due to delete settings --- Commons/Settings.qml | 2 -- Modules/Bar/Widgets/ActiveWindow.qml | 2 +- Modules/Bar/Widgets/NotificationHistory.qml | 1 - Modules/Bar/Widgets/SystemMonitor.qml | 1 - Modules/Bar/Widgets/Tray.qml | 4 +--- 5 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index dcb664a..6797201 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -121,14 +121,12 @@ Singleton { bar: JsonObject { property string position: "top" // Possible values: "top", "bottom" property bool showActiveWindowIcon: true - property bool alwaysShowBatteryPercentage: false property real backgroundOpacity: 1.0 property list monitors: [] // Widget configuration for modular bar system property JsonObject widgets - widgets: JsonObject { property list left: ["SystemMonitor", "ActiveWindow", "MediaMini"] property list center: ["Workspace"] diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index ec7b42b..25af960 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -11,7 +11,7 @@ Row { id: root anchors.verticalCenter: parent.verticalCenter spacing: Style.marginS * scaling - visible: (Settings.data.bar.showActiveWindow && getTitle() !== "") + visible: getTitle() !== "" property bool showingFullTitle: false property int lastWindowIndex: -1 diff --git a/Modules/Bar/Widgets/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml index ba794b4..8cf0502 100644 --- a/Modules/Bar/Widgets/NotificationHistory.qml +++ b/Modules/Bar/Widgets/NotificationHistory.qml @@ -10,7 +10,6 @@ import qs.Widgets NIconButton { id: root - visible: Settings.data.bar.showNotificationsHistory sizeMultiplier: 0.8 icon: "notifications" tooltipText: "Notification History" diff --git a/Modules/Bar/Widgets/SystemMonitor.qml b/Modules/Bar/Widgets/SystemMonitor.qml index 860adf3..18bb8b6 100644 --- a/Modules/Bar/Widgets/SystemMonitor.qml +++ b/Modules/Bar/Widgets/SystemMonitor.qml @@ -8,7 +8,6 @@ Row { id: root anchors.verticalCenter: parent.verticalCenter spacing: Style.marginS * scaling - visible: (Settings.data.bar.showSystemInfo) Rectangle { // Let the Rectangle size itself based on its content (the Row) diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index d85a174..553e56b 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -12,10 +12,8 @@ import qs.Widgets Rectangle { readonly property real itemSize: 24 * scaling - visible: Settings.data.bar.showTray && (SystemTray.items.values.length > 0) - + visible: SystemTray.items.values.length > 0 width: tray.width + Style.marginM * scaling * 2 - height: Math.round(Style.capsuleHeight * scaling) radius: Math.round(Style.radiusM * scaling) color: Color.mSurfaceVariant From b48e2422fa54c019bcd6da6dd494606fba6c1126 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 22 Aug 2025 14:50:20 -0400 Subject: [PATCH 15/16] Rosepine update --- Assets/ColorScheme/Rosepine.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Assets/ColorScheme/Rosepine.json b/Assets/ColorScheme/Rosepine.json index 891b544..37e9b3a 100644 --- a/Assets/ColorScheme/Rosepine.json +++ b/Assets/ColorScheme/Rosepine.json @@ -1,19 +1,19 @@ { "dark": { "mPrimary": "#ebbcba", - "mOnPrimary": "#191724", + "mOnPrimary": "#1f1d2e", "mSecondary": "#9ccfd8", - "mOnSecondary": "#191724", + "mOnSecondary": "#1f1d2e", "mTertiary": "#f6c177", - "mOnTertiary": "#191724", + "mOnTertiary": "#1f1d2e", "mError": "#eb6f92", "mOnError": "#1f1d2e", - "mSurface": "#191724", + "mSurface": "#1f1d2e", "mOnSurface": "#e0def4", "mSurfaceVariant": "#26233a", "mOnSurfaceVariant": "#908caa", "mOutline": "#403d52", - "mShadow": "#191724" + "mShadow": "#1f1d2e" }, "light": { "mPrimary": "#d46e6b", From cbbfbcf671d0f523ece0a9d29673d625b6667608 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Fri, 22 Aug 2025 21:01:00 +0200 Subject: [PATCH 16/16] Add NWidgetCard, fix some BarTab things --- Modules/SettingsPanel/Tabs/BarTab.qml | 398 ++------------------------ Widgets/NWidgetCard.qml | 158 ++++++++++ 2 files changed, 182 insertions(+), 374 deletions(-) create mode 100644 Widgets/NWidgetCard.qml diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index c751dcb..3209637 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -162,386 +162,36 @@ ColumnLayout { spacing: Style.marginM * scaling // Left Section - NCard { - Layout.fillWidth: true - Layout.minimumHeight: { - var widgetCount = Settings.data.bar.widgets.left.length - if (widgetCount === 0) - return 140 * scaling - - var availableWidth = scrollView.availableWidth - (Style.marginM * scaling * 2) // Card margins - var avgWidgetWidth = 150 * scaling // Estimated widget width including spacing - var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) - var rows = Math.ceil(widgetCount / widgetsPerRow) - - // Header (50) + spacing (20) + (rows * widget height) + (rows-1 * spacing) + bottom margin (20) - return (50 + 20 + (rows * 48) + ((rows - 1) * Style.marginS) + 20) * scaling - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: Style.marginM * scaling - spacing: Style.marginM * scaling - - RowLayout { - Layout.fillWidth: true - - NText { - text: "Left Section" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - Item { - Layout.fillWidth: true - } - - NComboBox { - id: leftComboBox - width: 120 * scaling - model: availableWidgets - label: "" - description: "" - placeholder: "Add widget to left section" - onSelected: key => { - addWidgetToSection(key, "left") - } - } - } - - Flow { - id: leftWidgetsFlow - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: 65 * scaling - spacing: Style.marginS * scaling - flow: Flow.LeftToRight - - Repeater { - model: Settings.data.bar.widgets.left - delegate: Rectangle { - width: widgetContent.implicitWidth + 16 * scaling - height: 48 * scaling - radius: Style.radiusS * scaling - color: Color.mPrimary - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - - RowLayout { - id: widgetContent - anchors.centerIn: parent - spacing: Style.marginXS * scaling - - NIconButton { - icon: "chevron_left" - size: 20 * scaling - colorBg: Color.applyOpacity(Color.mOnPrimary, "20") - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - enabled: index > 0 - onClicked: { - if (index > 0) { - reorderWidgetInSection("left", index, index - 1) - } - } - } - - NText { - text: modelData - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnPrimary - horizontalAlignment: Text.AlignHCenter - } - - NIconButton { - icon: "chevron_right" - size: 20 * scaling - colorBg: Color.applyOpacity(Color.mOnPrimary, "20") - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - enabled: index < Settings.data.bar.widgets.left.length - 1 - onClicked: { - if (index < Settings.data.bar.widgets.left.length - 1) { - reorderWidgetInSection("left", index, index + 1) - } - } - } - - NIconButton { - icon: "close" - size: 20 * scaling - colorBg: Color.applyOpacity(Color.mOnPrimary, "20") - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - onClicked: { - removeWidgetFromSection("left", index) - } - } - } - } - } - } - } + NWidgetCard { + sectionName: "Left" + widgetModel: Settings.data.bar.widgets.left + availableWidgets: availableWidgets + scrollView: scrollView + onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section) + onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) } // Center Section - NCard { - Layout.fillWidth: true - Layout.minimumHeight: { - var widgetCount = Settings.data.bar.widgets.center.length - if (widgetCount === 0) - return 140 * scaling - - var availableWidth = scrollView.availableWidth - (Style.marginM * scaling * 2) // Card margins - var avgWidgetWidth = 150 * scaling // Estimated widget width including spacing - var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) - var rows = Math.ceil(widgetCount / widgetsPerRow) - - // Header (50) + spacing (20) + (rows * widget height) + (rows-1 * spacing) + bottom margin (20) - return (50 + 20 + (rows * 48) + ((rows - 1) * Style.marginS) + 20) * scaling - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: Style.marginM * scaling - spacing: Style.marginM * scaling - - RowLayout { - Layout.fillWidth: true - - NText { - text: "Center Section" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - Item { - Layout.fillWidth: true - } - - NComboBox { - id: centerComboBox - width: 120 * scaling - model: availableWidgets - label: "" - description: "" - placeholder: "Add widget to center section" - onSelected: key => { - addWidgetToSection(key, "center") - reset() // Reset selection - } - } - } - - Flow { - id: centerWidgetsFlow - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: 65 * scaling - spacing: Style.marginS * scaling - flow: Flow.LeftToRight - - Repeater { - model: Settings.data.bar.widgets.center - delegate: Rectangle { - width: widgetContent.implicitWidth + 16 * scaling - height: 48 * scaling - radius: Style.radiusS * scaling - color: Color.mPrimary - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - - RowLayout { - id: widgetContent - anchors.centerIn: parent - spacing: Style.marginXS * scaling - - NIconButton { - icon: "chevron_left" - size: 20 * scaling - colorBg: Color.applyOpacity(Color.mOnPrimary, "20") - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - enabled: index > 0 - onClicked: { - if (index > 0) { - reorderWidgetInSection("center", index, index - 1) - } - } - } - - NText { - text: modelData - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnPrimary - horizontalAlignment: Text.AlignHCenter - } - - NIconButton { - icon: "chevron_right" - size: 20 * scaling - colorBg: Color.applyOpacity(Color.mOnPrimary, "20") - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - enabled: index < Settings.data.bar.widgets.center.length - 1 - onClicked: { - if (index < Settings.data.bar.widgets.center.length - 1) { - reorderWidgetInSection("center", index, index + 1) - } - } - } - - NIconButton { - icon: "close" - size: 20 * scaling - colorBg: Color.applyOpacity(Color.mOnPrimary, "20") - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - onClicked: { - removeWidgetFromSection("center", index) - } - } - } - } - } - } - } + NWidgetCard { + sectionName: "Center" + widgetModel: Settings.data.bar.widgets.center + availableWidgets: availableWidgets + scrollView: scrollView + onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section) + onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) } // Right Section - NCard { - Layout.fillWidth: true - Layout.minimumHeight: { - var widgetCount = Settings.data.bar.widgets.right.length - if (widgetCount === 0) - return 140 * scaling - - var availableWidth = scrollView.availableWidth - (Style.marginM * scaling * 2) // Card margins - var avgWidgetWidth = 150 * scaling // Estimated widget width including spacing - var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) - var rows = Math.ceil(widgetCount / widgetsPerRow) - - // Header (50) + spacing (20) + (rows * widget height) + (rows-1 * spacing) + bottom margin (20) - return (50 + 20 + (rows * 48) + ((rows - 1) * Style.marginS) + 20) * scaling - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: Style.marginM * scaling - spacing: Style.marginM * scaling - - RowLayout { - Layout.fillWidth: true - - NText { - text: "Right Section" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - Item { - Layout.fillWidth: true - } - - NComboBox { - id: rightComboBox - width: 120 * scaling - model: availableWidgets - label: "" - description: "" - placeholder: "Add widget to right section" - onSelected: key => { - addWidgetToSection(key, "right") - reset() // Reset selection - } - } - } - - Flow { - id: rightWidgetsFlow - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: 65 * scaling - spacing: Style.marginS * scaling - flow: Flow.LeftToRight - - Repeater { - model: Settings.data.bar.widgets.right - delegate: Rectangle { - width: widgetContent.implicitWidth + 16 * scaling - height: 48 * scaling - radius: Style.radiusS * scaling - color: Color.mPrimary - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - - RowLayout { - id: widgetContent - anchors.centerIn: parent - spacing: Style.marginXS * scaling - - NIconButton { - icon: "chevron_left" - size: 20 * scaling - colorBg: Color.applyOpacity(Color.mOnPrimary, "20") - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - enabled: index > 0 - onClicked: { - if (index > 0) { - reorderWidgetInSection("right", index, index - 1) - } - } - } - - NText { - text: modelData - font.pointSize: Style.fontSizeS * scaling - color: Color.mOnPrimary - horizontalAlignment: Text.AlignHCenter - } - - NIconButton { - icon: "chevron_right" - size: 20 * scaling - colorBg: Color.applyOpacity(Color.mOnPrimary, "20") - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - enabled: index < Settings.data.bar.widgets.right.length - 1 - onClicked: { - if (index < Settings.data.bar.widgets.right.length - 1) { - reorderWidgetInSection("right", index, index + 1) - } - } - } - - NIconButton { - icon: "close" - size: 20 * scaling - colorBg: Color.applyOpacity(Color.mOnPrimary, "20") - colorFg: Color.mOnPrimary - colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") - colorFgHover: Color.mOnPrimary - onClicked: { - removeWidgetFromSection("right", index) - } - } - } - } - } - } - } + NWidgetCard { + sectionName: "Right" + widgetModel: Settings.data.bar.widgets.right + availableWidgets: availableWidgets + scrollView: scrollView + onAddWidget: (widgetName, section) => addWidgetToSection(widgetName, section) + onRemoveWidget: (section, index) => removeWidgetFromSection(section, index) + onReorderWidget: (section, fromIndex, toIndex) => reorderWidgetInSection(section, fromIndex, toIndex) } } } diff --git a/Widgets/NWidgetCard.qml b/Widgets/NWidgetCard.qml new file mode 100644 index 0000000..2808465 --- /dev/null +++ b/Widgets/NWidgetCard.qml @@ -0,0 +1,158 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +NCard { + id: root + + property string sectionName: "" + property var widgetModel: [] + property var availableWidgets: [] + property var scrollView: null + + signal addWidget(string widgetName, string section) + signal removeWidget(string section, int index) + signal reorderWidget(string section, int fromIndex, int toIndex) + + Layout.fillWidth: true + Layout.minimumHeight: { + var widgetCount = widgetModel.length + if (widgetCount === 0) + return 140 * scaling + + var availableWidth = scrollView ? scrollView.availableWidth - (Style.marginM * scaling * 2) : 400 * scaling + var avgWidgetWidth = 150 * scaling + var widgetsPerRow = Math.max(1, Math.floor(availableWidth / avgWidgetWidth)) + var rows = Math.ceil(widgetCount / widgetsPerRow) + + return (50 + 20 + (rows * 48) + ((rows - 1) * Style.marginS) + 20) * scaling + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginM * scaling + spacing: Style.marginM * scaling + + RowLayout { + Layout.fillWidth: true + + NText { + text: sectionName + " Section" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.alignment: Qt.AlignVCenter + } + + Item { + Layout.fillWidth: true + } + + NComboBox { + id: comboBox + width: 120 * scaling + model: availableWidgets + label: "" + description: "" + placeholder: "Add widget to " + sectionName.toLowerCase() + " section" + onSelected: key => { + comboBox.selectedKey = key + } + } + + NIconButton { + icon: "add" + size: 24 * scaling + colorBg: Color.mPrimary + colorFg: Color.mOnPrimary + colorBgHover: Color.mPrimaryContainer + colorFgHover: Color.mOnPrimaryContainer + enabled: comboBox.selectedKey !== "" + Layout.alignment: Qt.AlignVCenter + onClicked: { + if (comboBox.selectedKey !== "") { + addWidget(comboBox.selectedKey, sectionName.toLowerCase()) + comboBox.reset() + } + } + } + } + + Flow { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: 65 * scaling + spacing: Style.marginS * scaling + flow: Flow.LeftToRight + + Repeater { + model: widgetModel + delegate: Rectangle { + width: widgetContent.implicitWidth + 16 * scaling + height: 48 * scaling + radius: Style.radiusS * scaling + color: Color.mPrimary + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + + RowLayout { + id: widgetContent + anchors.centerIn: parent + spacing: Style.marginXS * scaling + + NIconButton { + icon: "chevron_left" + size: 20 * scaling + colorBg: Color.applyOpacity(Color.mOnPrimary, "20") + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + enabled: index > 0 + onClicked: { + if (index > 0) { + reorderWidget(sectionName.toLowerCase(), index, index - 1) + } + } + } + + NText { + text: modelData + font.pointSize: Style.fontSizeS * scaling + color: Color.mOnPrimary + horizontalAlignment: Text.AlignHCenter + } + + NIconButton { + icon: "chevron_right" + size: 20 * scaling + colorBg: Color.applyOpacity(Color.mOnPrimary, "20") + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + enabled: index < widgetModel.length - 1 + onClicked: { + if (index < widgetModel.length - 1) { + reorderWidget(sectionName.toLowerCase(), index, index + 1) + } + } + } + + NIconButton { + icon: "close" + size: 20 * scaling + colorBg: Color.applyOpacity(Color.mOnPrimary, "20") + colorFg: Color.mOnPrimary + colorBgHover: Color.applyOpacity(Color.mOnPrimary, "40") + colorFgHover: Color.mOnPrimary + onClicked: { + removeWidget(sectionName.toLowerCase(), index) + } + } + } + } + } + } + } +}