diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 65f900e..c84d2b0 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -18,10 +18,44 @@ RowLayout { spacing: Style.marginS * scaling visible: getTitle() !== "" + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool userShowIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : ((Settings.data.bar.showActiveWindowIcon !== undefined) ? Settings.data.bar.showActiveWindowIcon : BarWidgetRegistry.widgetMetadata["ActiveWindow"].showIcon) + function getTitle() { return CompositorService.focusedWindowTitle !== "(No active window)" ? CompositorService.focusedWindowTitle : "" } + Component.onCompleted: { + try { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + if (widgets[sectionWidgetIndex].showIcon === undefined + && Settings.data.bar.showActiveWindowIcon !== undefined) { + widgets[sectionWidgetIndex].showIcon = Settings.data.bar.showActiveWindowIcon + } + } + } + } catch (e) { + + } + } + function getAppIcon() { // Try CompositorService first const focusedWindow = CompositorService.getFocusedWindow() @@ -74,7 +108,7 @@ RowLayout { Layout.preferredWidth: Style.fontSizeL * scaling * 1.2 Layout.preferredHeight: Style.fontSizeL * scaling * 1.2 Layout.alignment: Qt.AlignVCenter - visible: getTitle() !== "" && Settings.data.bar.showActiveWindowIcon + visible: getTitle() !== "" && userShowIcon IconImage { id: windowIcon diff --git a/Modules/Bar/Widgets/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml index c16866c..2c299f3 100644 --- a/Modules/Bar/Widgets/Brightness.qml +++ b/Modules/Bar/Widgets/Brightness.qml @@ -14,6 +14,20 @@ Item { property int sectionWidgetIndex: 0 property int sectionWidgetsCount: 0 + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool userAlwaysShowPercentage: (widgetSettings.alwaysShowPercentage + !== undefined) ? widgetSettings.alwaysShowPercentage : BarWidgetRegistry.widgetMetadata["Brightness"].alwaysShowPercentage + // Used to avoid opening the pill on Quickshell startup property bool firstBrightnessReceived: false @@ -57,6 +71,23 @@ Item { onTriggered: pill.hide() } + Component.onCompleted: { + try { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + if (widgets[sectionWidgetIndex].alwaysShowPercentage === undefined + && Settings.data.bar.alwaysShowBatteryPercentage !== undefined) { + widgets[sectionWidgetIndex].alwaysShowPercentage = Settings.data.bar.alwaysShowBatteryPercentage + } + } + } + } catch (e) { + + } + } + NPill { id: pill @@ -69,6 +100,7 @@ Item { var monitor = getMonitor() return monitor ? (Math.round(monitor.brightness * 100) + "%") : "" } + forceOpen: userAlwaysShowPercentage tooltipText: { var monitor = getMonitor() if (!monitor) diff --git a/Modules/Bar/Widgets/Clock.qml b/Modules/Bar/Widgets/Clock.qml index ee57b57..38b964b 100644 --- a/Modules/Bar/Widgets/Clock.qml +++ b/Modules/Bar/Widgets/Clock.qml @@ -10,6 +10,29 @@ Rectangle { property ShellScreen screen property real scaling: 1.0 + // Widget properties passed from Bar.qml for per-instance settings + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + // Resolve per-instance widget settings from Settings.data + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + // Use settings or defaults from BarWidgetRegistry metadata + readonly property bool userShowDate: (widgetSettings.showDate + !== undefined) ? widgetSettings.showDate : BarWidgetRegistry.widgetMetadata["Clock"].showDate + readonly property bool userUse12h: (widgetSettings.use12HourClock !== undefined) ? widgetSettings.use12HourClock : BarWidgetRegistry.widgetMetadata["Clock"].use12HourClock + readonly property bool userShowSeconds: (widgetSettings.showSeconds !== undefined) ? widgetSettings.showSeconds : BarWidgetRegistry.widgetMetadata["Clock"].showSeconds + implicitWidth: clock.width + Style.marginM * 2 * scaling implicitHeight: Math.round(Style.capsuleHeight * scaling) radius: Math.round(Style.radiusM * scaling) @@ -20,6 +43,10 @@ Rectangle { id: clock anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter + // Per-instance overrides to Time formatting + showDate: userShowDate + use12h: userUse12h + showSeconds: userShowSeconds NTooltip { id: tooltip diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 2483dbc..76a5655 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -20,6 +20,28 @@ RowLayout { visible: MediaService.currentPlayer !== null && MediaService.canPlay Layout.preferredWidth: MediaService.currentPlayer !== null && MediaService.canPlay ? implicitWidth : 0 + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool userShowAlbumArt: (widgetSettings.showAlbumArt !== undefined) ? widgetSettings.showAlbumArt : ((Settings.data.audio.showMiniplayerAlbumArt !== undefined) ? Settings.data.audio.showMiniplayerAlbumArt : BarWidgetRegistry.widgetMetadata["MediaMini"].showAlbumArt) + readonly property bool userShowVisualizer: (widgetSettings.showVisualizer !== undefined) ? widgetSettings.showVisualizer : ((Settings.data.audio.showMiniplayerCava !== undefined) ? Settings.data.audio.showMiniplayerCava : BarWidgetRegistry.widgetMetadata["MediaMini"].showVisualizer) + readonly property string userVisualizerType: (widgetSettings.visualizerType !== undefined + && widgetSettings.visualizerType + !== "") ? widgetSettings.visualizerType : ((Settings.data.audio.visualizerType !== undefined + && Settings.data.audio.visualizerType !== "") ? Settings.data.audio.visualizerType : BarWidgetRegistry.widgetMetadata["MediaMini"].visualizerType) + function getTitle() { return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "") } @@ -58,8 +80,7 @@ RowLayout { Loader { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter - active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "linear" - && MediaService.isPlaying + active: userShowVisualizer && userVisualizerType == "linear" && MediaService.isPlaying z: 0 sourceComponent: LinearSpectrum { @@ -74,8 +95,7 @@ RowLayout { Loader { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter - active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "mirrored" - && MediaService.isPlaying + active: userShowVisualizer && userVisualizerType == "mirrored" && MediaService.isPlaying z: 0 sourceComponent: MirroredSpectrum { @@ -90,8 +110,7 @@ RowLayout { Loader { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter - active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "wave" - && MediaService.isPlaying + active: userShowVisualizer && userVisualizerType == "wave" && MediaService.isPlaying z: 0 sourceComponent: WaveSpectrum { @@ -115,12 +134,12 @@ RowLayout { font.pointSize: Style.fontSizeL * scaling verticalAlignment: Text.AlignVCenter Layout.alignment: Qt.AlignVCenter - visible: !Settings.data.audio.showMiniplayerAlbumArt && getTitle() !== "" && !trackArt.visible + visible: !userShowAlbumArt && getTitle() !== "" && !trackArt.visible } ColumnLayout { Layout.alignment: Qt.AlignVCenter - visible: Settings.data.audio.showMiniplayerAlbumArt + visible: userShowAlbumArt spacing: 0 Item { @@ -199,6 +218,30 @@ RowLayout { } } + Component.onCompleted: { + try { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + var w = widgets[sectionWidgetIndex] + if (w.showAlbumArt === undefined && Settings.data.audio.showMiniplayerAlbumArt !== undefined) { + w.showAlbumArt = Settings.data.audio.showMiniplayerAlbumArt + } + if (w.showVisualizer === undefined && Settings.data.audio.showMiniplayerCava !== undefined) { + w.showVisualizer = Settings.data.audio.showMiniplayerCava + } + if ((w.visualizerType === undefined || w.visualizerType === "") + && (Settings.data.audio.visualizerType !== undefined && Settings.data.audio.visualizerType !== "")) { + w.visualizerType = Settings.data.audio.visualizerType + } + } + } + } catch (e) { + + } + } + NTooltip { id: tooltip text: { diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml index f4e1c1a..75a5a20 100644 --- a/Modules/Bar/Widgets/Microphone.qml +++ b/Modules/Bar/Widgets/Microphone.qml @@ -16,6 +16,20 @@ Item { property int sectionWidgetIndex: 0 property int sectionWidgetsCount: 0 + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool userAlwaysShowPercentage: (widgetSettings.alwaysShowPercentage + !== undefined) ? widgetSettings.alwaysShowPercentage : BarWidgetRegistry.widgetMetadata["Microphone"].alwaysShowPercentage + // Used to avoid opening the pill on Quickshell startup property bool firstInputVolumeReceived: false property int wheelAccumulator: 0 @@ -69,6 +83,23 @@ Item { } } + Component.onCompleted: { + try { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + if (widgets[sectionWidgetIndex].alwaysShowPercentage === undefined + && Settings.data.bar.alwaysShowBatteryPercentage !== undefined) { + widgets[sectionWidgetIndex].alwaysShowPercentage = Settings.data.bar.alwaysShowBatteryPercentage + } + } + } + } catch (e) { + + } + } + NPill { id: pill @@ -78,6 +109,7 @@ Item { collapsedIconColor: Color.mOnSurface autoHide: false // Important to be false so we can hover as long as we want text: Math.floor(AudioService.inputVolume * 100) + "%" + forceOpen: userAlwaysShowPercentage tooltipText: "Microphone: " + Math.round(AudioService.inputVolume * 100) + "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute." diff --git a/Modules/Bar/Widgets/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml index 48a62fd..9897d05 100644 --- a/Modules/Bar/Widgets/NotificationHistory.qml +++ b/Modules/Bar/Widgets/NotificationHistory.qml @@ -13,15 +13,94 @@ NIconButton { property ShellScreen screen property real scaling: 1.0 + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool userShowUnreadBadge: (widgetSettings.showUnreadBadge !== undefined) ? widgetSettings.showUnreadBadge : BarWidgetRegistry.widgetMetadata["NotificationHistory"].showUnreadBadge + readonly property bool userHideWhenZero: (widgetSettings.hideWhenZero !== undefined) ? widgetSettings.hideWhenZero : BarWidgetRegistry.widgetMetadata["NotificationHistory"].hideWhenZero + readonly property bool userDoNotDisturb: (widgetSettings.doNotDisturb !== undefined) ? widgetSettings.doNotDisturb : BarWidgetRegistry.widgetMetadata["NotificationHistory"].doNotDisturb + + function lastSeenTs() { + return widgetSettings.lastSeenTs || 0 + } + + function computeUnreadCount() { + var since = lastSeenTs() + var count = 0 + var model = NotificationService.historyModel + for (var i = 0; i < model.count; i++) { + var item = model.get(i) + var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp + if (ts > since) + count++ + } + return count + } + sizeRatio: 0.8 - icon: Settings.data.notifications.doNotDisturb ? "notifications_off" : "notifications" - tooltipText: Settings.data.notifications.doNotDisturb ? "Notification history.\nRight-click to disable 'Do Not Disturb'." : "Notification history.\nRight-click to enable 'Do Not Disturb'." + icon: (Settings.data.notifications.doNotDisturb || userDoNotDisturb) ? "notifications_off" : "notifications" + tooltipText: (Settings.data.notifications.doNotDisturb + || userDoNotDisturb) ? "Notification history.\nRight-click to disable 'Do Not Disturb'." : "Notification history.\nRight-click to enable 'Do Not Disturb'." colorBg: Color.mSurfaceVariant - colorFg: Settings.data.notifications.doNotDisturb ? Color.mError : Color.mOnSurface + colorFg: (Settings.data.notifications.doNotDisturb || userDoNotDisturb) ? Color.mError : Color.mOnSurface colorBorder: Color.transparent colorBorderHover: Color.transparent - onClicked: PanelService.getPanel("notificationHistoryPanel")?.toggle(screen, this) + onClicked: { + // Open first using current geometry as anchor + var panel = PanelService.getPanel("notificationHistoryPanel") + panel?.toggle(screen, this) + // Update last seen right after to avoid affecting anchor calculation + Qt.callLater(function () { + try { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + widgets[sectionWidgetIndex].lastSeenTs = Time.timestamp * 1000 + } + } + } catch (e) { + + } + }) + } onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb + + Loader { + anchors.right: parent.right + anchors.top: parent.top + anchors.rightMargin: -4 * scaling + anchors.topMargin: -4 * scaling + z: 2 + active: userShowUnreadBadge && (!userHideWhenZero || computeUnreadCount() > 0) + sourceComponent: Rectangle { + width: 16 * scaling + height: 16 * scaling + radius: width / 2 + color: Color.mError + border.color: Color.mSurface + border.width: 1 + visible: computeUnreadCount() > 0 || !userHideWhenZero + NText { + anchors.centerIn: parent + text: Math.min(computeUnreadCount(), 9) + font.pointSize: Style.fontSizeXXS * scaling + color: Color.mOnError + } + } + } } diff --git a/Modules/Bar/Widgets/SidePanelToggle.qml b/Modules/Bar/Widgets/SidePanelToggle.qml index b9572fb..6c0b489 100644 --- a/Modules/Bar/Widgets/SidePanelToggle.qml +++ b/Modules/Bar/Widgets/SidePanelToggle.qml @@ -1,3 +1,4 @@ +import QtQuick import Quickshell import Quickshell.Widgets import QtQuick.Effects @@ -10,8 +11,24 @@ NIconButton { property ShellScreen screen property real scaling: 1.0 + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 - icon: Settings.data.bar.useDistroLogo ? "" : "widgets" + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool userUseDistroLogo: (widgetSettings.useDistroLogo !== undefined) ? widgetSettings.useDistroLogo : ((Settings.data.bar.useDistroLogo !== undefined) ? Settings.data.bar.useDistroLogo : BarWidgetRegistry.widgetMetadata["SidePanelToggle"].useDistroLogo) + + icon: userUseDistroLogo ? "" : "widgets" tooltipText: "Open side panel." sizeRatio: 0.8 @@ -24,14 +41,30 @@ NIconButton { onClicked: PanelService.getPanel("sidePanel")?.toggle(screen, this) onRightClicked: PanelService.getPanel("settingsPanel")?.toggle(screen) - // When enabled, draw the distro logo instead of the icon glyph + Component.onCompleted: { + try { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + if (widgets[sectionWidgetIndex].useDistroLogo === undefined + && Settings.data.bar.useDistroLogo !== undefined) { + widgets[sectionWidgetIndex].useDistroLogo = Settings.data.bar.useDistroLogo + } + } + } + } catch (e) { + + } + } + IconImage { id: logo anchors.centerIn: parent width: root.width * 0.6 height: width - source: Settings.data.bar.useDistroLogo ? DistroLogoService.osLogo : "" - visible: false //Settings.data.bar.useDistroLogo && source !== "" + source: userUseDistroLogo ? DistroLogoService.osLogo : "" + visible: userUseDistroLogo && source !== "" smooth: true } diff --git a/Modules/Bar/Widgets/SystemMonitor.qml b/Modules/Bar/Widgets/SystemMonitor.qml index e57d599..654f161 100644 --- a/Modules/Bar/Widgets/SystemMonitor.qml +++ b/Modules/Bar/Widgets/SystemMonitor.qml @@ -11,6 +11,44 @@ RowLayout { property ShellScreen screen property real scaling: 1.0 + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool userShowCpuUsage: (widgetSettings.showCpuUsage !== undefined) ? widgetSettings.showCpuUsage : BarWidgetRegistry.widgetMetadata["SystemMonitor"].showCpuUsage + readonly property bool userShowCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : BarWidgetRegistry.widgetMetadata["SystemMonitor"].showCpuTemp + readonly property bool userShowMemoryUsage: (widgetSettings.showMemoryUsage !== undefined) ? widgetSettings.showMemoryUsage : BarWidgetRegistry.widgetMetadata["SystemMonitor"].showMemoryUsage + readonly property bool userShowNetworkStats: (widgetSettings.showNetworkStats + !== undefined) ? widgetSettings.showNetworkStats : ((Settings.data.bar.showNetworkStats !== undefined) ? Settings.data.bar.showNetworkStats : BarWidgetRegistry.widgetMetadata["SystemMonitor"].showNetworkStats) + + Component.onCompleted: { + try { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + if (widgets[sectionWidgetIndex].showNetworkStats === undefined + && Settings.data.bar.showNetworkStats !== undefined) { + widgets[sectionWidgetIndex].showNetworkStats = Settings.data.bar.showNetworkStats + } + } + } + } catch (e) { + + } + } + Layout.alignment: Qt.AlignVCenter spacing: Style.marginS * scaling @@ -34,6 +72,7 @@ RowLayout { id: cpuUsageLayout spacing: Style.marginXS * scaling Layout.alignment: Qt.AlignVCenter + visible: userShowCpuUsage NIcon { id: cpuUsageIcon @@ -59,6 +98,7 @@ RowLayout { // spacing is thin here to compensate for the vertical thermometer icon spacing: Style.marginXXS * scaling Layout.alignment: Qt.AlignVCenter + visible: userShowCpuTemp NIcon { text: "thermometer" @@ -81,6 +121,7 @@ RowLayout { id: memoryUsageLayout spacing: Style.marginXS * scaling Layout.alignment: Qt.AlignVCenter + visible: userShowMemoryUsage NIcon { text: "memory" @@ -103,7 +144,7 @@ RowLayout { id: networkDownloadLayout spacing: Style.marginXS * scaling Layout.alignment: Qt.AlignVCenter - visible: Settings.data.bar.showNetworkStats + visible: userShowNetworkStats NIcon { text: "download" @@ -126,7 +167,7 @@ RowLayout { id: networkUploadLayout spacing: Style.marginXS * scaling Layout.alignment: Qt.AlignVCenter - visible: Settings.data.bar.showNetworkStats + visible: userShowNetworkStats NIcon { text: "upload" diff --git a/Modules/Bar/Widgets/Volume.qml b/Modules/Bar/Widgets/Volume.qml index 84f8b22..116718e 100644 --- a/Modules/Bar/Widgets/Volume.qml +++ b/Modules/Bar/Widgets/Volume.qml @@ -16,6 +16,19 @@ Item { property int sectionWidgetIndex: 0 property int sectionWidgetsCount: 0 + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + readonly property bool userAlwaysShowPercentage: (widgetSettings.alwaysShowPercentage + !== undefined) ? widgetSettings.alwaysShowPercentage : ((Settings.data.bar.alwaysShowBatteryPercentage !== undefined) ? Settings.data.bar.alwaysShowBatteryPercentage : BarWidgetRegistry.widgetMetadata["Volume"].alwaysShowPercentage) + // Used to avoid opening the pill on Quickshell startup property bool firstVolumeReceived: false property int wheelAccumulator: 0 @@ -54,6 +67,23 @@ Item { } } + Component.onCompleted: { + try { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + if (widgets[sectionWidgetIndex].alwaysShowPercentage === undefined + && Settings.data.bar.alwaysShowBatteryPercentage !== undefined) { + widgets[sectionWidgetIndex].alwaysShowPercentage = Settings.data.bar.alwaysShowBatteryPercentage + } + } + } + } catch (e) { + + } + } + NPill { id: pill @@ -63,6 +93,7 @@ Item { collapsedIconColor: Color.mOnSurface autoHide: false // Important to be false so we can hover as long as we want text: Math.floor(AudioService.volume * 100) + "%" + forceOpen: userAlwaysShowPercentage tooltipText: "Volume: " + Math.round(AudioService.volume * 100) + "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute." diff --git a/Modules/Bar/Widgets/Workspace.qml b/Modules/Bar/Widgets/Workspace.qml index 051bdea..3fe6641 100644 --- a/Modules/Bar/Widgets/Workspace.qml +++ b/Modules/Bar/Widgets/Workspace.qml @@ -14,6 +14,23 @@ Item { property ShellScreen screen property real scaling: 1.0 + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property string userLabelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : ((Settings.data.bar.showWorkspaceLabel !== undefined) ? Settings.data.bar.showWorkspaceLabel : BarWidgetRegistry.widgetMetadata["Workspace"].labelMode) + property bool isDestroying: false property bool hovered: false @@ -50,6 +67,20 @@ Item { Component.onCompleted: { refreshWorkspaces() + try { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + if (widgets[sectionWidgetIndex].labelMode === undefined + && Settings.data.bar.showWorkspaceLabel !== undefined) { + widgets[sectionWidgetIndex].labelMode = Settings.data.bar.showWorkspaceLabel + } + } + } + } catch (e) { + + } } Component.onDestruction: { @@ -145,7 +176,7 @@ Item { model: localWorkspaces Item { id: workspacePillContainer - height: (Settings.data.bar.showWorkspaceLabel !== "none") ? Math.round(18 * scaling) : Math.round(14 * scaling) + height: (userLabelMode !== "none") ? Math.round(18 * scaling) : Math.round(14 * scaling) width: root.calculatedWsWidth(model) Rectangle { @@ -153,15 +184,13 @@ Item { anchors.fill: parent Loader { - active: (Settings.data.bar.showWorkspaceLabel !== "none") + active: (userLabelMode !== "none") sourceComponent: Component { Text { - // Center horizontally x: (pill.width - width) / 2 - // Center vertically accounting for font metrics y: (pill.height - height) / 2 + (height - contentHeight) / 2 text: { - if (Settings.data.bar.showWorkspaceLabel === "name" && model.name && model.name.length > 0) { + if (userLabelMode === "name" && model.name && model.name.length > 0) { return model.name.substring(0, 2) } else { return model.idx.toString() diff --git a/Modules/SettingsPanel/Extras/BarSectionEditor.qml b/Modules/SettingsPanel/Extras/BarSectionEditor.qml index fb17c34..4ea82e2 100644 --- a/Modules/SettingsPanel/Extras/BarSectionEditor.qml +++ b/Modules/SettingsPanel/Extras/BarSectionEditor.qml @@ -188,13 +188,33 @@ NBox { colorBgHover: Qt.alpha(Color.mOnPrimary, Style.opacityLight) colorFgHover: Color.mOnPrimary onClicked: { - var dialog = Qt.createComponent("BarWidgetSettingsDialog.qml").createObject(root, { - "widgetIndex": index, - "widgetData": modelData, - "widgetId": modelData.id, - "parent": Overlay.overlay - }) - dialog.open() + var component = Qt.createComponent(Qt.resolvedUrl("BarWidgetSettingsDialog.qml")) + function instantiateAndOpen() { + var dialog = component.createObject(root, { + "widgetIndex": index, + "widgetData": modelData, + "widgetId": modelData.id, + "parent": Overlay.overlay + }) + if (dialog) { + dialog.open() + } else { + Logger.error("BarSectionEditor", "Failed to create settings dialog instance") + } + } + if (component.status === Component.Ready) { + instantiateAndOpen() + } else if (component.status === Component.Error) { + Logger.error("BarSectionEditor", component.errorString()) + } else { + component.statusChanged.connect(function () { + if (component.status === Component.Ready) { + instantiateAndOpen() + } else if (component.status === Component.Error) { + Logger.error("BarSectionEditor", component.errorString()) + } + }) + } } } } diff --git a/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml b/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml index f9aa98d..a7c0311 100644 --- a/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml +++ b/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml @@ -70,6 +70,26 @@ Popup { return customButtonSettings } else if (settingsPopup.widgetId === "Spacer") { return spacerSettings + } else if (settingsPopup.widgetId === "Workspace") { + return workspaceSettings + } else if (settingsPopup.widgetId === "SystemMonitor") { + return systemMonitorSettings + } else if (settingsPopup.widgetId === "ActiveWindow") { + return activeWindowSettings + } else if (settingsPopup.widgetId === "MediaMini") { + return mediaMiniSettings + } else if (settingsPopup.widgetId === "Clock") { + return clockSettings + } else if (settingsPopup.widgetId === "Volume") { + return volumeSettings + } else if (settingsPopup.widgetId === "Microphone") { + return microphoneSettings + } else if (settingsPopup.widgetId === "NotificationHistory") { + return notificationHistorySettings + } else if (settingsPopup.widgetId === "Brightness") { + return brightnessSettings + } else if (settingsPopup.widgetId === "SidePanelToggle") { + return sidePanelToggleSettings } // Add more widget settings components here as needed return null @@ -104,6 +124,279 @@ Popup { } } + // SidePanelToggle settings component + Component { + id: sidePanelToggleSettings + + ColumnLayout { + spacing: Style.marginM * scaling + + // Local state + property bool valueUseDistroLogo: settingsPopup.widgetData.useDistroLogo + !== undefined ? settingsPopup.widgetData.useDistroLogo : BarWidgetRegistry.widgetMetadata["SidePanelToggle"].useDistroLogo + + function saveSettings() { + var settings = Object.assign({}, settingsPopup.widgetData) + settings.useDistroLogo = valueUseDistroLogo + return settings + } + + NCheckbox { + label: "Use distro logo instead of icon" + checked: valueUseDistroLogo + onToggled: checked => valueUseDistroLogo = checked + } + } + } + + // Brightness settings component + Component { + id: brightnessSettings + + ColumnLayout { + spacing: Style.marginM * scaling + + // Local state + property bool valueAlwaysShowPercentage: settingsPopup.widgetData.alwaysShowPercentage + !== undefined ? settingsPopup.widgetData.alwaysShowPercentage : BarWidgetRegistry.widgetMetadata["Brightness"].alwaysShowPercentage + + function saveSettings() { + var settings = Object.assign({}, settingsPopup.widgetData) + settings.alwaysShowPercentage = valueAlwaysShowPercentage + return settings + } + + NCheckbox { + label: "Always show percentage" + checked: valueAlwaysShowPercentage + onToggled: checked => valueAlwaysShowPercentage = checked + } + } + } + + // NotificationHistory settings component + Component { + id: notificationHistorySettings + + ColumnLayout { + spacing: Style.marginM * scaling + + // Local state + property bool valueShowUnreadBadge: settingsPopup.widgetData.showUnreadBadge + !== undefined ? settingsPopup.widgetData.showUnreadBadge : BarWidgetRegistry.widgetMetadata["NotificationHistory"].showUnreadBadge + property bool valueHideWhenZero: settingsPopup.widgetData.hideWhenZero + !== undefined ? settingsPopup.widgetData.hideWhenZero : BarWidgetRegistry.widgetMetadata["NotificationHistory"].hideWhenZero + // Stage DND locally; commit on Save + property bool valueDoNotDisturbGlobal: Settings.data.notifications.doNotDisturb + + function saveSettings() { + var settings = Object.assign({}, settingsPopup.widgetData) + settings.showUnreadBadge = valueShowUnreadBadge + settings.hideWhenZero = valueHideWhenZero + Settings.data.notifications.doNotDisturb = valueDoNotDisturbGlobal + return settings + } + + NCheckbox { + label: "Show unread badge" + checked: valueShowUnreadBadge + onToggled: checked => valueShowUnreadBadge = checked + } + + NCheckbox { + label: "Hide badge when zero" + checked: valueHideWhenZero + onToggled: checked => valueHideWhenZero = checked + } + + NCheckbox { + label: "Do Not Disturb (notifications)" + description: "Toggle notifications 'Do Not Disturb'" + checked: valueDoNotDisturbGlobal + onToggled: checked => valueDoNotDisturbGlobal = checked + } + } + } + + // Microphone settings component + Component { + id: microphoneSettings + + ColumnLayout { + spacing: Style.marginM * scaling + + // Local state + property bool valueAlwaysShowPercentage: settingsPopup.widgetData.alwaysShowPercentage + !== undefined ? settingsPopup.widgetData.alwaysShowPercentage : BarWidgetRegistry.widgetMetadata["Microphone"].alwaysShowPercentage + + function saveSettings() { + var settings = Object.assign({}, settingsPopup.widgetData) + settings.alwaysShowPercentage = valueAlwaysShowPercentage + return settings + } + + NCheckbox { + label: "Always show percentage" + checked: valueAlwaysShowPercentage + onToggled: checked => valueAlwaysShowPercentage = checked + } + } + } + + // Volume settings component + Component { + id: volumeSettings + + ColumnLayout { + spacing: Style.marginM * scaling + + // Local state + property bool valueAlwaysShowPercentage: settingsPopup.widgetData.alwaysShowPercentage + !== undefined ? settingsPopup.widgetData.alwaysShowPercentage : BarWidgetRegistry.widgetMetadata["Volume"].alwaysShowPercentage + + function saveSettings() { + var settings = Object.assign({}, settingsPopup.widgetData) + settings.alwaysShowPercentage = valueAlwaysShowPercentage + return settings + } + + NCheckbox { + label: "Always show percentage" + checked: valueAlwaysShowPercentage + onToggled: checked => valueAlwaysShowPercentage = checked + } + } + } + + // Clock settings component + Component { + id: clockSettings + + ColumnLayout { + spacing: Style.marginM * scaling + + // Local state + property bool valueShowDate: settingsPopup.widgetData.showDate + !== undefined ? settingsPopup.widgetData.showDate : BarWidgetRegistry.widgetMetadata["Clock"].showDate + property bool valueUse12h: settingsPopup.widgetData.use12HourClock + !== undefined ? settingsPopup.widgetData.use12HourClock : BarWidgetRegistry.widgetMetadata["Clock"].use12HourClock + property bool valueShowSeconds: settingsPopup.widgetData.showSeconds + !== undefined ? settingsPopup.widgetData.showSeconds : BarWidgetRegistry.widgetMetadata["Clock"].showSeconds + + function saveSettings() { + var settings = Object.assign({}, settingsPopup.widgetData) + settings.showDate = valueShowDate + settings.use12HourClock = valueUse12h + settings.showSeconds = valueShowSeconds + return settings + } + + NCheckbox { + label: "Show date next to time" + checked: valueShowDate + onToggled: checked => valueShowDate = checked + } + + NCheckbox { + label: "Use 12-hour clock" + checked: valueUse12h + onToggled: checked => valueUse12h = checked + } + + NCheckbox { + label: "Show seconds" + checked: valueShowSeconds + onToggled: checked => valueShowSeconds = checked + } + } + } + + // MediaMini settings component + Component { + id: mediaMiniSettings + + ColumnLayout { + spacing: Style.marginM * scaling + + // Local state + property bool valueShowAlbumArt: settingsPopup.widgetData.showAlbumArt + !== undefined ? settingsPopup.widgetData.showAlbumArt : BarWidgetRegistry.widgetMetadata["MediaMini"].showAlbumArt + property bool valueShowVisualizer: settingsPopup.widgetData.showVisualizer + !== undefined ? settingsPopup.widgetData.showVisualizer : BarWidgetRegistry.widgetMetadata["MediaMini"].showVisualizer + property string valueVisualizerType: settingsPopup.widgetData.visualizerType + || BarWidgetRegistry.widgetMetadata["MediaMini"].visualizerType + + function saveSettings() { + var settings = Object.assign({}, settingsPopup.widgetData) + settings.showAlbumArt = valueShowAlbumArt + settings.showVisualizer = valueShowVisualizer + settings.visualizerType = valueVisualizerType + return settings + } + + NCheckbox { + label: "Show album art" + checked: valueShowAlbumArt + onToggled: checked => valueShowAlbumArt = checked + } + + NCheckbox { + label: "Show visualizer" + checked: valueShowVisualizer + onToggled: checked => valueShowVisualizer = checked + } + + NComboBox { + label: "Visualizer type" + description: "Select the visualizer style" + preferredWidth: 180 * scaling + model: ListModel { + ListElement { + key: "linear" + name: "Linear" + } + ListElement { + key: "mirrored" + name: "Mirrored" + } + ListElement { + key: "wave" + name: "Wave" + } + } + currentKey: valueVisualizerType + onSelected: key => valueVisualizerType = key + } + } + } + + // ActiveWindow settings component + Component { + id: activeWindowSettings + + ColumnLayout { + spacing: Style.marginM * scaling + + // Local, editable state + property bool valueShowIcon: settingsPopup.widgetData.showIcon + !== undefined ? settingsPopup.widgetData.showIcon : BarWidgetRegistry.widgetMetadata["ActiveWindow"].showIcon + + function saveSettings() { + var settings = Object.assign({}, settingsPopup.widgetData) + settings.showIcon = valueShowIcon + return settings + } + + NCheckbox { + id: showIcon + Layout.fillWidth: true + label: "Show app icon" + checked: valueShowIcon + onToggled: checked => valueShowIcon = checked + } + } + } + // CustomButton settings component Component { id: customButtonSettings @@ -183,4 +476,103 @@ Popup { } } } + + // Workspace settings component + Component { + id: workspaceSettings + + ColumnLayout { + spacing: Style.marginM * scaling + + function saveSettings() { + var settings = Object.assign({}, settingsPopup.widgetData) + settings.labelMode = labelModeCombo.currentKey + return settings + } + + NComboBox { + id: labelModeCombo + Layout.fillWidth: true + preferredWidth: 180 * scaling + label: "Label Mode" + description: "Choose how to label workspace pills." + model: ListModel { + ListElement { + key: "none" + name: "None" + } + ListElement { + key: "index" + name: "Index" + } + ListElement { + key: "name" + name: "Name" + } + } + currentKey: settingsPopup.widgetData.labelMode || BarWidgetRegistry.widgetMetadata["Workspace"].labelMode + onSelected: key => labelModeCombo.currentKey = key + } + } + } + + // SystemMonitor settings component + Component { + id: systemMonitorSettings + + ColumnLayout { + spacing: Style.marginM * scaling + + // Local, editable state for checkboxes + property bool valueShowCpuUsage: settingsPopup.widgetData.showCpuUsage + !== undefined ? settingsPopup.widgetData.showCpuUsage : BarWidgetRegistry.widgetMetadata["SystemMonitor"].showCpuUsage + property bool valueShowCpuTemp: settingsPopup.widgetData.showCpuTemp + !== undefined ? settingsPopup.widgetData.showCpuTemp : BarWidgetRegistry.widgetMetadata["SystemMonitor"].showCpuTemp + property bool valueShowMemoryUsage: settingsPopup.widgetData.showMemoryUsage + !== undefined ? settingsPopup.widgetData.showMemoryUsage : BarWidgetRegistry.widgetMetadata["SystemMonitor"].showMemoryUsage + property bool valueShowNetworkStats: settingsPopup.widgetData.showNetworkStats + !== undefined ? settingsPopup.widgetData.showNetworkStats : BarWidgetRegistry.widgetMetadata["SystemMonitor"].showNetworkStats + + function saveSettings() { + var settings = Object.assign({}, settingsPopup.widgetData) + settings.showCpuUsage = valueShowCpuUsage + settings.showCpuTemp = valueShowCpuTemp + settings.showMemoryUsage = valueShowMemoryUsage + settings.showNetworkStats = valueShowNetworkStats + return settings + } + + NCheckbox { + id: showCpuUsage + Layout.fillWidth: true + label: "CPU usage" + checked: valueShowCpuUsage + onToggled: checked => valueShowCpuUsage = checked + } + + NCheckbox { + id: showCpuTemp + Layout.fillWidth: true + label: "CPU temperature" + checked: valueShowCpuTemp + onToggled: checked => valueShowCpuTemp = checked + } + + NCheckbox { + id: showMemoryUsage + Layout.fillWidth: true + label: "Memory usage" + checked: valueShowMemoryUsage + onToggled: checked => valueShowMemoryUsage = checked + } + + NCheckbox { + id: showNetworkStats + Layout.fillWidth: true + label: "Network traffic" + checked: valueShowNetworkStats + onToggled: checked => valueShowNetworkStats = checked + } + } + } } diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index e1bf63a..2e6368f 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -71,56 +71,13 @@ 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 - } - + // Keep Battery toggle here for now (cannot test per-widget yet) NToggle { label: "Show Battery Percentage" description: "Display battery percentage at all times." checked: Settings.data.bar.alwaysShowBatteryPercentage onToggled: checked => Settings.data.bar.alwaysShowBatteryPercentage = checked } - - NToggle { - label: "Show Network Statistics" - description: "Display network upload and download speeds in the system monitor." - checked: Settings.data.bar.showNetworkStats - onToggled: checked => Settings.data.bar.showNetworkStats = checked - } - - NToggle { - label: "Replace SidePanel toggle with distro logo" - description: "Show distro logo instead of the SidePanel toggle button in the bar." - checked: Settings.data.bar.useDistroLogo - onToggled: checked => { - Settings.data.bar.useDistroLogo = checked - } - } - - NComboBox { - label: "Show Workspaces Labels" - description: "Show the workspace name or index within the workspace indicator." - model: ListModel { - ListElement { - key: "none" - name: "None" - } - ListElement { - key: "index" - name: "Index" - } - ListElement { - key: "name" - name: "Name" - } - } - currentKey: Settings.data.bar.showWorkspaceLabel - onSelected: key => Settings.data.bar.showWorkspaceLabel = key - } } NDivider { diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index 39afcd3..33d26f9 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -49,6 +49,61 @@ Singleton { "allowUserSettings": true, "icon": "space_bar", "width": 20 + }, + "ActiveWindow"// Per-instance settings for common widgets shown in BarTab + : { + "allowUserSettings": true, + "showIcon": true + }, + "Battery": { + "allowUserSettings": true, + "alwaysShowPercentage": false + }, + "SystemMonitor": { + "allowUserSettings": true, + "showCpuUsage": true, + "showCpuTemp": true, + "showMemoryUsage": true, + "showNetworkStats": false + }, + "Workspace": { + "allowUserSettings": true, + "labelMode"// none | index | name + : "index" + }, + "MediaMini": { + "allowUserSettings": true, + "showAlbumArt": false, + "showVisualizer": false, + "visualizerType"// linear | mirrored | wave + : "linear" + }, + "Clock": { + "allowUserSettings": true, + "showDate": false, + "use12HourClock": false, + "showSeconds": false + }, + "Volume": { + "allowUserSettings": true, + "alwaysShowPercentage": false + }, + "Microphone": { + "allowUserSettings": true, + "alwaysShowPercentage": false + }, + "Brightness": { + "allowUserSettings": true, + "alwaysShowPercentage": false + }, + "SidePanelToggle": { + "allowUserSettings": true, + "useDistroLogo": false + }, + "NotificationHistory": { + "allowUserSettings": true, + "showUnreadBadge": true, + "hideWhenZero": false } }) diff --git a/Widgets/NCheckbox.qml b/Widgets/NCheckbox.qml index 48d76e5..f903e8a 100644 --- a/Widgets/NCheckbox.qml +++ b/Widgets/NCheckbox.qml @@ -27,6 +27,11 @@ RowLayout { visible: root.label !== "" || root.description !== "" } + // Spacer to push the checkbox to the far right + Item { + Layout.fillWidth: true + } + Rectangle { id: box diff --git a/Widgets/NClock.qml b/Widgets/NClock.qml index aa8ce33..552388c 100644 --- a/Widgets/NClock.qml +++ b/Widgets/NClock.qml @@ -10,13 +10,32 @@ Rectangle { signal exited signal clicked + // Per-instance overrides (default to global settings if not provided by parent) + // Parent widgets like Bar `Clock.qml` can bind these + property bool showDate: Settings.data.location.showDateWithClock + property bool use12h: Settings.data.location.use12HourClock + property bool showSeconds: false + width: textItem.paintedWidth height: textItem.paintedHeight color: Color.transparent NText { id: textItem - text: Time.time + text: { + const now = Time.date + const timeFormat = use12h ? (showSeconds ? "h:mm:ss AP" : "h:mm AP") : (showSeconds ? "HH:mm:ss" : "HH:mm") + const timeString = Qt.formatDateTime(now, timeFormat) + + if (showDate) { + let dayName = now.toLocaleDateString(Qt.locale(), "ddd") + dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1) + let day = now.getDate() + let month = now.toLocaleDateString(Qt.locale(), "MMM") + return timeString + " - " + (Settings.data.location.reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`) + } + return timeString + } anchors.centerIn: parent font.pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightBold