1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-01 19:18:28 -04:00
Files
DankMaterialShell/quickshell/Modules/DankDash/DankDashPopout.qml
T
bbedward 7535b70fa6 dash: allow hiding all tabs
port 1.5
2026-07-13 16:11:15 -04:00

481 lines
18 KiB
QML

import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
DankPopout {
id: root
layerNamespace: "dms:dash"
property bool dashVisible: false
property var triggerScreen: null
property string currentTabId: "overview"
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()
// -1 when the current view's tab is hidden: the view still shows, no tab is highlighted.
readonly property int currentTabIndex: orderedTabIds.indexOf(currentTabId)
function __isActionTab(id) {
return root.__tabPresentation[id]?.isAction === true;
}
// Show a view regardless of tab visibility; bar widgets and IPC land here.
function requestTab(id) {
const valid = __tabPresentation[id] !== undefined && !__isActionTab(id) && (id !== "weather" || SettingsData.weatherEnabled);
currentTabId = valid ? id : "overview";
}
function __cycleTab(dir) {
const ids = orderedTabIds.filter(id => !__isActionTab(id));
if (ids.length === 0)
return;
const pos = ids.indexOf(currentTabId);
const next = pos < 0 ? (dir > 0 ? 0 : ids.length - 1) : (pos + dir + ids.length) % ids.length;
currentTabId = ids[next];
}
Connections {
target: SettingsData
function onWeatherEnabledChanged() {
if (!SettingsData.weatherEnabled && root.currentTabId === "weather")
root.currentTabId = "overview";
}
}
popupWidth: SettingsData.showWeekNumber ? 736 : 700
popupHeight: contentLoader.item ? contentLoader.item.implicitHeight : 500
triggerWidth: 80
screen: triggerScreen
property bool __focusArmed: false
property bool __contentReady: false
property var __mediaTabRef: null
property int __dropdownType: 0
property point __dropdownAnchor: Qt.point(0, 0)
property bool __dropdownRightEdge: false
property var __dropdownPlayer: MprisController.activePlayer
property var __dropdownPlayers: MprisController.availablePlayers
function __showVolumeDropdown(pos, rightEdge, player, players) {
__dropdownAnchor = pos;
__dropdownRightEdge = rightEdge;
__dropdownPlayer = Qt.binding(() => MprisController.activePlayer);
__dropdownPlayers = Qt.binding(() => MprisController.availablePlayers);
__dropdownType = 1;
}
function __showAudioDevicesDropdown(pos, rightEdge) {
__dropdownAnchor = pos;
__dropdownRightEdge = rightEdge;
__dropdownType = 2;
}
function __showPlayersDropdown(pos, rightEdge, player, players) {
__dropdownAnchor = pos;
__dropdownRightEdge = rightEdge;
__dropdownPlayer = Qt.binding(() => MprisController.activePlayer);
__dropdownPlayers = Qt.binding(() => MprisController.availablePlayers);
__dropdownType = 3;
}
function __hideDropdowns() {
__volumeCloseTimer.stop();
__dropdownType = 0;
if (__mediaTabRef && typeof __mediaTabRef.resetDropdownStates === "function")
__mediaTabRef.resetDropdownStates();
}
function __startCloseTimer() {
__volumeCloseTimer.restart();
}
function __stopCloseTimer() {
__volumeCloseTimer.stop();
}
Timer {
id: __volumeCloseTimer
interval: 400
onTriggered: {
if (__dropdownType !== 0) {
__hideDropdowns();
}
}
}
overlayContent: shouldBeVisible ? mediaDropdownOverlayComponent : null
Component {
id: mediaDropdownOverlayComponent
MediaDropdownOverlay {
dropdownType: root.__dropdownType
anchorPos: root.__dropdownAnchor
isRightEdge: root.__dropdownRightEdge
activePlayer: root.__dropdownPlayer
allPlayers: root.__dropdownPlayers
targetWindow: root.backgroundWindow
onCloseRequested: root.__hideDropdowns()
onPanelEntered: root.__stopCloseTimer()
onPanelExited: root.__startCloseTimer()
onVolumeChanged: volume => {
const player = root.__dropdownPlayer;
const isChrome = player?.identity?.toLowerCase().includes("chrome") || player?.identity?.toLowerCase().includes("chromium");
const usePlayerVolume = player && player.volumeSupported && !isChrome;
if (usePlayerVolume) {
player.volume = volume;
} else if (AudioService.sink?.audio) {
AudioService.sink.audio.volume = volume;
}
}
onPlayerSelected: player => {
const currentPlayer = MprisController.activePlayer;
if (currentPlayer && currentPlayer !== player && currentPlayer.canPause) {
currentPlayer.pause();
}
MprisController.setActivePlayer(player);
root.__hideDropdowns();
}
}
}
function __tryFocusOnce() {
if (!__focusArmed)
return;
const win = root.window;
if (!win || !win.visible)
return;
if (!contentLoader.item)
return;
if (win.requestActivate)
win.requestActivate();
contentLoader.item.forceActiveFocus(Qt.TabFocusReason);
if (contentLoader.item.activeFocus)
__focusArmed = false;
}
onDashVisibleChanged: {
if (dashVisible) {
__focusArmed = true;
__contentReady = !!contentLoader.item;
open();
__tryFocusOnce();
} else {
__focusArmed = false;
__contentReady = false;
__hideDropdowns();
close();
}
}
Connections {
target: contentLoader
function onLoaded() {
__contentReady = true;
if (__focusArmed)
__tryFocusOnce();
}
}
Connections {
target: root.window ? root.window : null
enabled: !!root.window
function onVisibleChanged() {
if (__focusArmed)
__tryFocusOnce();
}
}
onBackgroundClicked: {
dashVisible = false;
}
content: Component {
Rectangle {
id: mainContainer
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
MouseArea {
anchors.fill: parent
z: -1
enabled: root.__dropdownType !== 0
onClicked: root.__hideDropdowns()
}
implicitWidth: Math.max(700, pages.implicitWidth + (Theme.spacingM * 2))
implicitHeight: contentColumn.height + Theme.spacingM * 2
color: "transparent"
focus: true
Component.onCompleted: {
if (root.shouldBeVisible) {
mainContainer.forceActiveFocus();
}
}
Connections {
target: root
function onShouldBeVisibleChanged() {
if (!root.shouldBeVisible)
return;
mainContainer.forceActiveFocus();
tabBar.snapIndicator();
}
}
Keys.onPressed: function (event) {
if (event.key === Qt.Key_Escape) {
if (root.currentTabId === "wallpaper" && wallpaperLoader.item?.handleKeyEvent && wallpaperLoader.item.handleKeyEvent(event)) {
event.accepted = true;
return;
}
root.dashVisible = false;
event.accepted = true;
return;
}
if (event.key === Qt.Key_Tab && !(event.modifiers & Qt.ShiftModifier)) {
root.__cycleTab(1);
event.accepted = true;
return;
}
if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) {
root.__cycleTab(-1);
event.accepted = true;
return;
}
if (root.currentTabId === "overview" && overviewLoader.item?.handleKeyEvent) {
if (overviewLoader.item.handleKeyEvent(event)) {
event.accepted = true;
return;
}
}
if (root.currentTabId === "media" && mediaLoader.item?.handleKeyEvent) {
if (mediaLoader.item.handleKeyEvent(event)) {
event.accepted = true;
return;
}
}
if (root.currentTabId === "wallpaper" && wallpaperLoader.item?.handleKeyEvent) {
if (wallpaperLoader.item.handleKeyEvent(event)) {
event.accepted = true;
return;
}
}
}
Column {
id: contentColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
DankTabBar {
id: tabBar
// Effective visibility is false while the popout window is unmapped, so gating
// height on `visible` collapses the bar between opens and resizes the surface
// mid-animation. Gate on data only. The bar also hides entirely when the
// current view's tab isn't in the visible set (e.g. IPC-opened hidden tab).
readonly property bool showTabs: (model?.length ?? 0) > 0 && root.currentTabIndex >= 0
width: parent.width
height: showTabs ? 48 : 0
visible: showTabs
currentIndex: root.currentTabIndex
spacing: Theme.spacingS
equalWidthTabs: true
enableArrowNavigation: false
focus: false
activeFocusOnTab: false
nextFocusTarget: {
const item = pages.currentItem;
if (!item)
return null;
if (item.focusTarget)
return item.focusTarget;
return item;
}
model: root.orderedTabIds.map(id => root.__tabPresentation[id])
onTabClicked: function (index) {
const id = root.orderedTabIds[index];
if (id !== undefined)
root.currentTabId = id;
}
onActionTriggered: function (index) {
if (root.orderedTabIds[index] === "settings") {
dashVisible = false;
PopoutService.focusOrToggleSettings();
}
}
}
Item {
width: parent.width
height: tabBar.showTabs ? Theme.spacingXS : 0
visible: tabBar.showTabs
}
Item {
id: pages
width: parent.width
height: implicitHeight
implicitWidth: currentItem && currentItem.implicitWidth > 0 ? currentItem.implicitWidth : (700 - Theme.spacingM * 2)
implicitHeight: {
if (root.currentTabId === "overview")
return overviewLoader.item?.implicitHeight ?? 410;
if (root.currentTabId === "media")
return mediaLoader.item?.implicitHeight ?? 410;
if (root.currentTabId === "wallpaper")
return wallpaperLoader.item?.implicitHeight ?? 410;
if (root.currentTabId === "weather")
return weatherLoader.item?.implicitHeight ?? 410;
return 410;
}
readonly property var currentItem: {
if (root.currentTabId === "overview")
return overviewLoader.item;
if (root.currentTabId === "media")
return mediaLoader.item;
if (root.currentTabId === "wallpaper")
return wallpaperLoader.item;
if (root.currentTabId === "weather")
return weatherLoader.item;
return null;
}
Loader {
id: overviewLoader
anchors.fill: parent
active: root.currentTabId === "overview"
visible: active
sourceComponent: Component {
OverviewTab {
onCloseDash: root.dashVisible = false
onNavFocusRequested: mainContainer.forceActiveFocus()
onSwitchToWeatherTab: {
if (SettingsData.weatherEnabled) {
root.requestTab("weather");
}
}
onSwitchToMediaTab: {
root.requestTab("media");
}
}
}
}
Loader {
id: mediaLoader
anchors.fill: parent
active: root.currentTabId === "media"
visible: active
asynchronous: true
sourceComponent: Component {
MediaPlayerTab {
targetScreen: root.screen
popoutX: root.alignedX
popoutY: root.alignedY
popoutWidth: root.alignedWidth
popoutHeight: root.alignedHeight
contentOffsetY: Theme.spacingM + (tabBar.showTabs ? 48 + Theme.spacingS + Theme.spacingXS : 0)
section: root.triggerSection
barPosition: root.effectiveBarPosition
Component.onCompleted: root.__mediaTabRef = this
Component.onDestruction: {
if (root.__mediaTabRef === this)
root.__mediaTabRef = null;
}
onShowVolumeDropdown: (pos, screen, rightEdge, player, players) => {
root.__showVolumeDropdown(pos, rightEdge, player, players);
}
onShowAudioDevicesDropdown: (pos, screen, rightEdge) => {
root.__showAudioDevicesDropdown(pos, rightEdge);
}
onShowPlayersDropdown: (pos, screen, rightEdge, player, players) => {
root.__showPlayersDropdown(pos, rightEdge, player, players);
}
onHideDropdowns: root.__hideDropdowns()
onDropdownButtonExited: root.__startCloseTimer()
onDropdownButtonEntered: root.__stopCloseTimer()
}
}
}
Loader {
id: wallpaperLoader
anchors.fill: parent
active: root.currentTabId === "wallpaper"
visible: active
asynchronous: true
sourceComponent: Component {
WallpaperTab {
active: true
tabBarItem: tabBar
keyForwardTarget: mainContainer
targetScreen: root.screen
parentPopout: root
}
}
}
DankSpinner {
anchors.centerIn: parent
size: 40
visible: (wallpaperLoader.active && wallpaperLoader.status === Loader.Loading) || (mediaLoader.active && mediaLoader.status === Loader.Loading) || (weatherLoader.active && weatherLoader.status === Loader.Loading)
}
Loader {
id: weatherLoader
anchors.fill: parent
active: root.currentTabId === "weather"
visible: active
asynchronous: true
sourceComponent: Component {
WeatherTab {}
}
}
}
}
}
}
}