mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-25 04:25:22 -04:00
feat(DankDash): configurable drag-n-drop tab arrangement & hide options in dms settings
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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" },
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -109,7 +109,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
function triggerWallpaperBrowser() {
|
||||
triggerDashTab(2);
|
||||
triggerDashTab(SettingsData.dashTabIndexForId("wallpaper"));
|
||||
}
|
||||
|
||||
readonly property bool usesOverlayLayer: CompositorService.framePeerSurfacesUseOverlayForScreen(barWindow.screen) || (barConfig?.useOverlayLayer ?? false)
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,6 +178,7 @@ TAB_CATEGORY_MAP = {
|
||||
39: "Network",
|
||||
40: "Network",
|
||||
41: "Network",
|
||||
43: "Dank Dash",
|
||||
}
|
||||
|
||||
SEARCHABLE_COMPONENTS = [
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user