From 838d21aae8dd5996f160f387501c8b67160450cc Mon Sep 17 00:00:00 2001 From: purian23 Date: Wed, 24 Jun 2026 23:33:35 -0400 Subject: [PATCH] feat(DankDash): configurable drag-n-drop tab arrangement & hide options in dms settings --- quickshell/Common/SettingsData.qml | 91 +++ quickshell/Common/settings/SettingsSpec.js | 1 + quickshell/DMSShellIPC.qml | 8 +- .../Modals/Settings/SettingsContent.qml | 15 + .../Modals/Settings/SettingsSidebar.qml | 6 + quickshell/Modules/DankBar/DankBarWindow.qml | 2 +- .../Modules/DankDash/DankDashPopout.qml | 126 ++-- quickshell/Modules/Settings/DankDashTab.qml | 577 ++++++++++++++++++ .../translations/extract_settings_index.py | 1 + .../translations/settings_search_index.json | 11 + 10 files changed, 783 insertions(+), 55 deletions(-) create mode 100644 quickshell/Modules/Settings/DankDashTab.qml diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index e7d97a2b..fad4c53c 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -511,6 +511,97 @@ Singleton { property bool useAutoLocation: false property bool weatherEnabled: true + readonly property var _dashTabIds: ["overview", "media", "wallpaper", "weather", "settings"] + readonly property var _dashTabsDefault: [ + { + "id": "overview", + "enabled": true + }, + { + "id": "media", + "enabled": true + }, + { + "id": "wallpaper", + "enabled": true + }, + { + "id": "weather", + "enabled": true + }, + { + "id": "settings", + "enabled": true + } + ] + property var dashTabs: _dashTabsDefault + onDashTabsChanged: saveSettings() + + function getDashTabs() { + const stored = Array.isArray(dashTabs) ? dashTabs : []; + const result = []; + const seen = {}; + for (var i = 0; i < stored.length; i++) { + const id = stored[i] && stored[i].id; + if (_dashTabIds.indexOf(id) < 0 || seen[id]) + continue; + seen[id] = true; + result.push({ + "id": id, + "enabled": stored[i].enabled !== false + }); + } + for (var j = 0; j < _dashTabIds.length; j++) { + if (!seen[_dashTabIds[j]]) + result.push({ + "id": _dashTabIds[j], + "enabled": true + }); + } + return result; + } + + function visibleDashTabIds() { + return getDashTabs().filter(t => t.enabled && (t.id !== "weather" || weatherEnabled)).map(t => t.id); + } + + function dashTabIndexForId(id) { + const idx = visibleDashTabIds().indexOf(id); + return idx < 0 ? 0 : idx; + } + + function setDashTabOrder(ids) { + const current = getDashTabs(); + const ordered = []; + for (var i = 0; i < ids.length; i++) { + const existing = current.find(t => t.id === ids[i]); + if (existing) + ordered.push(existing); + } + for (var j = 0; j < current.length; j++) { + if (ids.indexOf(current[j].id) < 0) + ordered.push(current[j]); + } + dashTabs = ordered; + } + + function setDashTabEnabled(id, on) { + const current = getDashTabs(); + if (!on && id !== "settings" && current.filter(t => t.enabled && t.id !== "settings").length <= 1) + return; + dashTabs = current.map(t => t.id === id ? { + "id": t.id, + "enabled": on + } : t); + } + + function resetDashTabs() { + dashTabs = _dashTabsDefault.map(t => ({ + "id": t.id, + "enabled": t.enabled + })); + } + property string networkPreference: "auto" property string iconThemeDark: "System Default" diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index 52089444..df7e0271 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -248,6 +248,7 @@ var SPEC = { useAutoLocation: { def: false }, weatherEnabled: { def: true }, + dashTabs: { def: [{ id: "overview", enabled: true }, { id: "media", enabled: true }, { id: "wallpaper", enabled: true }, { id: "weather", enabled: true }, { id: "settings", enabled: true }] }, networkPreference: { def: "auto" }, diff --git a/quickshell/DMSShellIPC.qml b/quickshell/DMSShellIPC.qml index baeb8f55..9ac839da 100644 --- a/quickshell/DMSShellIPC.qml +++ b/quickshell/DMSShellIPC.qml @@ -269,13 +269,13 @@ Item { function resolveTabIndex(tab: string): int { switch ((tab || "").toLowerCase()) { case "media": - return 1; + return SettingsData.dashTabIndexForId("media"); case "wallpaper": - return 2; + return SettingsData.dashTabIndexForId("wallpaper"); case "weather": - return SettingsData.weatherEnabled ? 3 : 0; + return SettingsData.dashTabIndexForId("weather"); default: - return 0; + return SettingsData.dashTabIndexForId("overview"); } } diff --git a/quickshell/Modals/Settings/SettingsContent.qml b/quickshell/Modals/Settings/SettingsContent.qml index c425a0e9..2a2eb8c1 100644 --- a/quickshell/Modals/Settings/SettingsContent.qml +++ b/quickshell/Modals/Settings/SettingsContent.qml @@ -701,5 +701,20 @@ FocusScope { Qt.callLater(() => item.forceActiveFocus()); } } + + Loader { + id: dankDashLoader + anchors.fill: parent + active: root.currentIndex === 43 + visible: active + focus: active + + sourceComponent: DankDashTab {} + + onActiveChanged: { + if (active && item) + Qt.callLater(() => item.forceActiveFocus()); + } + } } } diff --git a/quickshell/Modals/Settings/SettingsSidebar.qml b/quickshell/Modals/Settings/SettingsSidebar.qml index 20ccca99..bb469ca5 100644 --- a/quickshell/Modals/Settings/SettingsSidebar.qml +++ b/quickshell/Modals/Settings/SettingsSidebar.qml @@ -155,6 +155,12 @@ Rectangle { "icon": "dashboard", "collapsedByDefault": true, "children": [ + { + "id": "dank_dash", + "text": I18n.tr("Dank Dash"), + "icon": "space_dashboard", + "tabIndex": 43 + }, { "id": "media_player", "text": I18n.tr("Media Player"), diff --git a/quickshell/Modules/DankBar/DankBarWindow.qml b/quickshell/Modules/DankBar/DankBarWindow.qml index 45c414b3..b325f052 100644 --- a/quickshell/Modules/DankBar/DankBarWindow.qml +++ b/quickshell/Modules/DankBar/DankBarWindow.qml @@ -109,7 +109,7 @@ PanelWindow { } function triggerWallpaperBrowser() { - triggerDashTab(2); + triggerDashTab(SettingsData.dashTabIndexForId("wallpaper")); } readonly property bool usesOverlayLayer: CompositorService.framePeerSurfacesUseOverlayForScreen(barWindow.screen) || (barConfig?.useOverlayLayer ?? false) diff --git a/quickshell/Modules/DankDash/DankDashPopout.qml b/quickshell/Modules/DankDash/DankDashPopout.qml index e1dc4889..4b4df9b8 100644 --- a/quickshell/Modules/DankDash/DankDashPopout.qml +++ b/quickshell/Modules/DankDash/DankDashPopout.qml @@ -12,6 +12,62 @@ DankPopout { property var triggerScreen: null property int currentTabIndex: 0 + readonly property var __tabPresentation: ({ + "overview": { + "icon": "dashboard", + "text": I18n.tr("Overview") + }, + "media": { + "icon": "music_note", + "text": I18n.tr("Media") + }, + "wallpaper": { + "icon": "wallpaper", + "text": I18n.tr("Wallpapers") + }, + "weather": { + "icon": "wb_sunny", + "text": I18n.tr("Weather") + }, + "settings": { + "icon": "settings", + "text": I18n.tr("Settings"), + "isAction": true + } + }) + readonly property var orderedTabIds: SettingsData.visibleDashTabIds() + readonly property string currentTabId: orderedTabIds.length > 0 ? (orderedTabIds[Math.min(currentTabIndex, orderedTabIds.length - 1)] ?? "overview") : "overview" + + function __isActionTab(id) { + return root.__tabPresentation[id]?.isAction === true; + } + + function __resolveContentIndex(idx) { + if (orderedTabIds.length === 0) + return 0; + var clamped = Math.max(0, Math.min(idx, orderedTabIds.length - 1)); + if (!__isActionTab(orderedTabIds[clamped])) + return clamped; + for (var f = clamped + 1; f < orderedTabIds.length; f++) + if (!__isActionTab(orderedTabIds[f])) + return f; + for (var b = clamped - 1; b >= 0; b--) + if (!__isActionTab(orderedTabIds[b])) + return b; + return clamped; + } + + onOrderedTabIdsChanged: { + var resolved = __resolveContentIndex(currentTabIndex); + if (resolved !== currentTabIndex) + currentTabIndex = resolved; + } + onCurrentTabIndexChanged: { + var resolved = __resolveContentIndex(currentTabIndex); + if (resolved !== currentTabIndex) + currentTabIndex = resolved; + } + popupWidth: SettingsData.showWeekNumber ? 736 : 700 popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500 triggerWidth: 80 @@ -191,7 +247,7 @@ DankPopout { Keys.onPressed: function (event) { if (event.key === Qt.Key_Escape) { - if (root.currentTabIndex === 2 && wallpaperLoader.item?.handleKeyEvent && wallpaperLoader.item.handleKeyEvent(event)) { + if (root.currentTabId === "wallpaper" && wallpaperLoader.item?.handleKeyEvent && wallpaperLoader.item.handleKeyEvent(event)) { event.accepted = true; return; } @@ -231,21 +287,21 @@ DankPopout { return; } - if (root.currentTabIndex === 0 && overviewLoader.item?.handleKeyEvent) { + if (root.currentTabId === "overview" && overviewLoader.item?.handleKeyEvent) { if (overviewLoader.item.handleKeyEvent(event)) { event.accepted = true; return; } } - if (root.currentTabIndex === 1 && mediaLoader.item?.handleKeyEvent) { + if (root.currentTabId === "media" && mediaLoader.item?.handleKeyEvent) { if (mediaLoader.item.handleKeyEvent(event)) { event.accepted = true; return; } } - if (root.currentTabIndex === 2 && wallpaperLoader.item?.handleKeyEvent) { + if (root.currentTabId === "wallpaper" && wallpaperLoader.item?.handleKeyEvent) { if (wallpaperLoader.item.handleKeyEvent(event)) { event.accepted = true; return; @@ -281,44 +337,14 @@ DankPopout { return item; } - model: { - let tabs = [ - { - "icon": "dashboard", - "text": I18n.tr("Overview") - }, - { - "icon": "music_note", - "text": I18n.tr("Media") - }, - { - "icon": "wallpaper", - "text": I18n.tr("Wallpapers") - } - ]; - - if (SettingsData.weatherEnabled) { - tabs.push({ - "icon": "wb_sunny", - "text": I18n.tr("Weather") - }); - } - - tabs.push({ - "icon": "settings", - "text": I18n.tr("Settings"), - "isAction": true - }); - return tabs; - } + model: root.orderedTabIds.map(id => root.__tabPresentation[id]) onTabClicked: function (index) { root.currentTabIndex = index; } onActionTriggered: function (index) { - let settingsIndex = SettingsData.weatherEnabled ? 4 : 3; - if (index === settingsIndex) { + if (root.orderedTabIds[index] === "settings") { dashVisible = false; PopoutService.focusOrToggleSettings(); } @@ -336,25 +362,25 @@ DankPopout { height: implicitHeight implicitWidth: currentItem && currentItem.implicitWidth > 0 ? currentItem.implicitWidth : (700 - Theme.spacingM * 2) implicitHeight: { - if (root.currentTabIndex === 0) + if (root.currentTabId === "overview") return overviewLoader.item?.implicitHeight ?? 410; - if (root.currentTabIndex === 1) + if (root.currentTabId === "media") return mediaLoader.item?.implicitHeight ?? 410; - if (root.currentTabIndex === 2) + if (root.currentTabId === "wallpaper") return wallpaperLoader.item?.implicitHeight ?? 410; - if (SettingsData.weatherEnabled && root.currentTabIndex === 3) + if (root.currentTabId === "weather") return weatherLoader.item?.implicitHeight ?? 410; return 410; } readonly property var currentItem: { - if (root.currentTabIndex === 0) + if (root.currentTabId === "overview") return overviewLoader.item; - if (root.currentTabIndex === 1) + if (root.currentTabId === "media") return mediaLoader.item; - if (root.currentTabIndex === 2) + if (root.currentTabId === "wallpaper") return wallpaperLoader.item; - if (root.currentTabIndex === 3) + if (root.currentTabId === "weather") return weatherLoader.item; return null; } @@ -362,7 +388,7 @@ DankPopout { Loader { id: overviewLoader anchors.fill: parent - active: root.currentTabIndex === 0 + active: root.currentTabId === "overview" visible: active sourceComponent: Component { OverviewTab { @@ -370,11 +396,11 @@ DankPopout { onNavFocusRequested: mainContainer.forceActiveFocus() onSwitchToWeatherTab: { if (SettingsData.weatherEnabled) { - root.currentTabIndex = 3; + root.currentTabIndex = SettingsData.dashTabIndexForId("weather"); } } onSwitchToMediaTab: { - root.currentTabIndex = 1; + root.currentTabIndex = SettingsData.dashTabIndexForId("media"); } } } @@ -383,7 +409,7 @@ DankPopout { Loader { id: mediaLoader anchors.fill: parent - active: root.currentTabIndex === 1 + active: root.currentTabId === "media" visible: active sourceComponent: Component { MediaPlayerTab { @@ -419,7 +445,7 @@ DankPopout { Loader { id: wallpaperLoader anchors.fill: parent - active: root.currentTabIndex === 2 + active: root.currentTabId === "wallpaper" visible: active sourceComponent: Component { WallpaperTab { @@ -435,7 +461,7 @@ DankPopout { Loader { id: weatherLoader anchors.fill: parent - active: SettingsData.weatherEnabled && root.currentTabIndex === 3 + active: root.currentTabId === "weather" visible: active sourceComponent: Component { WeatherTab {} diff --git a/quickshell/Modules/Settings/DankDashTab.qml b/quickshell/Modules/Settings/DankDashTab.qml new file mode 100644 index 00000000..3818ea1c --- /dev/null +++ b/quickshell/Modules/Settings/DankDashTab.qml @@ -0,0 +1,577 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Common +import qs.Services +import qs.Widgets + +Item { + id: root + + focus: true + property string highlightedId: "" + + readonly property var __presentation: ({ + "overview": { + "icon": "dashboard", + "text": I18n.tr("Overview"), + "description": I18n.tr("Clock, calendar, system info and profile") + }, + "media": { + "icon": "music_note", + "text": I18n.tr("Media"), + "description": I18n.tr("Now playing and media controls") + }, + "wallpaper": { + "icon": "wallpaper", + "text": I18n.tr("Wallpapers"), + "description": I18n.tr("Browse and set wallpapers") + }, + "weather": { + "icon": "wb_sunny", + "text": I18n.tr("Weather"), + "description": SettingsData.weatherEnabled ? I18n.tr("Forecast and conditions") : I18n.tr("Hidden until weather is enabled") + }, + "settings": { + "icon": "settings", + "text": I18n.tr("Settings"), + "description": I18n.tr("Shortcut that opens this settings window") + } + }) + + // Stable model: the canonical id list never reorders, so the Repeater keeps + // its delegates alive across commits (preserving focus for keyboard reorder) + readonly property var tabIds: SettingsData._dashTabIds + readonly property var tabState: SettingsData.getDashTabs() + readonly property int enabledContentCount: tabState.filter(t => t.enabled && t.id !== "settings").length + + function presentationFor(id) { + return __presentation[id] ?? { + "icon": "tab", + "text": id, + "description": "" + }; + } + function isEnabled(id) { + const t = tabState.find(t => t.id === id); + return t ? t.enabled : false; + } + + readonly property real rowHeight: 70 + readonly property real rowSpacing: Theme.spacingM + readonly property real dividerGap: 40 + + property var enabledOrder: [] + property var disabledOrder: [] + property string draggingId: "" + property var dragStartOrder: [] + + readonly property bool hasHidden: disabledOrder.length > 0 + readonly property real dividerY: enabledOrder.length * (rowHeight + rowSpacing) + readonly property real totalHeight: { + const base = enabledOrder.length * (rowHeight + rowSpacing); + if (!hasHidden) + return Math.max(0, base - rowSpacing); + return base + dividerGap + disabledOrder.length * (rowHeight + rowSpacing) - rowSpacing; + } + + function rebuild() { + const en = []; + const dis = []; + for (var i = 0; i < tabState.length; i++) { + if (tabState[i].enabled) + en.push(tabState[i].id); + else + dis.push(tabState[i].id); + } + enabledOrder = en; + disabledOrder = dis; + } + + onTabStateChanged: rebuild() + Component.onCompleted: rebuild() + + function slotYForId(id) { + const p = enabledOrder.indexOf(id); + if (p >= 0) + return p * (rowHeight + rowSpacing); + const k = disabledOrder.indexOf(id); + return dividerY + dividerGap + Math.max(0, k) * (rowHeight + rowSpacing); + } + + function beginDrag(id) { + draggingId = id; + dragStartOrder = enabledOrder.slice(); + } + + function updateDragTarget(centerY) { + if (draggingId === "") + return; + var pos = Math.floor(centerY / (rowHeight + rowSpacing)); + pos = Math.max(0, Math.min(pos, enabledOrder.length - 1)); + const arr = enabledOrder.slice(); + const d = arr.indexOf(draggingId); + if (d < 0 || d === pos) + return; + arr.splice(d, 1); + arr.splice(pos, 0, draggingId); + enabledOrder = arr; + } + + function commit() { + SettingsData.setDashTabOrder(enabledOrder.concat(disabledOrder)); + } + + function endDrag() { + if (draggingId === "") + return; + const changed = JSON.stringify(enabledOrder) !== JSON.stringify(dragStartOrder); + draggingId = ""; + if (changed) + commit(); + } + + function moveEnabled(id, delta) { + const pos = enabledOrder.indexOf(id); + const next = pos + delta; + if (pos < 0 || next < 0 || next >= enabledOrder.length) + return; + const arr = enabledOrder.slice(); + arr.splice(pos, 1); + arr.splice(next, 0, id); + enabledOrder = arr; + commit(); + } + + function canHide(id) { + return !isEnabled(id) || id === "settings" || enabledContentCount > 1; + } + + // Keyboard nav is handled at the tab root (not per-row activeFocusOnTab) + Keys.onPressed: function (event) { + const order = enabledOrder.concat(disabledOrder); + if (order.length === 0) + return; + const ctrl = (event.modifiers & Qt.ControlModifier) !== 0; + if (event.key === Qt.Key_Up || event.key === Qt.Key_Down) { + const dir = event.key === Qt.Key_Down ? 1 : -1; + if (ctrl) { + if (highlightedId !== "" && isEnabled(highlightedId)) + moveEnabled(highlightedId, dir); + } else if (highlightedId === "") { + highlightedId = dir > 0 ? order[0] : order[order.length - 1]; + } else { + var idx = order.indexOf(highlightedId); + idx = Math.max(0, Math.min(order.length - 1, idx + dir)); + highlightedId = order[idx]; + } + event.accepted = true; + } else if ((event.key === Qt.Key_Space || event.key === Qt.Key_Return) && highlightedId !== "") { + if (canHide(highlightedId)) + SettingsData.setDashTabEnabled(highlightedId, !isEnabled(highlightedId)); + event.accepted = true; + } + } + + DankFlickable { + anchors.fill: parent + clip: true + contentHeight: mainColumn.height + Theme.spacingXL + contentWidth: width + + Column { + id: mainColumn + topPadding: 4 + width: Math.min(550, parent.width - Theme.spacingL * 2) + anchors.horizontalCenter: parent.horizontalCenter + spacing: Theme.spacingXL + + StyledRect { + width: parent.width + height: headerContent.implicitHeight + Theme.spacingL * 2 + radius: Theme.cornerRadius + color: Theme.surfaceContainerHigh + border.width: 0 + + Column { + id: headerContent + anchors.fill: parent + anchors.margins: Theme.spacingL + spacing: Theme.spacingM + + RowLayout { + id: headerText + width: parent.width + spacing: Theme.spacingM + + DankIcon { + name: "space_dashboard" + size: Theme.iconSize + color: Theme.primary + Layout.alignment: Qt.AlignVCenter + } + + StyledText { + text: I18n.tr("Dank Dash") + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Medium + color: Theme.surfaceText + Layout.alignment: Qt.AlignVCenter + } + + Item { + height: 1 + Layout.fillWidth: true + } + + Rectangle { + id: resetButton + width: resetContentRow.implicitWidth + Theme.spacingM * 2 + height: 28 + radius: Theme.cornerRadius + color: resetArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariant + border.width: 0 + Layout.alignment: Qt.AlignVCenter + + Row { + id: resetContentRow + anchors.centerIn: parent + spacing: Theme.spacingXS + + DankIcon { + name: "refresh" + size: 14 + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Reset") + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } + + MouseArea { + id: resetArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: SettingsData.resetDashTabs() + } + + Behavior on color { + ColorAnimation { + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + } + } + } + + StyledText { + text: I18n.tr("Drag to reorder or click to hide tabs. Use ↑/↓ to highlight a tab and Ctrl+↑/↓ to move it.") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + width: parent.width + wrapMode: Text.WordWrap + } + } + } + + StyledRect { + width: parent.width + height: root.totalHeight + Theme.spacingL * 2 + radius: Theme.cornerRadius + color: Theme.surfaceContainerHigh + border.width: 0 + + Item { + id: reorderArea + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Theme.spacingL + height: root.totalHeight + + Behavior on height { + NumberAnimation { + duration: Theme.expressiveDurations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial + } + } + + Item { + id: hiddenDivider + width: parent.width + height: root.dividerGap + y: root.dividerY + (root.rowSpacing / 2) + opacity: root.hasHidden ? 1 : 0 + visible: opacity > 0.01 + + Behavior on y { + NumberAnimation { + duration: Theme.expressiveDurations.normal + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial + } + } + Behavior on opacity { + NumberAnimation { + duration: Theme.shortDuration + } + } + + Row { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + spacing: Theme.spacingM + + DankIcon { + name: "visibility_off" + size: 14 + color: Theme.outline + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Hidden") + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + color: Theme.outline + anchors.verticalCenter: parent.verticalCenter + } + + Rectangle { + width: parent.width - x + height: 1 + color: Theme.outline + opacity: 0.2 + anchors.verticalCenter: parent.verticalCenter + } + } + } + + Repeater { + model: root.tabIds + + delegate: Item { + id: rowItem + required property int index + required property string modelData + + readonly property var present: root.presentationFor(modelData) + readonly property bool isEnabled: root.isEnabled(modelData) + readonly property bool dragging: root.draggingId === modelData + readonly property bool highlighted: root.highlightedId === modelData + readonly property bool canHide: root.canHide(modelData) + + width: reorderArea.width + height: root.rowHeight + z: dragging ? 100 : (highlighted ? 3 : 1) + + Binding { + target: rowItem + property: "y" + value: root.slotYForId(rowItem.modelData) + when: !rowItem.dragging + restoreMode: Binding.RestoreNone + } + + onYChanged: { + if (dragging) + root.updateDragTarget(y + height / 2); + } + + Behavior on y { + enabled: !rowItem.dragging + NumberAnimation { + duration: Theme.expressiveDurations.expressiveDefaultSpatial + easing.type: Easing.BezierSpline + easing.bezierCurve: Theme.expressiveCurves.expressiveFastSpatial + } + } + + Item { + id: content + anchors.fill: parent + scale: rowItem.dragging ? 1.02 : 1.0 + transformOrigin: Item.Center + + Behavior on scale { + NumberAnimation { + duration: Theme.shortDuration + easing.type: Easing.OutCubic + } + } + + Rectangle { + id: surface + anchors.fill: parent + radius: rowItem.dragging ? Theme.cornerRadius + 6 : Theme.cornerRadius + color: { + if (rowItem.dragging) + return Theme.secondaryContainer; + const base = Theme.surfaceContainer; + return Qt.rgba(base.r, base.g, base.b, rowItem.isEnabled ? 0.7 : 0.4); + } + border.width: rowItem.dragging ? 2 : 1 + border.color: rowItem.dragging ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) + + Behavior on radius { + NumberAnimation { + duration: Theme.shortDuration + easing.type: Easing.OutCubic + } + } + Behavior on color { + ColorAnimation { + duration: Theme.shortDuration + } + } + Behavior on border.color { + ColorAnimation { + duration: Theme.shortDuration + } + } + + Rectangle { + anchors.fill: parent + radius: parent.radius + color: Theme.primary + opacity: (dragArea.containsMouse && !rowItem.dragging) ? 0.06 : 0 + Behavior on opacity { + NumberAnimation { + duration: Theme.shortDuration + } + } + } + + DankIcon { + id: dragHandle + name: "drag_indicator" + size: Theme.iconSize - 4 + color: rowItem.dragging ? Theme.primary : Theme.outline + anchors.left: parent.left + anchors.leftMargin: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + opacity: rowItem.isEnabled ? ((dragArea.containsMouse || rowItem.dragging || rowItem.highlighted) ? 1.0 : 0.45) : 0 + visible: opacity > 0.01 + + Behavior on opacity { + NumberAnimation { + duration: Theme.shortDuration + } + } + } + + DankIcon { + id: tabIcon + name: rowItem.present.icon + size: Theme.iconSize + color: rowItem.isEnabled ? Theme.primary : Theme.outline + anchors.left: parent.left + anchors.leftMargin: Theme.spacingM * 2 + Theme.iconSize - 4 + anchors.verticalCenter: parent.verticalCenter + + Behavior on color { + ColorAnimation { + duration: Theme.shortDuration + } + } + } + + Column { + anchors.left: tabIcon.right + anchors.leftMargin: Theme.spacingM + anchors.right: visibilityButton.left + anchors.rightMargin: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + spacing: 2 + + StyledText { + text: rowItem.present.text + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: rowItem.isEnabled ? Theme.surfaceText : Theme.outline + elide: Text.ElideRight + width: parent.width + } + + StyledText { + text: rowItem.present.description + font.pixelSize: Theme.fontSizeSmall + color: rowItem.isEnabled ? Theme.outline : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6) + elide: Text.ElideRight + width: parent.width + visible: text.length > 0 + } + } + + DankActionButton { + id: visibilityButton + anchors.right: parent.right + anchors.rightMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + buttonSize: 36 + iconName: rowItem.isEnabled ? "visibility" : "visibility_off" + iconSize: 18 + iconColor: rowItem.isEnabled ? Theme.primary : Theme.outline + enabled: rowItem.canHide + onClicked: { + root.forceActiveFocus(); + root.highlightedId = rowItem.modelData; + SettingsData.setDashTabEnabled(rowItem.modelData, !rowItem.isEnabled); + } + } + } + } + + Rectangle { + anchors.fill: parent + anchors.margins: -2 + radius: Theme.cornerRadius + 2 + color: "transparent" + border.width: 2 + border.color: Theme.primary + opacity: rowItem.highlighted && !rowItem.dragging ? 0.6 : 0 + visible: opacity > 0.01 + + Behavior on opacity { + NumberAnimation { + duration: Theme.shortDuration + } + } + } + + MouseArea { + id: dragArea + anchors.fill: parent + anchors.rightMargin: 48 + hoverEnabled: true + enabled: rowItem.isEnabled + cursorShape: rowItem.dragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor + drag.target: rowItem + drag.axis: Drag.YAxis + drag.minimumY: -rowItem.height + drag.maximumY: reorderArea.height + drag.smoothed: false + onPressed: { + root.forceActiveFocus(); + root.highlightedId = rowItem.modelData; + root.beginDrag(rowItem.modelData); + } + onReleased: root.endDrag() + } + } + } + } + } + } +} +} diff --git a/quickshell/translations/extract_settings_index.py b/quickshell/translations/extract_settings_index.py index cfe4110f..35c5bc7c 100755 --- a/quickshell/translations/extract_settings_index.py +++ b/quickshell/translations/extract_settings_index.py @@ -178,6 +178,7 @@ TAB_CATEGORY_MAP = { 39: "Network", 40: "Network", 41: "Network", + 43: "Dank Dash", } SEARCHABLE_COMPONENTS = [ diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 32d51e19..6e06b0e5 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -9124,5 +9124,16 @@ "settings" ], "icon": "battery_charging_full" + }, + { + "section": "_tab_43", + "label": "Dank Dash", + "tabIndex": 43, + "category": "Dank Dash", + "keywords": [ + "dank", + "dash" + ], + "icon": "space_dashboard" } ]