mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 03:28:28 -04:00
7535b70fa6
port 1.5
481 lines
18 KiB
QML
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 {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|