From 7f44293f8dcb475ba5e93b717bbd4e116a17f21b Mon Sep 17 00:00:00 2001 From: purian23 Date: Sun, 10 Aug 2025 08:43:39 -0400 Subject: [PATCH] feat: Implement Topbar Autohide options --- Common/SettingsData.qml | 10 +- Modules/Settings/PersonalizationTab.qml | 70 + Modules/TopBar/TopBar.qml | 1711 ++++++++++++----------- 3 files changed, 956 insertions(+), 835 deletions(-) diff --git a/Common/SettingsData.qml b/Common/SettingsData.qml index 5c22920e..65396d2e 100644 --- a/Common/SettingsData.qml +++ b/Common/SettingsData.qml @@ -90,6 +90,7 @@ Singleton { property bool dockAutoHide: false property real cornerRadius: 12 property bool notificationOverlayEnabled: false + property bool topBarAutoHide: false readonly property string defaultFontFamily: "Inter Variable" readonly property string defaultMonoFontFamily: "Fira Code" @@ -255,6 +256,7 @@ Singleton { dockAutoHide = settings.dockAutoHide !== undefined ? settings.dockAutoHide : false cornerRadius = settings.cornerRadius !== undefined ? settings.cornerRadius : 12 notificationOverlayEnabled = settings.notificationOverlayEnabled !== undefined ? settings.notificationOverlayEnabled : false + topBarAutoHide = settings.topBarAutoHide !== undefined ? settings.topBarAutoHide : false applyStoredTheme() detectAvailableIconThemes() detectQtTools() @@ -324,7 +326,8 @@ Singleton { "showDock": showDock, "dockAutoHide": dockAutoHide, "cornerRadius": cornerRadius, - "notificationOverlayEnabled": notificationOverlayEnabled + "notificationOverlayEnabled": notificationOverlayEnabled, + "topBarAutoHide": topBarAutoHide }, null, 2)) } @@ -746,6 +749,11 @@ Singleton { saveSettings() } + function setTopBarAutoHide(enabled) { + topBarAutoHide = enabled + saveSettings() + } + function _shq(s) { return "'" + String(s).replace(/'/g, "'\\''") + "'" } diff --git a/Modules/Settings/PersonalizationTab.qml b/Modules/Settings/PersonalizationTab.qml index 04458147..94009e52 100644 --- a/Modules/Settings/PersonalizationTab.qml +++ b/Modules/Settings/PersonalizationTab.qml @@ -46,6 +46,11 @@ Item { sourceComponent: dynamicThemeComponent } + Loader { + width: parent.width + sourceComponent: topBarAutoHideComponent + } + Loader { width: parent.width sourceComponent: notificationOverlayComponent @@ -949,4 +954,69 @@ Item { } } } + + // TopBar Auto-hide Component + Component { + id: topBarAutoHideComponent + + StyledRect { + width: parent.width + height: topBarAutoHideSection.implicitHeight + Theme.spacingL * 2 + radius: Theme.cornerRadius + color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, + Theme.surfaceVariant.b, 0.3) + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, + Theme.outline.b, 0.2) + border.width: 1 + + Column { + id: topBarAutoHideSection + + anchors.fill: parent + anchors.margins: Theme.spacingL + spacing: Theme.spacingM + + Row { + width: parent.width + spacing: Theme.spacingM + + DankIcon { + name: "visibility_off" + size: Theme.iconSize + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + } + + Column { + width: parent.width - Theme.iconSize - Theme.spacingM - autoHideToggle.width - Theme.spacingM + spacing: Theme.spacingXS + anchors.verticalCenter: parent.verticalCenter + + StyledText { + text: "TopBar Auto-hide" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Medium + color: Theme.surfaceText + } + + StyledText { + text: "Automatically hide the top bar to expand screen real estate" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + wrapMode: Text.WordWrap + width: parent.width + } + } + + DankToggle { + id: autoHideToggle + + anchors.verticalCenter: parent.verticalCenter + checked: SettingsData.topBarAutoHide + onToggled: toggled => SettingsData.setTopBarAutoHide(toggled) + } + } + } + } + } } diff --git a/Modules/TopBar/TopBar.qml b/Modules/TopBar/TopBar.qml index 20118ef1..c7f06c16 100644 --- a/Modules/TopBar/TopBar.qml +++ b/Modules/TopBar/TopBar.qml @@ -14,860 +14,903 @@ import qs.Services import qs.Widgets PanelWindow { - id: root + id: root - property var modelData - property string screenName: modelData.name - property real backgroundTransparency: SettingsData.topBarTransparency - readonly property int notificationCount: NotificationService.notifications.length + property var modelData + property string screenName: modelData.name + property real backgroundTransparency: SettingsData.topBarTransparency + readonly property int notificationCount: NotificationService.notifications.length + property bool autoHide: SettingsData.topBarAutoHide + property bool reveal: !autoHide || topBarMouseArea.containsMouse - screen: modelData - implicitHeight: Theme.barHeight - 4 - color: "transparent" - Component.onCompleted: { - let fonts = Qt.fontFamilies() - if (fonts.indexOf("Material Symbols Rounded") === -1) - ToastService.showError( - "Please install Material Symbols Rounded and Restart your Shell. See README.md for instructions") + screen: modelData + implicitHeight: Theme.barHeight - 4 + color: "transparent" + Component.onCompleted: { + let fonts = Qt.fontFamilies() + if (fonts.indexOf("Material Symbols Rounded") === -1) + ToastService.showError( + "Please install Material Symbols Rounded and Restart your Shell. See README.md for instructions") - SettingsData.forceTopBarLayoutRefresh.connect(function () { - Qt.callLater(() => { - leftSection.visible = false - centerSection.visible = false - rightSection.visible = false - Qt.callLater(() => { - leftSection.visible = true - centerSection.visible = true - rightSection.visible = true - }) - }) - }) - - // Configure GPU temperature monitoring based on widget configuration - updateGpuTempConfig() - } - - function updateGpuTempConfig() { - const allWidgets = [...(SettingsData.topBarLeftWidgets || []), - ...(SettingsData.topBarCenterWidgets || []), - ...(SettingsData.topBarRightWidgets || [])] - - const hasGpuTempWidget = allWidgets.some(widget => { - const widgetId = typeof widget === "string" ? widget : widget.id - const widgetEnabled = typeof widget === "string" ? true : (widget.enabled !== false) - return widgetId === "gpuTemp" && widgetEnabled - }) - - DankgopService.gpuTempEnabled = hasGpuTempWidget || SessionData.nvidiaGpuTempEnabled || SessionData.nonNvidiaGpuTempEnabled - DankgopService.nvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nvidiaGpuTempEnabled - DankgopService.nonNvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nonNvidiaGpuTempEnabled - } - - Connections { - function onTopBarTransparencyChanged() { - root.backgroundTransparency = SettingsData.topBarTransparency + SettingsData.forceTopBarLayoutRefresh.connect(function () { + Qt.callLater(() => { + leftSection.visible = false + centerSection.visible = false + rightSection.visible = false + Qt.callLater(() => { + leftSection.visible = true + centerSection.visible = true + rightSection.visible = true + }) + }) + }) + + // Configure GPU temperature monitoring based on widget configuration + updateGpuTempConfig() } - function onTopBarLeftWidgetsChanged() { - root.updateGpuTempConfig() + function updateGpuTempConfig() { + const allWidgets = [...(SettingsData.topBarLeftWidgets || []), + ...(SettingsData.topBarCenterWidgets || []), + ...(SettingsData.topBarRightWidgets || [])] + + const hasGpuTempWidget = allWidgets.some(widget => { + const widgetId = typeof widget === "string" ? widget : widget.id + const widgetEnabled = typeof widget === "string" ? true : (widget.enabled !== false) + return widgetId === "gpuTemp" && widgetEnabled + }) + + DankgopService.gpuTempEnabled = hasGpuTempWidget || SessionData.nvidiaGpuTempEnabled || SessionData.nonNvidiaGpuTempEnabled + DankgopService.nvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nvidiaGpuTempEnabled + DankgopService.nonNvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nonNvidiaGpuTempEnabled + } + + Connections { + function onTopBarTransparencyChanged() { + root.backgroundTransparency = SettingsData.topBarTransparency + } + + function onTopBarLeftWidgetsChanged() { + root.updateGpuTempConfig() + } + + function onTopBarCenterWidgetsChanged() { + root.updateGpuTempConfig() + } + + function onTopBarRightWidgetsChanged() { + root.updateGpuTempConfig() + } + + target: SettingsData } - function onTopBarCenterWidgetsChanged() { - root.updateGpuTempConfig() - } - - function onTopBarRightWidgetsChanged() { - root.updateGpuTempConfig() + Connections { + function onNvidiaGpuTempEnabledChanged() { + root.updateGpuTempConfig() + } + + function onNonNvidiaGpuTempEnabledChanged() { + root.updateGpuTempConfig() + } + + target: SessionData } - target: SettingsData - } - - Connections { - function onNvidiaGpuTempEnabledChanged() { - root.updateGpuTempConfig() - } - - function onNonNvidiaGpuTempEnabledChanged() { - root.updateGpuTempConfig() - } - - target: SessionData - } - - Connections { - target: root.screen - function onGeometryChanged() { - // Re-layout center widgets when screen geometry changes - if (centerSection && centerSection.width > 0) { - Qt.callLater(centerSection.updateLayout) - } - } - } - - QtObject { - id: notificationHistory - - property int count: 0 - } - - anchors { - top: true - left: true - right: true - } - - Item { - anchors.fill: parent - anchors.margins: 2 - anchors.topMargin: 6 - anchors.bottomMargin: 0 - anchors.leftMargin: 8 - anchors.rightMargin: 8 - - Rectangle { - anchors.fill: parent - radius: Theme.cornerRadius - color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, - Theme.surfaceContainer.b, root.backgroundTransparency) - layer.enabled: true - - Rectangle { - anchors.fill: parent - color: "transparent" - border.color: Theme.outlineMedium - border.width: 1 - radius: parent.radius - } - - Rectangle { - anchors.fill: parent - color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, - Theme.surfaceTint.b, 0.04) - radius: parent.radius - - SequentialAnimation on opacity { - running: false - loops: Animation.Infinite - - NumberAnimation { - to: 0.08 - duration: Theme.extraLongDuration - easing.type: Theme.standardEasing - } - - NumberAnimation { - to: 0.02 - duration: Theme.extraLongDuration - easing.type: Theme.standardEasing - } - } - } - - layer.effect: MultiEffect { - shadowEnabled: true - shadowHorizontalOffset: 0 - shadowVerticalOffset: 4 - shadowBlur: 0.5 // radius/32, adjusted for visual match - shadowColor: Qt.rgba(0, 0, 0, 0.15) - shadowOpacity: 0.15 - } - } - - Item { - id: topBarContent - - readonly property int availableWidth: width - readonly property int launcherButtonWidth: 40 - readonly property int workspaceSwitcherWidth: 120 // Approximate - readonly property int focusedAppMaxWidth: 456 // Fixed width since we don't have focusedApp reference - readonly property int estimatedLeftSectionWidth: launcherButtonWidth + workspaceSwitcherWidth - + focusedAppMaxWidth + (Theme.spacingXS * 2) - readonly property int rightSectionWidth: rightSection.width - readonly property int clockWidth: 120 // Approximate clock width - readonly property int mediaMaxWidth: 280 // Normal max width - readonly property int weatherWidth: 80 // Approximate weather width - readonly property bool validLayout: availableWidth > 100 - && estimatedLeftSectionWidth > 0 - && rightSectionWidth > 0 - readonly property int clockLeftEdge: (availableWidth - clockWidth) / 2 - readonly property int clockRightEdge: clockLeftEdge + clockWidth - readonly property int leftSectionRightEdge: estimatedLeftSectionWidth - readonly property int mediaLeftEdge: clockLeftEdge - mediaMaxWidth - Theme.spacingS - readonly property int rightSectionLeftEdge: availableWidth - rightSectionWidth - readonly property int leftToClockGap: Math.max( - 0, - clockLeftEdge - leftSectionRightEdge) - readonly property int leftToMediaGap: mediaMaxWidth > 0 ? Math.max( - 0, - mediaLeftEdge - leftSectionRightEdge) : leftToClockGap - readonly property int mediaToClockGap: mediaMaxWidth > 0 ? Theme.spacingS : 0 - readonly property int clockToRightGap: validLayout ? Math.max( - 0, - rightSectionLeftEdge - - clockRightEdge) : 1000 - readonly property bool spacingTight: validLayout - && (leftToMediaGap < 150 - || clockToRightGap < 100) - readonly property bool overlapping: validLayout - && (leftToMediaGap < 100 - || clockToRightGap < 50) - - function getWidgetEnabled(enabled) { - return enabled !== undefined ? enabled : true - } - - function getWidgetVisible(widgetId) { - switch (widgetId) { - case "launcherButton": - return true - case "workspaceSwitcher": - return true - case "focusedWindow": - return true - case "clock": - return true - case "music": - return true - case "weather": - return true - case "systemTray": - return true - case "privacyIndicator": - return true - case "clipboard": - return true - case "cpuUsage": - return DankgopService.dankgopAvailable - case "memUsage": - return DankgopService.dankgopAvailable - case "cpuTemp": - return DankgopService.dankgopAvailable - case "gpuTemp": - return DankgopService.dankgopAvailable - case "notificationButton": - return true - case "battery": - return true - case "controlCenterButton": - return true - case "spacer": - return true - case "separator": - return true - default: - return false - } - } - - function getWidgetComponent(widgetId) { - switch (widgetId) { - case "launcherButton": - return launcherButtonComponent - case "workspaceSwitcher": - return workspaceSwitcherComponent - case "focusedWindow": - return focusedWindowComponent - case "clock": - return clockComponent - case "music": - return mediaComponent - case "weather": - return weatherComponent - case "systemTray": - return systemTrayComponent - case "privacyIndicator": - return privacyIndicatorComponent - case "clipboard": - return clipboardComponent - case "cpuUsage": - return cpuUsageComponent - case "memUsage": - return memUsageComponent - case "cpuTemp": - return cpuTempComponent - case "gpuTemp": - return gpuTempComponent - case "notificationButton": - return notificationButtonComponent - case "battery": - return batteryComponent - case "controlCenterButton": - return controlCenterButtonComponent - case "spacer": - return spacerComponent - case "separator": - return separatorComponent - default: - return null - } - } - - anchors.fill: parent - anchors.leftMargin: Theme.spacingM - anchors.rightMargin: Theme.spacingM - anchors.topMargin: Theme.spacingXS - anchors.bottomMargin: Theme.spacingXS - clip: true - - Row { - id: leftSection - - height: parent.height - spacing: Theme.spacingXS - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - - Repeater { - model: SettingsData.topBarLeftWidgetsModel - - Loader { - property string widgetId: model.widgetId - property var widgetData: model - property int spacerSize: model.size || 20 - - anchors.verticalCenter: parent ? parent.verticalCenter : undefined - active: topBarContent.getWidgetVisible(model.widgetId) - sourceComponent: topBarContent.getWidgetComponent(model.widgetId) - opacity: topBarContent.getWidgetEnabled(model.enabled) ? 1 : 0 - } - } - } - - Item { - id: centerSection - - property var centerWidgets: [] - property int totalWidgets: 0 - property real totalWidth: 0 - property real spacing: Theme.spacingS - - function updateLayout() { - // Defer layout if dimensions are invalid - if (width <= 0 || height <= 0 || !visible) { - Qt.callLater(updateLayout) - return - } - - centerWidgets = [] - totalWidgets = 0 - totalWidth = 0 - for (var i = 0; i < centerRepeater.count; i++) { - let item = centerRepeater.itemAt(i) - if (item && item.active && item.item) { - centerWidgets.push(item.item) - totalWidgets++ - totalWidth += item.item.width - } - } - if (totalWidgets > 1) - totalWidth += spacing * (totalWidgets - 1) - - positionWidgets() - } - - function positionWidgets() { - if (totalWidgets === 0 || width <= 0) - return - - let parentCenterX = width / 2 - if (totalWidgets % 2 === 1) { - let middleIndex = Math.floor(totalWidgets / 2) - let currentX = parentCenterX - (centerWidgets[middleIndex].width / 2) - centerWidgets[middleIndex].x = currentX - centerWidgets[middleIndex].anchors.horizontalCenter = undefined - currentX = centerWidgets[middleIndex].x - for (var i = middleIndex - 1; i >= 0; i--) { - currentX -= (spacing + centerWidgets[i].width) - centerWidgets[i].x = currentX - centerWidgets[i].anchors.horizontalCenter = undefined - } - currentX = centerWidgets[middleIndex].x + centerWidgets[middleIndex].width - for (var i = middleIndex + 1; i < totalWidgets; i++) { - currentX += spacing - centerWidgets[i].x = currentX - centerWidgets[i].anchors.horizontalCenter = undefined - currentX += centerWidgets[i].width - } - } else { - let leftMiddleIndex = (totalWidgets / 2) - 1 - let rightMiddleIndex = totalWidgets / 2 - let gapCenter = parentCenterX - let halfSpacing = spacing / 2 - centerWidgets[leftMiddleIndex].x = gapCenter - halfSpacing - - centerWidgets[leftMiddleIndex].width - centerWidgets[leftMiddleIndex].anchors.horizontalCenter = undefined - centerWidgets[rightMiddleIndex].x = gapCenter + halfSpacing - centerWidgets[rightMiddleIndex].anchors.horizontalCenter = undefined - let currentX = centerWidgets[leftMiddleIndex].x - for (var i = leftMiddleIndex - 1; i >= 0; i--) { - currentX -= (spacing + centerWidgets[i].width) - centerWidgets[i].x = currentX - centerWidgets[i].anchors.horizontalCenter = undefined - } - currentX = centerWidgets[rightMiddleIndex].x + centerWidgets[rightMiddleIndex].width - for (var i = rightMiddleIndex + 1; i < totalWidgets; i++) { - currentX += spacing - centerWidgets[i].x = currentX - centerWidgets[i].anchors.horizontalCenter = undefined - currentX += centerWidgets[i].width - } - } - } - - height: parent.height - width: parent.width - anchors.centerIn: parent - Component.onCompleted: { - Qt.callLater(() => { - Qt.callLater(updateLayout) - }) - } - - onWidthChanged: { - if (width > 0) { - Qt.callLater(updateLayout) - } - } - - onVisibleChanged: { - if (visible && width > 0) { - Qt.callLater(updateLayout) - } - } - - Repeater { - id: centerRepeater - - model: SettingsData.topBarCenterWidgetsModel - - Loader { - property string widgetId: model.widgetId - property var widgetData: model - property int spacerSize: model.size || 20 - - anchors.verticalCenter: parent ? parent.verticalCenter : undefined - active: topBarContent.getWidgetVisible(model.widgetId) - sourceComponent: topBarContent.getWidgetComponent(model.widgetId) - opacity: topBarContent.getWidgetEnabled(model.enabled) ? 1 : 0 - onLoaded: { - if (item) { - item.onWidthChanged.connect(centerSection.updateLayout) - if (model.widgetId === "spacer") - item.spacerSize = Qt.binding(() => { - return model.size || 20 - }) - + Connections { + target: root.screen + function onGeometryChanged() { + // Re-layout center widgets when screen geometry changes + if (centerSection && centerSection.width > 0) { Qt.callLater(centerSection.updateLayout) - } } - onActiveChanged: { - Qt.callLater(centerSection.updateLayout) + } + } + + QtObject { + id: notificationHistory + + property int count: 0 + } + + anchors { + top: true + left: true + right: true + } + + exclusiveZone: autoHide ? -1 : Theme.barHeight - 4 + + mask: Region { + item: topBarMouseArea + } + + MouseArea { + id: topBarMouseArea + height: root.reveal ? Theme.barHeight - 4 : 4 + anchors { + top: parent.top + left: parent.left + right: parent.right + } + hoverEnabled: true + + Behavior on height { + NumberAnimation { + duration: 200 + easing.type: Easing.OutCubic } - } } - Connections { - function onCountChanged() { - Qt.callLater(centerSection.updateLayout) - } - - target: SettingsData.topBarCenterWidgetsModel - } - } - - Row { - id: rightSection - - height: parent.height - spacing: Theme.spacingXS - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - Repeater { - model: SettingsData.topBarRightWidgetsModel - - Loader { - property string widgetId: model.widgetId - property var widgetData: model - property int spacerSize: model.size || 20 - - anchors.verticalCenter: parent ? parent.verticalCenter : undefined - active: topBarContent.getWidgetVisible(model.widgetId) - sourceComponent: topBarContent.getWidgetComponent(model.widgetId) - opacity: topBarContent.getWidgetEnabled(model.enabled) ? 1 : 0 - } - } - } - - Component { - id: launcherButtonComponent - - LauncherButton { - isActive: appDrawerPopout ? appDrawerPopout.isVisible : false - section: { - // Determine which section this loader is in by checking parent - if (parent && parent.parent) { - if (parent.parent === leftSection) - return "left" - if (parent.parent === rightSection) - return "right" - if (parent.parent === centerSection) - return "center" - } - return "left" // default fallback - } - popupTarget: appDrawerPopout - parentScreen: root.screen - onClicked: { - if (appDrawerPopout) - appDrawerPopout.toggle() - } - } - } - - Component { - id: workspaceSwitcherComponent - - WorkspaceSwitcher { - screenName: root.screenName - } - } - - Component { - id: focusedWindowComponent - - FocusedApp { - compactMode: topBarContent.spacingTight - availableWidth: topBarContent.leftToMediaGap - } - } - - Component { - id: clockComponent - - Clock { - compactMode: topBarContent.overlapping - section: { - if (parent && parent.parent === leftSection) - return "left" - if (parent && parent.parent === rightSection) - return "right" - if (parent && parent.parent === centerSection) - return "center" - return "center" - } - popupTarget: centcomPopout - parentScreen: root.screen - onClockClicked: { - centcomPopout.calendarVisible = !centcomPopout.calendarVisible - } - } - } - - Component { - id: mediaComponent - - Media { - compactMode: topBarContent.spacingTight || topBarContent.overlapping - section: { - if (parent && parent.parent === leftSection) - return "left" - if (parent && parent.parent === rightSection) - return "right" - if (parent && parent.parent === centerSection) - return "center" - return "center" - } - popupTarget: centcomPopout - parentScreen: root.screen - onClicked: { - centcomPopout.calendarVisible = !centcomPopout.calendarVisible - } - } - } - - Component { - id: weatherComponent - - Weather { - section: { - if (parent && parent.parent === leftSection) - return "left" - if (parent && parent.parent === rightSection) - return "right" - if (parent && parent.parent === centerSection) - return "center" - return "center" - } - popupTarget: centcomPopout - parentScreen: root.screen - onClicked: { - centcomPopout.calendarVisible = !centcomPopout.calendarVisible - } - } - } - - Component { - id: systemTrayComponent - - SystemTrayBar { - parentWindow: root - parentScreen: root.screen - } - } - - Component { - id: privacyIndicatorComponent - - PrivacyIndicator { - section: { - if (parent && parent.parent === leftSection) - return "left" - if (parent && parent.parent === rightSection) - return "right" - if (parent && parent.parent === centerSection) - return "center" - return "right" - } - parentScreen: root.screen - } - } - - Component { - id: clipboardComponent - - Rectangle { - width: 40 - height: 30 - radius: Theme.cornerRadius - color: { - const baseColor = clipboardArea.containsMouse ? Theme.primaryHover : Theme.secondaryHover - return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, - baseColor.a * Theme.widgetTransparency) - } - - DankIcon { - anchors.centerIn: parent - name: "content_paste" - size: Theme.iconSize - 6 - color: Theme.surfaceText - } - - MouseArea { - id: clipboardArea - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - clipboardHistoryModalPopup.toggle() - } - } - - Behavior on color { - ColorAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing - } - } - } - } - - Component { - id: cpuUsageComponent - - CpuMonitor { - section: { - if (parent && parent.parent === leftSection) - return "left" - if (parent && parent.parent === rightSection) - return "right" - if (parent && parent.parent === centerSection) - return "center" - return "right" - } - popupTarget: processListPopout - parentScreen: root.screen - toggleProcessList: () => { - return processListPopout.toggle() - } - } - } - - Component { - id: memUsageComponent - - RamMonitor { - section: { - if (parent && parent.parent === leftSection) - return "left" - if (parent && parent.parent === rightSection) - return "right" - if (parent && parent.parent === centerSection) - return "center" - return "right" - } - popupTarget: processListPopout - parentScreen: root.screen - toggleProcessList: () => { - return processListPopout.toggle() - } - } - } - - Component { - id: cpuTempComponent - - CpuTemperature { - section: { - if (parent && parent.parent === leftSection) - return "left" - if (parent && parent.parent === rightSection) - return "right" - if (parent && parent.parent === centerSection) - return "center" - return "right" - } - popupTarget: processListPopout - parentScreen: root.screen - toggleProcessList: () => { - return processListPopout.toggle() - } - } - } - - Component { - id: gpuTempComponent - - GpuTemperature { - section: { - if (parent && parent.parent === leftSection) - return "left" - if (parent && parent.parent === rightSection) - return "right" - if (parent && parent.parent === centerSection) - return "center" - return "right" - } - popupTarget: processListPopout - parentScreen: root.screen - widgetData: parent.widgetData - toggleProcessList: () => { - return processListPopout.toggle() - } - } - } - - Component { - id: notificationButtonComponent - - NotificationCenterButton { - hasUnread: root.notificationCount > 0 - isActive: notificationCenter.notificationHistoryVisible - section: { - if (parent && parent.parent === leftSection) - return "left" - if (parent && parent.parent === rightSection) - return "right" - if (parent && parent.parent === centerSection) - return "center" - return "right" - } - popupTarget: notificationCenter - parentScreen: root.screen - onClicked: { - notificationCenter.notificationHistoryVisible - = !notificationCenter.notificationHistoryVisible - } - } - } - - Component { - id: batteryComponent - - Battery { - batteryPopupVisible: batteryPopout.batteryPopupVisible - section: { - if (parent && parent.parent === leftSection) - return "left" - if (parent && parent.parent === rightSection) - return "right" - if (parent && parent.parent === centerSection) - return "center" - return "right" - } - popupTarget: batteryPopout - parentScreen: root.screen - onToggleBatteryPopup: { - batteryPopout.batteryPopupVisible = !batteryPopout.batteryPopupVisible - } - } - } - - Component { - id: controlCenterButtonComponent - - ControlCenterButton { - isActive: controlCenterPopout.controlCenterVisible - section: { - if (parent && parent.parent === leftSection) - return "left" - if (parent && parent.parent === rightSection) - return "right" - if (parent && parent.parent === centerSection) - return "center" - return "right" - } - popupTarget: controlCenterPopout - parentScreen: root.screen - onClicked: { - controlCenterPopout.triggerScreen = root.screen - controlCenterPopout.controlCenterVisible = !controlCenterPopout.controlCenterVisible - if (controlCenterPopout.controlCenterVisible) { - if (NetworkService.wifiEnabled) - NetworkService.scanWifi() - } - } - } - } - - Component { - id: spacerComponent - Item { - width: parent.spacerSize || 20 - height: 30 - - Rectangle { + id: topBarContainer anchors.fill: parent - color: "transparent" - border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, - Theme.outline.b, 0.1) - border.width: 1 - radius: 2 - visible: false - MouseArea { - anchors.fill: parent - hoverEnabled: true - onEntered: parent.visible = true - onExited: parent.visible = false + transform: Translate { + id: topBarSlide + y: root.reveal ? 0 : -(Theme.barHeight - 8) + + Behavior on y { + NumberAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + } } - } - } - } - Component { - id: separatorComponent + Item { + anchors.fill: parent + anchors.margins: 2 + anchors.topMargin: 6 + anchors.bottomMargin: 0 + anchors.leftMargin: 8 + anchors.rightMargin: 8 - Rectangle { - width: 1 - height: 20 - color: Theme.outline - opacity: 0.3 + Rectangle { + anchors.fill: parent + radius: Theme.cornerRadius + color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, + Theme.surfaceContainer.b, root.backgroundTransparency) + layer.enabled: true + + Rectangle { + anchors.fill: parent + color: "transparent" + border.color: Theme.outlineMedium + border.width: 1 + radius: parent.radius + } + + Rectangle { + anchors.fill: parent + color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, + Theme.surfaceTint.b, 0.04) + radius: parent.radius + + SequentialAnimation on opacity { + running: false + loops: Animation.Infinite + + NumberAnimation { + to: 0.08 + duration: Theme.extraLongDuration + easing.type: Theme.standardEasing + } + + NumberAnimation { + to: 0.02 + duration: Theme.extraLongDuration + easing.type: Theme.standardEasing + } + } + } + + layer.effect: MultiEffect { + shadowEnabled: true + shadowHorizontalOffset: 0 + shadowVerticalOffset: 4 + shadowBlur: 0.5 // radius/32, adjusted for visual match + shadowColor: Qt.rgba(0, 0, 0, 0.15) + shadowOpacity: 0.15 + } + } + + Item { + id: topBarContent + + readonly property int availableWidth: width + readonly property int launcherButtonWidth: 40 + readonly property int workspaceSwitcherWidth: 120 // Approximate + readonly property int focusedAppMaxWidth: 456 // Fixed width since we don't have focusedApp reference + readonly property int estimatedLeftSectionWidth: launcherButtonWidth + workspaceSwitcherWidth + + focusedAppMaxWidth + (Theme.spacingXS * 2) + readonly property int rightSectionWidth: rightSection.width + readonly property int clockWidth: 120 // Approximate clock width + readonly property int mediaMaxWidth: 280 // Normal max width + readonly property int weatherWidth: 80 // Approximate weather width + readonly property bool validLayout: availableWidth > 100 + && estimatedLeftSectionWidth > 0 + && rightSectionWidth > 0 + readonly property int clockLeftEdge: (availableWidth - clockWidth) / 2 + readonly property int clockRightEdge: clockLeftEdge + clockWidth + readonly property int leftSectionRightEdge: estimatedLeftSectionWidth + readonly property int mediaLeftEdge: clockLeftEdge - mediaMaxWidth - Theme.spacingS + readonly property int rightSectionLeftEdge: availableWidth - rightSectionWidth + readonly property int leftToClockGap: Math.max( + 0, + clockLeftEdge - leftSectionRightEdge) + readonly property int leftToMediaGap: mediaMaxWidth > 0 ? Math.max( + 0, + mediaLeftEdge - leftSectionRightEdge) : leftToClockGap + readonly property int mediaToClockGap: mediaMaxWidth > 0 ? Theme.spacingS : 0 + readonly property int clockToRightGap: validLayout ? Math.max( + 0, + rightSectionLeftEdge + - clockRightEdge) : 1000 + readonly property bool spacingTight: validLayout + && (leftToMediaGap < 150 + || clockToRightGap < 100) + readonly property bool overlapping: validLayout + && (leftToMediaGap < 100 + || clockToRightGap < 50) + + function getWidgetEnabled(enabled) { + return enabled !== undefined ? enabled : true + } + + function getWidgetVisible(widgetId) { + switch (widgetId) { + case "launcherButton": + return true + case "workspaceSwitcher": + return true + case "focusedWindow": + return true + case "clock": + return true + case "music": + return true + case "weather": + return true + case "systemTray": + return true + case "privacyIndicator": + return true + case "clipboard": + return true + case "cpuUsage": + return DankgopService.dankgopAvailable + case "memUsage": + return DankgopService.dankgopAvailable + case "cpuTemp": + return DankgopService.dankgopAvailable + case "gpuTemp": + return DankgopService.dankgopAvailable + case "notificationButton": + return true + case "battery": + return true + case "controlCenterButton": + return true + case "spacer": + return true + case "separator": + return true + default: + return false + } + } + + function getWidgetComponent(widgetId) { + switch (widgetId) { + case "launcherButton": + return launcherButtonComponent + case "workspaceSwitcher": + return workspaceSwitcherComponent + case "focusedWindow": + return focusedWindowComponent + case "clock": + return clockComponent + case "music": + return mediaComponent + case "weather": + return weatherComponent + case "systemTray": + return systemTrayComponent + case "privacyIndicator": + return privacyIndicatorComponent + case "clipboard": + return clipboardComponent + case "cpuUsage": + return cpuUsageComponent + case "memUsage": + return memUsageComponent + case "cpuTemp": + return cpuTempComponent + case "gpuTemp": + return gpuTempComponent + case "notificationButton": + return notificationButtonComponent + case "battery": + return batteryComponent + case "controlCenterButton": + return controlCenterButtonComponent + case "spacer": + return spacerComponent + case "separator": + return separatorComponent + default: + return null + } + } + + anchors.fill: parent + anchors.leftMargin: Theme.spacingM + anchors.rightMargin: Theme.spacingM + anchors.topMargin: Theme.spacingXS + anchors.bottomMargin: Theme.spacingXS + clip: true + + Row { + id: leftSection + + height: parent.height + spacing: Theme.spacingXS + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + + Repeater { + model: SettingsData.topBarLeftWidgetsModel + + Loader { + property string widgetId: model.widgetId + property var widgetData: model + property int spacerSize: model.size || 20 + + anchors.verticalCenter: parent ? parent.verticalCenter : undefined + active: topBarContent.getWidgetVisible(model.widgetId) + sourceComponent: topBarContent.getWidgetComponent(model.widgetId) + opacity: topBarContent.getWidgetEnabled(model.enabled) ? 1 : 0 + } + } + } + + Item { + id: centerSection + + property var centerWidgets: [] + property int totalWidgets: 0 + property real totalWidth: 0 + property real spacing: Theme.spacingS + + function updateLayout() { + // Defer layout if dimensions are invalid + if (width <= 0 || height <= 0 || !visible) { + Qt.callLater(updateLayout) + return + } + + centerWidgets = [] + totalWidgets = 0 + totalWidth = 0 + for (var i = 0; i < centerRepeater.count; i++) { + let item = centerRepeater.itemAt(i) + if (item && item.active && item.item) { + centerWidgets.push(item.item) + totalWidgets++ + totalWidth += item.item.width + } + } + if (totalWidgets > 1) + totalWidth += spacing * (totalWidgets - 1) + + positionWidgets() + } + + function positionWidgets() { + if (totalWidgets === 0 || width <= 0) + return + + let parentCenterX = width / 2 + if (totalWidgets % 2 === 1) { + let middleIndex = Math.floor(totalWidgets / 2) + let currentX = parentCenterX - (centerWidgets[middleIndex].width / 2) + centerWidgets[middleIndex].x = currentX + centerWidgets[middleIndex].anchors.horizontalCenter = undefined + currentX = centerWidgets[middleIndex].x + for (var i = middleIndex - 1; i >= 0; i--) { + currentX -= (spacing + centerWidgets[i].width) + centerWidgets[i].x = currentX + centerWidgets[i].anchors.horizontalCenter = undefined + } + currentX = centerWidgets[middleIndex].x + centerWidgets[middleIndex].width + for (var i = middleIndex + 1; i < totalWidgets; i++) { + currentX += spacing + centerWidgets[i].x = currentX + centerWidgets[i].anchors.horizontalCenter = undefined + currentX += centerWidgets[i].width + } + } else { + let leftMiddleIndex = (totalWidgets / 2) - 1 + let rightMiddleIndex = totalWidgets / 2 + let gapCenter = parentCenterX + let halfSpacing = spacing / 2 + centerWidgets[leftMiddleIndex].x = gapCenter - halfSpacing + - centerWidgets[leftMiddleIndex].width + centerWidgets[leftMiddleIndex].anchors.horizontalCenter = undefined + centerWidgets[rightMiddleIndex].x = gapCenter + halfSpacing + centerWidgets[rightMiddleIndex].anchors.horizontalCenter = undefined + let currentX = centerWidgets[leftMiddleIndex].x + for (var i = leftMiddleIndex - 1; i >= 0; i--) { + currentX -= (spacing + centerWidgets[i].width) + centerWidgets[i].x = currentX + centerWidgets[i].anchors.horizontalCenter = undefined + } + currentX = centerWidgets[rightMiddleIndex].x + centerWidgets[rightMiddleIndex].width + for (var i = rightMiddleIndex + 1; i < totalWidgets; i++) { + currentX += spacing + centerWidgets[i].x = currentX + centerWidgets[i].anchors.horizontalCenter = undefined + currentX += centerWidgets[i].width + } + } + } + + height: parent.height + width: parent.width + anchors.centerIn: parent + Component.onCompleted: { + Qt.callLater(() => { + Qt.callLater(updateLayout) + }) + } + + onWidthChanged: { + if (width > 0) { + Qt.callLater(updateLayout) + } + } + + onVisibleChanged: { + if (visible && width > 0) { + Qt.callLater(updateLayout) + } + } + + Repeater { + id: centerRepeater + + model: SettingsData.topBarCenterWidgetsModel + + Loader { + property string widgetId: model.widgetId + property var widgetData: model + property int spacerSize: model.size || 20 + + anchors.verticalCenter: parent ? parent.verticalCenter : undefined + active: topBarContent.getWidgetVisible(model.widgetId) + sourceComponent: topBarContent.getWidgetComponent(model.widgetId) + opacity: topBarContent.getWidgetEnabled(model.enabled) ? 1 : 0 + onLoaded: { + if (item) { + item.onWidthChanged.connect(centerSection.updateLayout) + if (model.widgetId === "spacer") + item.spacerSize = Qt.binding(() => { + return model.size || 20 + }) + + Qt.callLater(centerSection.updateLayout) + } + } + onActiveChanged: { + Qt.callLater(centerSection.updateLayout) + } + } + } + + Connections { + function onCountChanged() { + Qt.callLater(centerSection.updateLayout) + } + + target: SettingsData.topBarCenterWidgetsModel + } + } + + Row { + id: rightSection + + height: parent.height + spacing: Theme.spacingXS + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + Repeater { + model: SettingsData.topBarRightWidgetsModel + + Loader { + property string widgetId: model.widgetId + property var widgetData: model + property int spacerSize: model.size || 20 + + anchors.verticalCenter: parent ? parent.verticalCenter : undefined + active: topBarContent.getWidgetVisible(model.widgetId) + sourceComponent: topBarContent.getWidgetComponent(model.widgetId) + opacity: topBarContent.getWidgetEnabled(model.enabled) ? 1 : 0 + } + } + } + + Component { + id: launcherButtonComponent + + LauncherButton { + isActive: appDrawerPopout ? appDrawerPopout.isVisible : false + section: { + // Determine which section this loader is in by checking parent + if (parent && parent.parent) { + if (parent.parent === leftSection) + return "left" + if (parent.parent === rightSection) + return "right" + if (parent.parent === centerSection) + return "center" + } + return "left" // default fallback + } + popupTarget: appDrawerPopout + parentScreen: root.screen + onClicked: { + if (appDrawerPopout) + appDrawerPopout.toggle() + } + } + } + + Component { + id: workspaceSwitcherComponent + + WorkspaceSwitcher { + screenName: root.screenName + } + } + + Component { + id: focusedWindowComponent + + FocusedApp { + compactMode: topBarContent.spacingTight + availableWidth: topBarContent.leftToMediaGap + } + } + + Component { + id: clockComponent + + Clock { + compactMode: topBarContent.overlapping + section: { + if (parent && parent.parent === leftSection) + return "left" + if (parent && parent.parent === rightSection) + return "right" + if (parent && parent.parent === centerSection) + return "center" + return "center" + } + popupTarget: centcomPopout + parentScreen: root.screen + onClockClicked: { + centcomPopout.calendarVisible = !centcomPopout.calendarVisible + } + } + } + + Component { + id: mediaComponent + + Media { + compactMode: topBarContent.spacingTight || topBarContent.overlapping + section: { + if (parent && parent.parent === leftSection) + return "left" + if (parent && parent.parent === rightSection) + return "right" + if (parent && parent.parent === centerSection) + return "center" + return "center" + } + popupTarget: centcomPopout + parentScreen: root.screen + onClicked: { + centcomPopout.calendarVisible = !centcomPopout.calendarVisible + } + } + } + + Component { + id: weatherComponent + + Weather { + section: { + if (parent && parent.parent === leftSection) + return "left" + if (parent && parent.parent === rightSection) + return "right" + if (parent && parent.parent === centerSection) + return "center" + return "center" + } + popupTarget: centcomPopout + parentScreen: root.screen + onClicked: { + centcomPopout.calendarVisible = !centcomPopout.calendarVisible + } + } + } + + Component { + id: systemTrayComponent + + SystemTrayBar { + parentWindow: root + parentScreen: root.screen + } + } + + Component { + id: privacyIndicatorComponent + + PrivacyIndicator { + section: { + if (parent && parent.parent === leftSection) + return "left" + if (parent && parent.parent === rightSection) + return "right" + if (parent && parent.parent === centerSection) + return "center" + return "right" + } + parentScreen: root.screen + } + } + + Component { + id: clipboardComponent + + Rectangle { + width: 40 + height: 30 + radius: Theme.cornerRadius + color: { + const baseColor = clipboardArea.containsMouse ? Theme.primaryHover : Theme.secondaryHover + return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, + baseColor.a * Theme.widgetTransparency) + } + + DankIcon { + anchors.centerIn: parent + name: "content_paste" + size: Theme.iconSize - 6 + color: Theme.surfaceText + } + + MouseArea { + id: clipboardArea + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + clipboardHistoryModalPopup.toggle() + } + } + + Behavior on color { + ColorAnimation { + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + } + } + } + + Component { + id: cpuUsageComponent + + CpuMonitor { + section: { + if (parent && parent.parent === leftSection) + return "left" + if (parent && parent.parent === rightSection) + return "right" + if (parent && parent.parent === centerSection) + return "center" + return "right" + } + popupTarget: processListPopout + parentScreen: root.screen + toggleProcessList: () => { + return processListPopout.toggle() + } + } + } + + Component { + id: memUsageComponent + + RamMonitor { + section: { + if (parent && parent.parent === leftSection) + return "left" + if (parent && parent.parent === rightSection) + return "right" + if (parent && parent.parent === centerSection) + return "center" + return "right" + } + popupTarget: processListPopout + parentScreen: root.screen + toggleProcessList: () => { + return processListPopout.toggle() + } + } + } + + Component { + id: cpuTempComponent + + CpuTemperature { + section: { + if (parent && parent.parent === leftSection) + return "left" + if (parent && parent.parent === rightSection) + return "right" + if (parent && parent.parent === centerSection) + return "center" + return "right" + } + popupTarget: processListPopout + parentScreen: root.screen + toggleProcessList: () => { + return processListPopout.toggle() + } + } + } + + Component { + id: gpuTempComponent + + GpuTemperature { + section: { + if (parent && parent.parent === leftSection) + return "left" + if (parent && parent.parent === rightSection) + return "right" + if (parent && parent.parent === centerSection) + return "center" + return "right" + } + popupTarget: processListPopout + parentScreen: root.screen + widgetData: parent.widgetData + toggleProcessList: () => { + return processListPopout.toggle() + } + } + } + + Component { + id: notificationButtonComponent + + NotificationCenterButton { + hasUnread: root.notificationCount > 0 + isActive: notificationCenter.notificationHistoryVisible + section: { + if (parent && parent.parent === leftSection) + return "left" + if (parent && parent.parent === rightSection) + return "right" + if (parent && parent.parent === centerSection) + return "center" + return "right" + } + popupTarget: notificationCenter + parentScreen: root.screen + onClicked: { + notificationCenter.notificationHistoryVisible + = !notificationCenter.notificationHistoryVisible + } + } + } + + Component { + id: batteryComponent + + Battery { + batteryPopupVisible: batteryPopout.batteryPopupVisible + section: { + if (parent && parent.parent === leftSection) + return "left" + if (parent && parent.parent === rightSection) + return "right" + if (parent && parent.parent === centerSection) + return "center" + return "right" + } + popupTarget: batteryPopout + parentScreen: root.screen + onToggleBatteryPopup: { + batteryPopout.batteryPopupVisible = !batteryPopout.batteryPopupVisible + } + } + } + + Component { + id: controlCenterButtonComponent + + ControlCenterButton { + isActive: controlCenterPopout.controlCenterVisible + section: { + if (parent && parent.parent === leftSection) + return "left" + if (parent && parent.parent === rightSection) + return "right" + if (parent && parent.parent === centerSection) + return "center" + return "right" + } + popupTarget: controlCenterPopout + parentScreen: root.screen + onClicked: { + controlCenterPopout.triggerScreen = root.screen + controlCenterPopout.controlCenterVisible = !controlCenterPopout.controlCenterVisible + if (controlCenterPopout.controlCenterVisible) { + if (NetworkService.wifiEnabled) + NetworkService.scanWifi() + } + } + } + } + + Component { + id: spacerComponent + + Item { + width: parent.spacerSize || 20 + height: 30 + + Rectangle { + anchors.fill: parent + color: "transparent" + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, + Theme.outline.b, 0.1) + border.width: 1 + radius: 2 + visible: false + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: parent.visible = true + onExited: parent.visible = false + } + } + } + } + + Component { + id: separatorComponent + + Rectangle { + width: 1 + height: 20 + color: Theme.outline + opacity: 0.3 + } + } + } + } } - } } - } }