diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index bca0e5cbc..1cb0c6488 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -994,9 +994,10 @@ Singleton { property string dankIslandPalette: "default" property bool dankIslandHighContrast: false property bool dankIslandMediaClockVisible: true + property bool dankIslandHomeNotificationBadge: true property bool dankIslandNotificationExpand: false property string dankIslandHomeMediaSlot: "left" - property string dankIslandHomeStatusSlot: "right" + property string dankIslandHomeStatusSlot: "hidden" property string dankIslandHomeWeatherSlot: "hidden" property bool dankIslandHomeCompactTight: false property string dankIslandBatteryStyle: "solid" diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index df7d784ea..f9de2b255 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -534,9 +534,10 @@ var SPEC = { dankIslandPalette: { def: "default" }, dankIslandHighContrast: { def: false }, dankIslandMediaClockVisible: { def: true }, + dankIslandHomeNotificationBadge: { def: true }, dankIslandNotificationExpand: { def: false }, dankIslandHomeMediaSlot: { def: "left" }, - dankIslandHomeStatusSlot: { def: "right" }, + dankIslandHomeStatusSlot: { def: "hidden" }, dankIslandHomeWeatherSlot: { def: "hidden" }, dankIslandHomeCompactTight: { def: false }, dankIslandBatteryStyle: { def: "solid" }, diff --git a/quickshell/Modules/DankIsland/Activities/HomeCompact.qml b/quickshell/Modules/DankIsland/Activities/HomeCompact.qml index aa0bc0b63..9cdf531cb 100644 --- a/quickshell/Modules/DankIsland/Activities/HomeCompact.qml +++ b/quickshell/Modules/DankIsland/Activities/HomeCompact.qml @@ -15,22 +15,28 @@ Item { readonly property string timeText: systemClock.date.toLocaleTimeString(I18n.locale(), SettingsData.getEffectiveTimeFormat()) readonly property string dateText: systemClock.date.toLocaleDateString(I18n.locale(), SettingsData.getEffectiveDateFormat("ddd MMM d")) readonly property bool tight: root.controller.homeCompactTight - readonly property real slotSize: root.tight ? 24 : 32 - readonly property real iconSize: root.tight ? 16 : Theme.iconSizeSmall - readonly property real itemSpacing: root.tight ? Theme.spacingXS : Theme.spacingS - readonly property real dotSize: root.tight ? 3 : 4 + readonly property real slotSize: root.tight ? Theme.iconSize : Theme.iconSizeLarge + readonly property real textSize: root.tight ? Theme.fontSizeSmall : Theme.fontSizeMedium + readonly property real iconSize: root.textSize + Theme.spacingXS + readonly property real statusIconSize: root.textSize + Theme.spacingXXS + readonly property real groupSpacing: root.controller.homeSlotMargin readonly property bool weatherSlotEnabled: root.controller.homeWeatherSlot !== "hidden" - readonly property var clusterIds: root.itemsForSide("left").concat(["clock"]).concat(root.itemsForSide("right")) + readonly property var groupIds: root.groupsForSide("left").concat(["clock"]).concat(root.groupsForSide("right")) property bool weatherRefHeld: false - function itemsForSide(side) { + function groupsForSide(side) { const ids = []; - if (root.controller.homeMediaSlot === side) + const media = root.controller.homeMediaSlot === side; + if (media && side === "left") ids.push("media"); - if (root.controller.homeWeatherSlot === side) + if (root.controller.homeWeatherSlot === side && WeatherService.weather.available) ids.push("weather"); if (root.controller.homeStatusSlot === side) ids.push("status"); + if (side === "right" && root.controller.homeNotificationBadge) + ids.push("notifications"); + if (media && side === "right") + ids.push("media"); return ids; } @@ -66,16 +72,6 @@ Item { precision: SettingsData.showSeconds ? SystemClock.Seconds : SystemClock.Minutes } - component ClusterDot: Rectangle { - property bool unread: false - - anchors.verticalCenter: parent.verticalCenter - width: root.dotSize + (unread ? 1 : 0) - height: width - radius: height / 2 - color: unread ? Theme.secondary : Theme.primary - } - component HoverBackdrop: Rectangle { property bool hovered: false @@ -86,184 +82,173 @@ Item { color: hovered ? Theme.surfaceTextHover : "transparent" } - component ClusterItem: Row { + component GroupItem: Item { id: item - required property string itemId - property int itemIndex: 0 + required property string groupId - readonly property bool isClock: item.itemId === "clock" - readonly property bool isMedia: item.itemId === "media" - readonly property bool isWeather: item.itemId === "weather" - readonly property bool isStatus: item.itemId === "status" + readonly property bool isClock: item.groupId === "clock" + readonly property bool isMedia: item.groupId === "media" + readonly property bool isWeather: item.groupId === "weather" + readonly property bool isStatus: item.groupId === "status" + readonly property bool isNotifications: item.groupId === "notifications" readonly property bool usesBattery: item.isStatus && BatteryService.batteryAvailable - spacing: root.itemSpacing + width: { + if (item.isMedia) + return root.iconSize; + if (item.isWeather) + return weatherRow.implicitWidth; + if (item.isNotifications) + return notificationRow.implicitWidth; + if (item.isStatus) + return item.usesBattery ? batteryMeter.width : root.iconSize; + return clockRow.implicitWidth; + } + height: root.slotSize - ClusterDot { - visible: item.itemIndex > 0 + HoverBackdrop { + visible: item.isMedia || item.isWeather || item.isNotifications || (item.isStatus && !item.usesBattery) + hovered: groupArea.containsMouse } - Item { - width: clockRow.implicitWidth - height: root.slotSize - visible: item.isClock - - Row { - id: clockRow - - anchors.verticalCenter: parent.verticalCenter - spacing: root.itemSpacing - - NumericText { - anchors.verticalCenter: parent.verticalCenter - isMonospace: false - text: root.timeText - reserveText: root.timeText.replace(/\d/g, "0") - width: Math.ceil(Math.max(implicitWidth, reservedWidth)) - horizontalAlignment: Text.AlignHCenter - color: Theme.surfaceText - font.pixelSize: Theme.fontSizeSmall - } - - ClusterDot { - id: unreadDot - - unread: root.controller.homeNotificationBadge - - IslandSlotHoverArea { - anchors.centerIn: parent - width: parent.width + Theme.spacingXS - height: parent.height + Theme.spacingXS - enabled: unreadDot.unread - controller: root.controller - onClicked: root.controller.requestNotificationCenter(false) - } - } - - StyledText { - anchors.verticalCenter: parent.verticalCenter - text: root.dateText - color: Theme.surfaceTextMedium - font.pixelSize: Theme.fontSizeSmall - } - } - } - - Item { + AudioVisualization { + anchors.centerIn: parent width: root.iconSize + Theme.spacingXS - height: root.slotSize - visible: item.isMedia - - HoverBackdrop { - hovered: mediaArea.containsMouse - } - - AudioVisualization { - anchors.centerIn: parent - width: root.iconSize + Theme.spacingXS - height: width - maxBarHeight: Math.max(3, height - 2) - idleIconName: "graphic_eq" - visible: root.controller.mediaAvailable - } - - DankIcon { - anchors.centerIn: parent - visible: !root.controller.mediaAvailable - name: "search" - size: root.iconSize - color: Theme.surfaceTextMedium - } - - IslandSlotHoverArea { - id: mediaArea - - anchors.fill: parent - controller: root.controller - onClicked: { - if (root.controller.mediaAvailable) { - root.controller.requestActivity("media", false, false); - return; - } - root.controller.requestLauncher("", "", false); - } - } + height: width + maxBarHeight: Math.max(3, height - 2) + idleIconName: "graphic_eq" + visible: item.isMedia && root.controller.mediaAvailable } - Item { - width: weatherRow.implicitWidth - height: root.slotSize - visible: item.isWeather + DankIcon { + anchors.centerIn: parent + visible: item.isMedia && !root.controller.mediaAvailable + name: "search" + size: root.iconSize + color: Theme.surfaceTextMedium + } - HoverBackdrop { - hovered: weatherArea.containsMouse - } + Row { + id: clockRow - Row { - id: weatherRow + anchors.verticalCenter: parent.verticalCenter + visible: item.isClock + spacing: Theme.spacingS + NumericText { anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingXXS - - DankIcon { - anchors.verticalCenter: parent.verticalCenter - name: WeatherService.getWeatherIcon(WeatherService.weather.wCode) - size: root.iconSize - color: Theme.primary - } - - StyledText { - anchors.verticalCenter: parent.verticalCenter - text: WeatherService.currentTempText() - color: Theme.surfaceTextSecondary - font.pixelSize: Theme.fontSizeSmall - } + isMonospace: false + text: root.timeText + reserveText: root.timeText.replace(/\d/g, "0") + width: Math.ceil(Math.max(implicitWidth, reservedWidth)) + color: Theme.surfaceText + font.pixelSize: root.textSize } - IslandSlotHoverArea { - id: weatherArea + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: Theme.spacingXS + height: Theme.spacingXS + radius: height / 2 + color: Theme.primary + } - anchors.fill: parent - controller: root.controller - onClicked: root.controller.requestWeather(false) + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: root.dateText + color: Theme.surfaceTextMedium + font.pixelSize: root.textSize } } - Item { - width: item.usesBattery ? batteryMeter.width : root.iconSize - height: root.slotSize - visible: item.isStatus + Row { + id: notificationRow - HoverBackdrop { - hovered: statusArea.containsMouse && !item.usesBattery - } - - BatteryMeter { - id: batteryMeter - - anchors.centerIn: parent - visible: item.usesBattery - thickness: root.tight ? 12 : 14 - fontSize: Theme.fontSizeSmall - hovered: statusArea.containsMouse - outlined: SettingsData.dankIslandBatteryStyle === "outline" - } + anchors.verticalCenter: parent.verticalCenter + visible: item.isNotifications + spacing: Theme.spacingXXS DankIcon { - anchors.centerIn: parent - visible: !item.usesBattery - name: "tune" - size: root.iconSize - color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + name: "notifications" + size: root.statusIconSize + color: Theme.secondary } - IslandSlotHoverArea { - id: statusArea + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: root.controller.unreadNotificationCount + color: Theme.surfaceTextSecondary + font.pixelSize: root.textSize + } + } - anchors.fill: parent - controller: root.controller - onClicked: root.controller.requestControlCenter("", false) + Row { + id: weatherRow + + anchors.verticalCenter: parent.verticalCenter + visible: item.isWeather + spacing: Theme.spacingXXS + + DankIcon { + anchors.verticalCenter: parent.verticalCenter + name: WeatherService.getWeatherIcon(WeatherService.weather.wCode) + size: root.statusIconSize + color: Theme.surfaceTextSecondary + } + + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: WeatherService.currentTempText() + color: Theme.surfaceTextSecondary + font.pixelSize: root.textSize + } + } + + BatteryMeter { + id: batteryMeter + + anchors.centerIn: parent + visible: item.usesBattery + thickness: root.statusIconSize + fontSize: root.textSize + hovered: groupArea.containsMouse + outlined: SettingsData.dankIslandBatteryStyle === "outline" + } + + DankIcon { + anchors.centerIn: parent + visible: item.isStatus && !item.usesBattery + name: "tune" + size: root.iconSize + color: Theme.surfaceText + } + + IslandSlotHoverArea { + id: groupArea + + anchors.fill: parent + enabled: !item.isClock + controller: root.controller + onClicked: { + if (item.isMedia && root.controller.mediaAvailable) { + root.controller.requestActivity("media", false, false); + return; + } + if (item.isMedia) { + root.controller.requestLauncher("", "", false); + return; + } + if (item.isWeather) { + root.controller.requestWeather(false); + return; + } + if (item.isNotifications) { + root.controller.requestNotificationCenter(false); + return; + } + root.controller.requestControlCenter("", false); } } } @@ -273,16 +258,14 @@ Item { anchors.centerIn: parent height: root.slotSize - spacing: root.itemSpacing + spacing: root.groupSpacing Repeater { - model: root.clusterIds + model: root.groupIds - ClusterItem { + GroupItem { required property var modelData - required property int index - itemId: String(modelData) - itemIndex: index + groupId: String(modelData) } } } diff --git a/quickshell/Modules/DankIsland/Activities/MediaCompact.qml b/quickshell/Modules/DankIsland/Activities/MediaCompact.qml index 245d90f09..7df7d05ef 100644 --- a/quickshell/Modules/DankIsland/Activities/MediaCompact.qml +++ b/quickshell/Modules/DankIsland/Activities/MediaCompact.qml @@ -115,6 +115,7 @@ Item { text: root.mediaModel.artist color: Theme.surfaceTextSecondary font.pixelSize: Math.max(9, Theme.fontSizeSmall - 2) + wrapMode: Text.NoWrap elide: Text.ElideRight } } diff --git a/quickshell/Modules/DankIsland/Activities/NotificationCompact.qml b/quickshell/Modules/DankIsland/Activities/NotificationCompact.qml index 27980c05f..7a424ad04 100644 --- a/quickshell/Modules/DankIsland/Activities/NotificationCompact.qml +++ b/quickshell/Modules/DankIsland/Activities/NotificationCompact.qml @@ -79,6 +79,7 @@ Item { color: Theme.surfaceTextSecondary font.pixelSize: Theme.fontSizeSmall font.weight: Font.Medium + wrapMode: Text.NoWrap elide: Text.ElideRight } @@ -88,6 +89,7 @@ Item { color: Theme.surfaceText font.pixelSize: root.dense ? Theme.fontSizeSmall : Theme.fontSizeMedium font.weight: Font.DemiBold + wrapMode: Text.NoWrap elide: Text.ElideRight } } diff --git a/quickshell/Modules/DankIsland/DankIslandSurface.qml b/quickshell/Modules/DankIsland/DankIslandSurface.qml index d9ad96fb8..37d7b772d 100644 --- a/quickshell/Modules/DankIsland/DankIslandSurface.qml +++ b/quickshell/Modules/DankIsland/DankIslandSurface.qml @@ -235,6 +235,8 @@ Item { id: contentHost controller: root.controller + islandX: root.currentVisualX + hostWidth: root.width springTimeConstantMs: root.springTimeConstantMs morphProgress: root.morphProgress expanded: root.controller.expanded diff --git a/quickshell/Modules/DankIsland/IslandContentHost.qml b/quickshell/Modules/DankIsland/IslandContentHost.qml index 2cac21918..f67654dd6 100644 --- a/quickshell/Modules/DankIsland/IslandContentHost.qml +++ b/quickshell/Modules/DankIsland/IslandContentHost.qml @@ -13,6 +13,8 @@ Item { clip: true required property var controller + required property real islandX + required property real hostWidth required property real springTimeConstantMs required property real morphProgress required property bool expanded @@ -148,7 +150,13 @@ Item { } component CompactFace: Loader { - anchors.fill: parent + required property string activity + readonly property var target: root.controller.compactTargetFor(activity) + + x: Math.round((root.hostWidth - target.width) / 2 + target.offsetX) - Math.round(root.islandX) + y: Math.round((parent.height - height) / 2) + width: target.width + height: target.height asynchronous: false visible: opacity > 0.001 enabled: opacity >= 0.5 @@ -166,6 +174,7 @@ Item { CompactFace { active: true + activity: "home" sourceComponent: root.homeCompactComponent opacity: root.compactOpacity("home") } @@ -182,6 +191,7 @@ Item { CompactFace { active: root.mediaSurfaceActive + activity: "media" sourceComponent: root.mediaCompactComponent opacity: root.compactOpacity("media") } @@ -198,6 +208,7 @@ Item { CompactFace { active: root.surfaceActive("launcher") + activity: "launcher" sourceComponent: root.launcherCompactComponent opacity: root.compactOpacity("launcher") } @@ -214,6 +225,7 @@ Item { CompactFace { active: root.surfaceActive("controlcenter") + activity: "controlcenter" sourceComponent: root.controlCenterCompactComponent opacity: root.compactOpacity("controlcenter") } @@ -228,6 +240,7 @@ Item { CompactFace { active: root.surfaceActive("wallpaper") + activity: "wallpaper" sourceComponent: root.wallpaperCompactComponent opacity: root.compactOpacity("wallpaper") } @@ -244,6 +257,7 @@ Item { CompactFace { active: root.surfaceActive("weather") + activity: "weather" sourceComponent: root.weatherCompactComponent opacity: root.compactOpacity("weather") } @@ -260,6 +274,7 @@ Item { CompactFace { active: root.surfaceActive("notificationcenter") + activity: "notificationcenter" sourceComponent: root.notificationCenterCompactComponent opacity: root.compactOpacity("notificationcenter") } @@ -276,6 +291,7 @@ Item { CompactFace { active: root.systemSurfaceActive + activity: "volume" sourceComponent: root.systemCompactComponent opacity: Math.max(root.compactOpacity("volume"), root.compactOpacity("brightness")) } @@ -290,6 +306,7 @@ Item { CompactFace { active: root.surfaceActive("notification") + activity: "notification" sourceComponent: root.notificationCompactComponent opacity: root.compactOpacity("notification") } diff --git a/quickshell/Modules/DankIsland/IslandController.qml b/quickshell/Modules/DankIsland/IslandController.qml index 1d9340f03..1417c921d 100644 --- a/quickshell/Modules/DankIsland/IslandController.qml +++ b/quickshell/Modules/DankIsland/IslandController.qml @@ -59,10 +59,8 @@ QtObject { readonly property real homeCompactFaceHeight: homeCompactTight ? Math.max(16, Math.min(32, compactHeight - 8)) : compactFaceHeight property int unreadNotificationCount: 0 - readonly property bool homeNotificationBadge: unreadNotificationCount > 0 - readonly property real homeSlotMargin: homeCompactTight ? Theme.spacingXS : Theme.spacingS - readonly property real homeSlotSpacing: homeCompactTight ? Theme.spacingXXS : Theme.spacingXS - readonly property real homeClusterGap: homeCompactTight ? 6 : 10 + readonly property bool homeNotificationBadge: SettingsData.dankIslandHomeNotificationBadge && unreadNotificationCount > 0 + readonly property real homeSlotMargin: homeCompactTight ? Theme.spacingS : Theme.spacingM property real homeContentWidth: 200 property real mediaContentWidth: 360 readonly property real mediaCompactMaxWidth: 360 @@ -273,7 +271,7 @@ QtObject { } readonly property string homeMediaSlot: resolvedHomeSlot(SettingsData.dankIslandHomeMediaSlot, "left") - readonly property string homeStatusSlot: resolvedHomeSlot(SettingsData.dankIslandHomeStatusSlot, "right") + readonly property string homeStatusSlot: resolvedHomeSlot(SettingsData.dankIslandHomeStatusSlot, "hidden") readonly property string homeWeatherSlot: SettingsData.weatherEnabled ? resolvedHomeSlot(SettingsData.dankIslandHomeWeatherSlot, "hidden") : "hidden" readonly property real homeCompactWidth: Math.ceil(homeSlotMargin * 2 + Math.max(homeContentWidth, 1)) diff --git a/quickshell/Modules/Settings/DankIslandTab.qml b/quickshell/Modules/Settings/DankIslandTab.qml index fa7d6200e..ac9162691 100644 --- a/quickshell/Modules/Settings/DankIslandTab.qml +++ b/quickshell/Modules/Settings/DankIslandTab.qml @@ -190,10 +190,10 @@ Item { text: I18n.tr("Battery / Control Center", "island settings: battery or control center slot row") description: BatteryService.batteryAvailable ? I18n.tr("Battery gauge opens Control Center", "island settings: status slot description with battery") : I18n.tr("Tools icon opens Control Center", "island settings: status slot description without battery") model: [I18n.tr("Left", "island settings: battery or control center slot left of the clock"), I18n.tr("Right", "island settings: battery or control center slot right of the clock"), I18n.tr("Hidden", "island settings: battery or control center slot hidden")] - currentIndex: root.valueIndex(root.homeSlotValues, SettingsData.dankIslandHomeStatusSlot, "right") + currentIndex: root.valueIndex(root.homeSlotValues, SettingsData.dankIslandHomeStatusSlot, "hidden") onSelectionChanged: (index, selected) => { if (selected) - SettingsData.set("dankIslandHomeStatusSlot", root.homeSlotValues[index] ?? "right"); + SettingsData.set("dankIslandHomeStatusSlot", root.homeSlotValues[index] ?? "hidden"); } } @@ -293,6 +293,15 @@ Item { onToggled: checked => SettingsData.set("dankIslandMediaClockVisible", checked) } + SettingsToggleRow { + settingKey: "dankIslandHomeNotificationBadge" + tags: ["island", "home", "notifications", "badge", "unread", "count"] + text: I18n.tr("Show Badge", "island settings: unread notification count on the home face") + description: I18n.tr("Unread notification count beside the clock", "island settings: notification badge description") + checked: SettingsData.dankIslandHomeNotificationBadge + onToggled: checked => SettingsData.set("dankIslandHomeNotificationBadge", checked) + } + SettingsButtonGroupRow { settingKey: "dankIslandBatteryStyle" tags: ["island", "battery", "gauge", "solid", "outline", "appearance"] diff --git a/quickshell/shell.qml b/quickshell/shell.qml index 64a25d569..690d8a219 100644 --- a/quickshell/shell.qml +++ b/quickshell/shell.qml @@ -28,6 +28,8 @@ ShellRoot { DC.Log.backend = Log; DC.Host.session = SessionService; DC.Host.cache = CacheData; + // Build the polkit agent here, outside incubation: first-touching it from a Connections target during DMSShell's async load crashed QQmlConnections::connectSignalsToMethods. + void PolkitService.agent; } Loader { diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 9eab5666a..13d42fd35 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -12549,6 +12549,32 @@ "icon": "widgets", "description": "Place independent widgets to left and right of island" }, + { + "section": "dankIslandHomeNotificationBadge", + "label": "Show Badge", + "tabIndex": 46, + "category": "Dank Island", + "keywords": [ + "activities", + "alert", + "badge", + "beside", + "clock", + "count", + "dank", + "dynamic island", + "home", + "island", + "notif", + "notification", + "notifications", + "show", + "time", + "unread", + "watch" + ], + "description": "Unread notification count beside the clock" + }, { "section": "dankIslandSatellitesEnabled", "label": "Show Satellite Widgets",