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 useAutoLocation: false
|
||||||
property bool weatherEnabled: true
|
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 networkPreference: "auto"
|
||||||
|
|
||||||
property string iconThemeDark: "System Default"
|
property string iconThemeDark: "System Default"
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ var SPEC = {
|
|||||||
|
|
||||||
useAutoLocation: { def: false },
|
useAutoLocation: { def: false },
|
||||||
weatherEnabled: { def: true },
|
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" },
|
networkPreference: { def: "auto" },
|
||||||
|
|
||||||
|
|||||||
@@ -269,13 +269,13 @@ Item {
|
|||||||
function resolveTabIndex(tab: string): int {
|
function resolveTabIndex(tab: string): int {
|
||||||
switch ((tab || "").toLowerCase()) {
|
switch ((tab || "").toLowerCase()) {
|
||||||
case "media":
|
case "media":
|
||||||
return 1;
|
return SettingsData.dashTabIndexForId("media");
|
||||||
case "wallpaper":
|
case "wallpaper":
|
||||||
return 2;
|
return SettingsData.dashTabIndexForId("wallpaper");
|
||||||
case "weather":
|
case "weather":
|
||||||
return SettingsData.weatherEnabled ? 3 : 0;
|
return SettingsData.dashTabIndexForId("weather");
|
||||||
default:
|
default:
|
||||||
return 0;
|
return SettingsData.dashTabIndexForId("overview");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -701,5 +701,20 @@ FocusScope {
|
|||||||
Qt.callLater(() => item.forceActiveFocus());
|
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",
|
"icon": "dashboard",
|
||||||
"collapsedByDefault": true,
|
"collapsedByDefault": true,
|
||||||
"children": [
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "dank_dash",
|
||||||
|
"text": I18n.tr("Dank Dash"),
|
||||||
|
"icon": "space_dashboard",
|
||||||
|
"tabIndex": 43
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "media_player",
|
"id": "media_player",
|
||||||
"text": I18n.tr("Media Player"),
|
"text": I18n.tr("Media Player"),
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function triggerWallpaperBrowser() {
|
function triggerWallpaperBrowser() {
|
||||||
triggerDashTab(2);
|
triggerDashTab(SettingsData.dashTabIndexForId("wallpaper"));
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool usesOverlayLayer: CompositorService.framePeerSurfacesUseOverlayForScreen(barWindow.screen) || (barConfig?.useOverlayLayer ?? false)
|
readonly property bool usesOverlayLayer: CompositorService.framePeerSurfacesUseOverlayForScreen(barWindow.screen) || (barConfig?.useOverlayLayer ?? false)
|
||||||
|
|||||||
@@ -12,6 +12,62 @@ DankPopout {
|
|||||||
property var triggerScreen: null
|
property var triggerScreen: null
|
||||||
property int currentTabIndex: 0
|
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
|
popupWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||||
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
|
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
|
||||||
triggerWidth: 80
|
triggerWidth: 80
|
||||||
@@ -191,7 +247,7 @@ DankPopout {
|
|||||||
|
|
||||||
Keys.onPressed: function (event) {
|
Keys.onPressed: function (event) {
|
||||||
if (event.key === Qt.Key_Escape) {
|
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;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -231,21 +287,21 @@ DankPopout {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.currentTabIndex === 0 && overviewLoader.item?.handleKeyEvent) {
|
if (root.currentTabId === "overview" && overviewLoader.item?.handleKeyEvent) {
|
||||||
if (overviewLoader.item.handleKeyEvent(event)) {
|
if (overviewLoader.item.handleKeyEvent(event)) {
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.currentTabIndex === 1 && mediaLoader.item?.handleKeyEvent) {
|
if (root.currentTabId === "media" && mediaLoader.item?.handleKeyEvent) {
|
||||||
if (mediaLoader.item.handleKeyEvent(event)) {
|
if (mediaLoader.item.handleKeyEvent(event)) {
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.currentTabIndex === 2 && wallpaperLoader.item?.handleKeyEvent) {
|
if (root.currentTabId === "wallpaper" && wallpaperLoader.item?.handleKeyEvent) {
|
||||||
if (wallpaperLoader.item.handleKeyEvent(event)) {
|
if (wallpaperLoader.item.handleKeyEvent(event)) {
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
@@ -281,44 +337,14 @@ DankPopout {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
model: {
|
model: root.orderedTabIds.map(id => root.__tabPresentation[id])
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
onTabClicked: function (index) {
|
onTabClicked: function (index) {
|
||||||
root.currentTabIndex = index;
|
root.currentTabIndex = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
onActionTriggered: function (index) {
|
onActionTriggered: function (index) {
|
||||||
let settingsIndex = SettingsData.weatherEnabled ? 4 : 3;
|
if (root.orderedTabIds[index] === "settings") {
|
||||||
if (index === settingsIndex) {
|
|
||||||
dashVisible = false;
|
dashVisible = false;
|
||||||
PopoutService.focusOrToggleSettings();
|
PopoutService.focusOrToggleSettings();
|
||||||
}
|
}
|
||||||
@@ -336,25 +362,25 @@ DankPopout {
|
|||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
implicitWidth: currentItem && currentItem.implicitWidth > 0 ? currentItem.implicitWidth : (700 - Theme.spacingM * 2)
|
implicitWidth: currentItem && currentItem.implicitWidth > 0 ? currentItem.implicitWidth : (700 - Theme.spacingM * 2)
|
||||||
implicitHeight: {
|
implicitHeight: {
|
||||||
if (root.currentTabIndex === 0)
|
if (root.currentTabId === "overview")
|
||||||
return overviewLoader.item?.implicitHeight ?? 410;
|
return overviewLoader.item?.implicitHeight ?? 410;
|
||||||
if (root.currentTabIndex === 1)
|
if (root.currentTabId === "media")
|
||||||
return mediaLoader.item?.implicitHeight ?? 410;
|
return mediaLoader.item?.implicitHeight ?? 410;
|
||||||
if (root.currentTabIndex === 2)
|
if (root.currentTabId === "wallpaper")
|
||||||
return wallpaperLoader.item?.implicitHeight ?? 410;
|
return wallpaperLoader.item?.implicitHeight ?? 410;
|
||||||
if (SettingsData.weatherEnabled && root.currentTabIndex === 3)
|
if (root.currentTabId === "weather")
|
||||||
return weatherLoader.item?.implicitHeight ?? 410;
|
return weatherLoader.item?.implicitHeight ?? 410;
|
||||||
return 410;
|
return 410;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property var currentItem: {
|
readonly property var currentItem: {
|
||||||
if (root.currentTabIndex === 0)
|
if (root.currentTabId === "overview")
|
||||||
return overviewLoader.item;
|
return overviewLoader.item;
|
||||||
if (root.currentTabIndex === 1)
|
if (root.currentTabId === "media")
|
||||||
return mediaLoader.item;
|
return mediaLoader.item;
|
||||||
if (root.currentTabIndex === 2)
|
if (root.currentTabId === "wallpaper")
|
||||||
return wallpaperLoader.item;
|
return wallpaperLoader.item;
|
||||||
if (root.currentTabIndex === 3)
|
if (root.currentTabId === "weather")
|
||||||
return weatherLoader.item;
|
return weatherLoader.item;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -362,7 +388,7 @@ DankPopout {
|
|||||||
Loader {
|
Loader {
|
||||||
id: overviewLoader
|
id: overviewLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.currentTabIndex === 0
|
active: root.currentTabId === "overview"
|
||||||
visible: active
|
visible: active
|
||||||
sourceComponent: Component {
|
sourceComponent: Component {
|
||||||
OverviewTab {
|
OverviewTab {
|
||||||
@@ -370,11 +396,11 @@ DankPopout {
|
|||||||
onNavFocusRequested: mainContainer.forceActiveFocus()
|
onNavFocusRequested: mainContainer.forceActiveFocus()
|
||||||
onSwitchToWeatherTab: {
|
onSwitchToWeatherTab: {
|
||||||
if (SettingsData.weatherEnabled) {
|
if (SettingsData.weatherEnabled) {
|
||||||
root.currentTabIndex = 3;
|
root.currentTabIndex = SettingsData.dashTabIndexForId("weather");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onSwitchToMediaTab: {
|
onSwitchToMediaTab: {
|
||||||
root.currentTabIndex = 1;
|
root.currentTabIndex = SettingsData.dashTabIndexForId("media");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -383,7 +409,7 @@ DankPopout {
|
|||||||
Loader {
|
Loader {
|
||||||
id: mediaLoader
|
id: mediaLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.currentTabIndex === 1
|
active: root.currentTabId === "media"
|
||||||
visible: active
|
visible: active
|
||||||
sourceComponent: Component {
|
sourceComponent: Component {
|
||||||
MediaPlayerTab {
|
MediaPlayerTab {
|
||||||
@@ -419,7 +445,7 @@ DankPopout {
|
|||||||
Loader {
|
Loader {
|
||||||
id: wallpaperLoader
|
id: wallpaperLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.currentTabIndex === 2
|
active: root.currentTabId === "wallpaper"
|
||||||
visible: active
|
visible: active
|
||||||
sourceComponent: Component {
|
sourceComponent: Component {
|
||||||
WallpaperTab {
|
WallpaperTab {
|
||||||
@@ -435,7 +461,7 @@ DankPopout {
|
|||||||
Loader {
|
Loader {
|
||||||
id: weatherLoader
|
id: weatherLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: SettingsData.weatherEnabled && root.currentTabIndex === 3
|
active: root.currentTabId === "weather"
|
||||||
visible: active
|
visible: active
|
||||||
sourceComponent: Component {
|
sourceComponent: Component {
|
||||||
WeatherTab {}
|
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",
|
39: "Network",
|
||||||
40: "Network",
|
40: "Network",
|
||||||
41: "Network",
|
41: "Network",
|
||||||
|
43: "Dank Dash",
|
||||||
}
|
}
|
||||||
|
|
||||||
SEARCHABLE_COMPONENTS = [
|
SEARCHABLE_COMPONENTS = [
|
||||||
|
|||||||
@@ -9124,5 +9124,16 @@
|
|||||||
"settings"
|
"settings"
|
||||||
],
|
],
|
||||||
"icon": "battery_charging_full"
|
"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