diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 460eadbc1..19716b266 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -1021,8 +1021,6 @@ Singleton { property string dankIslandHomeVolumeDisplay: "both" property string dankIslandHomeBrightnessSlot: "hidden" property string dankIslandHomeBrightnessDisplay: "both" - property var dankIslandHomeLeftOrder: ["media", "weather", "status", "volume", "brightness", "notifications"] - property var dankIslandHomeRightOrder: ["weather", "status", "notifications", "media", "volume", "brightness"] property string dankIslandBatteryStyle: "solid" property bool dankIslandSatellitesEnabled: true property string dankIslandSatellitePosition: "edges" diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index b6763ef2b..6f581d7dd 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -557,8 +557,6 @@ var SPEC = { dankIslandHomeVolumeDisplay: { def: "both" }, dankIslandHomeBrightnessSlot: { def: "hidden" }, dankIslandHomeBrightnessDisplay: { def: "both" }, - dankIslandHomeLeftOrder: { def: ["media", "weather", "status", "volume", "brightness", "notifications"] }, - dankIslandHomeRightOrder: { def: ["weather", "status", "notifications", "media", "volume", "brightness"] }, dankIslandBatteryStyle: { def: "solid" }, dankIslandSatellitesEnabled: { def: true }, dankIslandSatellitePosition: { def: "edges" }, diff --git a/quickshell/Modules/DankIsland/Activities/HomeCompact.qml b/quickshell/Modules/DankIsland/Activities/HomeCompact.qml index fb3cd6f36..6dd080af8 100644 --- a/quickshell/Modules/DankIsland/Activities/HomeCompact.qml +++ b/quickshell/Modules/DankIsland/Activities/HomeCompact.qml @@ -27,43 +27,74 @@ Item { readonly property real brightnessMaximum: DisplayService.brightnessMaximum(root.brightnessDevice) readonly property int brightnessPercent: root.brightnessMaximum > 0 ? Math.round(DisplayService.brightnessLevel / root.brightnessMaximum * 100) : 0 readonly property int volumePercent: Math.min(AudioService.sinkMaxVolume, Math.round((AudioService.sink?.audio?.volume ?? 0) * 100)) + readonly property var leftGroupOrder: ["media", "weather", "status", "volume", "brightness"] + readonly property var rightGroupOrder: ["weather", "status", "volume", "brightness", "notifications", "media"] readonly property var groupIds: root.groupsForSide("left").concat(["clock"]).concat(root.groupsForSide("right")) + readonly property real touchpadThreshold: 100 + property real wheelAccumulator: 0 property bool weatherRefHeld: false function groupsForSide(side) { - const ids = []; - const order = side === "left" ? root.controller.homeLeftOrder : root.controller.homeRightOrder; - for (const id of order) { - if (id === "media" && root.controller.homeMediaSlot === side) - ids.push(id); - else if (id === "weather" && root.controller.homeWeatherSlot === side && WeatherService.weather.available) - ids.push(id); - else if (id === "status" && root.controller.homeStatusSlot === side) - ids.push(id); - else if (id === "notifications" && side === "right" && root.controller.homeNotificationBadge) - ids.push(id); - else if (id === "volume" && root.controller.homeVolumeSlot === side && AudioService.sink?.audio) - ids.push(id); - else if (id === "brightness" && root.controller.homeBrightnessSlot === side && DisplayService.brightnessAvailable && root.brightnessDevice) - ids.push(id); + const order = side === "left" ? root.leftGroupOrder : root.rightGroupOrder; + return order.filter(id => root.groupShown(id, side)); + } + + function groupShown(id, side) { + switch (id) { + case "media": + return root.controller.homeMediaSlot === side; + case "weather": + return root.controller.homeWeatherSlot === side && WeatherService.weather.available; + case "status": + return root.controller.homeStatusSlot === side; + case "notifications": + return side === "right" && root.controller.homeNotificationBadge; + case "volume": + return root.controller.homeVolumeSlot === side && !!AudioService.sink?.audio; + case "brightness": + return root.controller.homeBrightnessSlot === side && DisplayService.brightnessAvailable && !!root.brightnessDevice; } - return ids; + return false; + } + + function wheelDirection(delta) { + const isMouseWheel = Math.abs(delta) >= 120 && Math.abs(delta) % 120 === 0; + if (isMouseWheel) + return delta > 0 ? 1 : -1; + root.wheelAccumulator += delta; + if (Math.abs(root.wheelAccumulator) < root.touchpadThreshold) + return 0; + const direction = root.wheelAccumulator > 0 ? 1 : -1; + root.wheelAccumulator = 0; + return direction; } function adjustSystemLevel(activityId, delta) { - if (delta === 0) + const direction = root.wheelDirection(delta); + if (direction === 0) return; - const direction = delta > 0 ? 1 : -1; - if (activityId === "volume") { - if (!AudioService.sink?.audio) - return; - SessionData.suppressOSDTemporarily(); - AudioService.adjustDefaultSinkVolume(5, direction); - AudioService.playVolumeChangeSoundIfEnabled(); + switch (activityId) { + case "volume": + root.adjustVolume(direction); + return; + case "brightness": + root.adjustBrightness(direction); return; } - if (activityId !== "brightness" || !root.brightnessDevice) + } + + function adjustVolume(direction) { + if (!AudioService.sink?.audio) return; + SessionData.suppressOSDTemporarily(); + AudioService.adjustDefaultSinkVolume(AudioService.wheelVolumeStep, direction); + AudioService.playVolumeChangeSoundIfEnabled(); + } + + function adjustBrightness(direction) { + if (!root.brightnessDevice) + return; + SessionData.suppressOSDTemporarily(); const next = Math.max(DisplayService.brightnessMinimum(root.brightnessDevice), Math.min(root.brightnessMaximum, DisplayService.brightnessLevel + direction * 5)); DisplayService.setBrightness(next, root.brightnessDevice.id, true); } @@ -255,14 +286,14 @@ Item { visible: systemLevelRow.displayMode !== "percentage" name: item.isVolume ? AudioService.sinkVolumeIconName : DisplayService.brightnessIconName(root.brightnessDevice, DisplayService.brightnessLevel) size: root.statusIconSize - color: item.isBrightness ? Theme.primary : Theme.surfaceTextSecondary + color: Theme.surfaceTextSecondary } NumericText { anchors.verticalCenter: parent.verticalCenter visible: systemLevelRow.displayMode !== "icon" text: (item.isVolume ? root.volumePercent : root.brightnessPercent) + "%" - reserveText: item.isVolume ? Math.round(AudioService.sinkMaxVolume) + "%" : "100%" + reserveText: item.isVolume ? AudioService.sinkMaxVolume + "%" : "100%" color: Theme.surfaceTextSecondary font.pixelSize: root.textSize } @@ -314,7 +345,7 @@ Item { return; } if (item.isVolume || item.isBrightness) { - root.systemModel.show(item.groupId); + root.systemModel.open(item.groupId); return; } root.controller.requestControlCenter("", false); diff --git a/quickshell/Modules/DankIsland/IslandController.qml b/quickshell/Modules/DankIsland/IslandController.qml index 24a4aeb88..27c98bf46 100644 --- a/quickshell/Modules/DankIsland/IslandController.qml +++ b/quickshell/Modules/DankIsland/IslandController.qml @@ -279,21 +279,6 @@ QtObject { return "both"; } - function resolvedHomeOrder(value, fallback) { - const known = ["media", "weather", "status", "notifications", "volume", "brightness"]; - const order = []; - const configured = Array.isArray(value) ? value : []; - for (const id of configured) { - if (known.indexOf(id) !== -1 && order.indexOf(id) === -1) - order.push(id); - } - for (const id of fallback) { - if (order.indexOf(id) === -1) - order.push(id); - } - return order; - } - readonly property string homeMediaSlot: resolvedHomeSlot(SettingsData.dankIslandHomeMediaSlot, "left") readonly property string homeStatusSlot: resolvedHomeSlot(SettingsData.dankIslandHomeStatusSlot, "hidden") readonly property string homeWeatherSlot: SettingsData.weatherEnabled ? resolvedHomeSlot(SettingsData.dankIslandHomeWeatherSlot, "hidden") : "hidden" @@ -301,8 +286,6 @@ QtObject { readonly property string homeVolumeDisplay: resolvedSystemLevelDisplay(SettingsData.dankIslandHomeVolumeDisplay) readonly property string homeBrightnessSlot: resolvedHomeSlot(SettingsData.dankIslandHomeBrightnessSlot, "hidden") readonly property string homeBrightnessDisplay: resolvedSystemLevelDisplay(SettingsData.dankIslandHomeBrightnessDisplay) - readonly property var homeLeftOrder: resolvedHomeOrder(SettingsData.dankIslandHomeLeftOrder, ["media", "weather", "status", "volume", "brightness", "notifications"]) - readonly property var homeRightOrder: resolvedHomeOrder(SettingsData.dankIslandHomeRightOrder, ["weather", "status", "notifications", "media", "volume", "brightness"]) readonly property real homeCompactWidth: Math.ceil(homeSlotMargin * 2 + Math.max(homeContentWidth, 1)) readonly property real mediaCompactWidth: Math.ceil(Math.max(1, mediaContentWidth)) diff --git a/quickshell/Modules/DankIsland/IslandSystemSource.qml b/quickshell/Modules/DankIsland/IslandSystemSource.qml index 11ae782c1..ddecd6d87 100644 --- a/quickshell/Modules/DankIsland/IslandSystemSource.qml +++ b/quickshell/Modules/DankIsland/IslandSystemSource.qml @@ -27,6 +27,10 @@ QtObject { function show(activityKind) { if (SessionData.suppressOSD) return; + open(activityKind); + } + + function open(activityKind) { if (controller.requestSystemActivity(activityKind)) kind = activityKind; } diff --git a/quickshell/Modules/Settings/DankIslandTab.qml b/quickshell/Modules/Settings/DankIslandTab.qml index 0e1a8496a..9063c07d3 100644 --- a/quickshell/Modules/Settings/DankIslandTab.qml +++ b/quickshell/Modules/Settings/DankIslandTab.qml @@ -5,7 +5,6 @@ import qs.Common import qs.Modules.Settings.Widgets import qs.Services import qs.Widgets -import "../../Common/QmlUtils.js" as QmlUtils Item { id: root @@ -42,113 +41,6 @@ Item { return index >= 0 ? index : Math.max(0, values.indexOf(fallback)); } - function setHomeActivitySlot(activityId, settingKey, index, selected) { - if (!selected) - return; - const slot = root.homeSlotValues[index] ?? "hidden"; - const leftOrder = Array.isArray(SettingsData.dankIslandHomeLeftOrder) ? SettingsData.dankIslandHomeLeftOrder.filter(id => id !== activityId) : []; - const rightOrder = Array.isArray(SettingsData.dankIslandHomeRightOrder) ? SettingsData.dankIslandHomeRightOrder.filter(id => id !== activityId) : []; - if (slot === "left") - leftOrder.unshift(activityId); - else if (slot === "right") - rightOrder.push(activityId); - SettingsData.set("dankIslandHomeLeftOrder", leftOrder); - SettingsData.set("dankIslandHomeRightOrder", rightOrder); - SettingsData.set(settingKey, slot); - } - - component SystemLevelActivityRow: Item { - id: activityRow - - required property string activityId - required property string settingKey - required property string text - required property string description - required property string slot - required property string displayMode - property var tags: [] - - readonly property bool compact: width - controls.implicitWidth - Theme.spacingM * 3 < 200 - readonly property bool isHighlighted: SettingsSearchService.highlightSection === settingKey - - width: parent?.width ?? 0 - height: compact ? textColumn.implicitHeight + Theme.spacingS + controls.implicitHeight + Theme.spacingM * 2 : Math.max(60, Math.max(textColumn.implicitHeight, controls.implicitHeight) + Theme.spacingM * 2) - - Component.onCompleted: { - const flickable = QmlUtils.findParentFlickable(activityRow.parent); - if (flickable) - SettingsSearchService.registerCard(settingKey, activityRow, flickable); - } - Component.onDestruction: SettingsSearchService.unregisterCard(settingKey) - - Rectangle { - anchors.fill: parent - radius: Theme.cornerRadius - color: Theme.withAlpha(Theme.primary, activityRow.isHighlighted ? 0.2 : 0) - visible: activityRow.isHighlighted - } - - Column { - id: textColumn - - x: Theme.spacingM - y: activityRow.compact ? Theme.spacingM : (activityRow.height - height) / 2 - width: activityRow.compact ? activityRow.width - Theme.spacingM * 2 : activityRow.width - controls.implicitWidth - Theme.spacingM * 3 - spacing: Theme.spacingXXS - - StyledText { - width: parent.width - text: activityRow.text - color: Theme.surfaceText - font.pixelSize: Theme.fontSizeMedium - font.weight: Font.Medium - elide: Text.ElideRight - } - - StyledText { - width: parent.width - text: activityRow.description - color: Theme.surfaceVariantText - font.pixelSize: Theme.fontSizeSmall - wrapMode: Text.WordWrap - } - } - - Column { - id: controls - - x: activityRow.compact ? (activityRow.width - width) / 2 : activityRow.width - width - Theme.spacingM - y: activityRow.compact ? textColumn.y + textColumn.implicitHeight + Theme.spacingS : (activityRow.height - height) / 2 - spacing: Theme.spacingXS - - DankButtonGroup { - model: [I18n.tr("Left", "island settings: activity left of clock"), I18n.tr("Right", "island settings: activity right of clock"), I18n.tr("Hidden", "island settings: activity hidden")] - currentIndex: root.valueIndex(root.homeSlotValues, activityRow.slot, "hidden") - selectionMode: "single" - onSelectionChanged: (index, selected) => root.setHomeActivitySlot(activityRow.activityId, activityRow.settingKey, index, selected) - } - - DankButtonGroup { - anchors.horizontalCenter: parent.horizontalCenter - visible: activityRow.slot !== "hidden" - model: [I18n.tr("Icon", "island settings: system level icon only"), "%", I18n.tr("Both", "island settings: system level icon and percentage")] - currentIndex: root.valueIndex(root.systemLevelDisplayValues, activityRow.displayMode, "both") - buttonHeight: 32 - minButtonWidth: 52 - buttonPadding: Theme.spacingS - textSize: Theme.fontSizeSmall - checkEnabled: false - selectionMode: "single" - onSelectionChanged: (index, selected) => { - if (!selected) - return; - const value = root.systemLevelDisplayValues[index] ?? "both"; - SettingsData.set(activityRow.activityId === "volume" ? "dankIslandHomeVolumeDisplay" : "dankIslandHomeBrightnessDisplay", value); - } - } - } - } - DankFlickable { anchors.fill: parent clip: true @@ -287,7 +179,10 @@ Item { description: I18n.tr("Search when idle, visualizer when media is playing", "island settings: media slot description") model: [I18n.tr("Left", "island settings: media or launcher slot left of the clock"), I18n.tr("Right", "island settings: media or launcher slot right of the clock"), I18n.tr("Hidden", "island settings: media or launcher slot hidden")] currentIndex: root.valueIndex(root.homeSlotValues, SettingsData.dankIslandHomeMediaSlot, "left") - onSelectionChanged: (index, selected) => root.setHomeActivitySlot("media", "dankIslandHomeMediaSlot", index, selected) + onSelectionChanged: (index, selected) => { + if (selected) + SettingsData.set("dankIslandHomeMediaSlot", root.homeSlotValues[index] ?? "left"); + } } SettingsButtonGroupRow { @@ -297,7 +192,10 @@ Item { 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, "hidden") - onSelectionChanged: (index, selected) => root.setHomeActivitySlot("status", "dankIslandHomeStatusSlot", index, selected) + onSelectionChanged: (index, selected) => { + if (selected) + SettingsData.set("dankIslandHomeStatusSlot", root.homeSlotValues[index] ?? "hidden"); + } } SettingsButtonGroupRow { @@ -307,27 +205,62 @@ Item { description: SettingsData.weatherEnabled ? I18n.tr("Weather icon and temperature open the weather activity", "island settings: weather slot description") : I18n.tr("Enable weather in Time & Weather to show this shortcut", "island settings: weather slot disabled hint") model: [I18n.tr("Left", "island settings: weather slot left of the clock"), I18n.tr("Right", "island settings: weather slot right of the clock"), I18n.tr("Hidden", "island settings: weather slot hidden")] currentIndex: root.valueIndex(root.homeSlotValues, SettingsData.dankIslandHomeWeatherSlot, "hidden") - onSelectionChanged: (index, selected) => root.setHomeActivitySlot("weather", "dankIslandHomeWeatherSlot", index, selected) + onSelectionChanged: (index, selected) => { + if (selected) + SettingsData.set("dankIslandHomeWeatherSlot", root.homeSlotValues[index] ?? "hidden"); + } } - SystemLevelActivityRow { - activityId: "volume" + SettingsButtonGroupRow { settingKey: "dankIslandHomeVolumeSlot" - tags: ["island", "home", "compact", "volume", "audio", "percentage"] + tags: ["island", "home", "compact", "volume", "audio"] text: I18n.tr("Volume", "island settings: volume slot row") description: I18n.tr("Current output volume on the home face", "island settings: volume slot description") - slot: SettingsData.dankIslandHomeVolumeSlot - displayMode: SettingsData.dankIslandHomeVolumeDisplay + model: [I18n.tr("Left", "island settings: volume slot left of the clock"), I18n.tr("Right", "island settings: volume slot right of the clock"), I18n.tr("Hidden", "island settings: volume slot hidden")] + currentIndex: root.valueIndex(root.homeSlotValues, SettingsData.dankIslandHomeVolumeSlot, "hidden") + onSelectionChanged: (index, selected) => { + if (selected) + SettingsData.set("dankIslandHomeVolumeSlot", root.homeSlotValues[index] ?? "hidden"); + } } - SystemLevelActivityRow { - activityId: "brightness" + SettingsButtonGroupRow { + settingKey: "dankIslandHomeVolumeDisplay" + tags: ["island", "home", "compact", "volume", "icon", "percentage"] + text: I18n.tr("Volume Style", "island settings: volume display mode row") + visible: SettingsData.dankIslandHomeVolumeSlot !== "hidden" + model: [I18n.tr("Icon", "island settings: level shown as icon only"), I18n.tr("Percentage", "island settings: level shown as percentage only"), I18n.tr("Both", "island settings: level shown as icon and percentage")] + currentIndex: root.valueIndex(root.systemLevelDisplayValues, SettingsData.dankIslandHomeVolumeDisplay, "both") + onSelectionChanged: (index, selected) => { + if (selected) + SettingsData.set("dankIslandHomeVolumeDisplay", root.systemLevelDisplayValues[index] ?? "both"); + } + } + + SettingsButtonGroupRow { settingKey: "dankIslandHomeBrightnessSlot" - tags: ["island", "home", "compact", "brightness", "display", "percentage"] + tags: ["island", "home", "compact", "brightness", "display"] text: I18n.tr("Brightness", "island settings: brightness slot row") description: I18n.tr("Current display brightness on the home face", "island settings: brightness slot description") - slot: SettingsData.dankIslandHomeBrightnessSlot - displayMode: SettingsData.dankIslandHomeBrightnessDisplay + model: [I18n.tr("Left", "island settings: brightness slot left of the clock"), I18n.tr("Right", "island settings: brightness slot right of the clock"), I18n.tr("Hidden", "island settings: brightness slot hidden")] + currentIndex: root.valueIndex(root.homeSlotValues, SettingsData.dankIslandHomeBrightnessSlot, "hidden") + onSelectionChanged: (index, selected) => { + if (selected) + SettingsData.set("dankIslandHomeBrightnessSlot", root.homeSlotValues[index] ?? "hidden"); + } + } + + SettingsButtonGroupRow { + settingKey: "dankIslandHomeBrightnessDisplay" + tags: ["island", "home", "compact", "brightness", "icon", "percentage"] + text: I18n.tr("Brightness Style", "island settings: brightness display mode row") + visible: SettingsData.dankIslandHomeBrightnessSlot !== "hidden" + model: [I18n.tr("Icon", "island settings: level shown as icon only"), I18n.tr("Percentage", "island settings: level shown as percentage only"), I18n.tr("Both", "island settings: level shown as icon and percentage")] + currentIndex: root.valueIndex(root.systemLevelDisplayValues, SettingsData.dankIslandHomeBrightnessDisplay, "both") + onSelectionChanged: (index, selected) => { + if (selected) + SettingsData.set("dankIslandHomeBrightnessDisplay", root.systemLevelDisplayValues[index] ?? "both"); + } } SettingsToggleRow { diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index be24960ac..6e75954ef 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -12381,6 +12381,52 @@ "icon": "touch_app", "description": "Click expands only on an intentional press. Hybrid peeks the current compact face on hover" }, + { + "section": "dankIslandHomeBrightnessSlot", + "label": "Brightness", + "tabIndex": 46, + "category": "Dank Island", + "keywords": [ + "activities", + "backlight", + "bright", + "brightness", + "compact", + "current", + "dank", + "dim", + "display", + "dynamic island", + "face", + "home", + "island", + "monitor", + "output", + "screen" + ], + "description": "Current display brightness on the home face" + }, + { + "section": "dankIslandHomeBrightnessDisplay", + "label": "Brightness Style", + "tabIndex": 46, + "category": "Dank Island", + "keywords": [ + "activities", + "backlight", + "bright", + "brightness", + "compact", + "dank", + "dim", + "dynamic island", + "home", + "icon", + "island", + "percentage", + "style" + ] + }, { "section": "dankIslandNotificationBadgeClearOnOpen", "label": "Clear Badge on Open", @@ -13208,6 +13254,51 @@ ], "description": "Stage the Wayland overlay layer to remain visible over fullscreen apps" }, + { + "section": "dankIslandHomeVolumeSlot", + "label": "Volume", + "tabIndex": 46, + "category": "Dank Island", + "keywords": [ + "activities", + "audio", + "compact", + "current", + "dank", + "dynamic island", + "face", + "home", + "island", + "loudness", + "output", + "sound", + "speaker", + "volume" + ], + "description": "Current output volume on the home face" + }, + { + "section": "dankIslandHomeVolumeDisplay", + "label": "Volume Style", + "tabIndex": 46, + "category": "Dank Island", + "keywords": [ + "activities", + "audio", + "compact", + "dank", + "dynamic island", + "home", + "icon", + "island", + "loudness", + "percentage", + "sound", + "speaker", + "style", + "volume" + ] + }, { "section": "dankIslandHomeWeatherSlot", "label": "Weather",