mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-30 23:17:51 -04:00
island: re-use media controls from dankdash
This commit is contained in:
1 parent
a781b7b236
commit
de24b1515a
18 files changed
+187
-1157
No files matched your search
@@ -144,24 +144,6 @@ DankPopout {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,12 +60,26 @@ Item {
|
||||
}
|
||||
|
||||
signal closeRequested
|
||||
signal deviceSelected(var device)
|
||||
signal playerSelected(var player)
|
||||
signal volumeChanged(real volume)
|
||||
signal panelEntered
|
||||
signal panelExited
|
||||
|
||||
function applyVolume(volume) {
|
||||
if (usePlayerVolume) {
|
||||
activePlayer.volume = volume;
|
||||
return;
|
||||
}
|
||||
if (AudioService.sink?.audio)
|
||||
AudioService.sink.audio.volume = volume;
|
||||
}
|
||||
|
||||
function selectPlayer(player) {
|
||||
const current = MprisController.activePlayer;
|
||||
if (current && current !== player && current.canPause)
|
||||
current.pause();
|
||||
MprisController.setActivePlayer(player);
|
||||
root.closeRequested();
|
||||
}
|
||||
|
||||
property int __panelHoverCount: 0
|
||||
|
||||
readonly property bool overlayBlurActive: dropdownBlur.active
|
||||
@@ -100,7 +114,7 @@ Item {
|
||||
_wheelAccum -= notches * 120;
|
||||
SessionData.suppressOSDTemporarily();
|
||||
const next = currentVolume + notches * AudioService.wheelVolumeStep / 100;
|
||||
root.volumeChanged(Math.max(0, Math.min(1, next)));
|
||||
root.applyVolume(Math.max(0, Math.min(1, next)));
|
||||
}
|
||||
|
||||
readonly property Item activePanel: __activePanel
|
||||
@@ -257,7 +271,7 @@ Item {
|
||||
return;
|
||||
const ratio = 1.0 - (mouse.y / height);
|
||||
const volume = Math.max(0, Math.min(1, ratio));
|
||||
root.volumeChanged(volume);
|
||||
root.applyVolume(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -464,10 +478,8 @@ Item {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (modelData && modelData.name) {
|
||||
if (modelData && modelData.name)
|
||||
AudioService.setDefaultSinkByName(modelData.name);
|
||||
root.deviceSelected(modelData);
|
||||
}
|
||||
}
|
||||
onEntered: panelAreaEntered()
|
||||
onExited: panelAreaExited()
|
||||
@@ -648,9 +660,8 @@ Item {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (modelData?.identity) {
|
||||
root.playerSelected(modelData);
|
||||
}
|
||||
if (modelData?.identity)
|
||||
root.selectPlayer(modelData);
|
||||
}
|
||||
onEntered: panelAreaEntered()
|
||||
onExited: panelAreaExited()
|
||||
|
||||
@@ -1,540 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import "../../Common/Format.js" as Format
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var player
|
||||
|
||||
property alias volumeButton: volumeButton
|
||||
property alias playerSelectorButton: sourceButton
|
||||
property alias audioDevicesButton: outputButton
|
||||
|
||||
implicitHeight: 352
|
||||
|
||||
readonly property var activePlayer: root.player.activePlayer
|
||||
readonly property color accent: root.player.accent
|
||||
readonly property color onAccent: root.player.onAccent
|
||||
readonly property string artUrl: TrackArtService.resolvedArtUrl
|
||||
readonly property string title: MprisController.stableTitle || I18n.tr("Unknown Track", "island media player: fallback title")
|
||||
readonly property string artist: MprisController.stableArtist || I18n.tr("Unknown Artist", "island media player: fallback artist")
|
||||
readonly property string album: MprisController.stableAlbum || ""
|
||||
|
||||
Component {
|
||||
id: artBlobComponent
|
||||
|
||||
MediaBlobHalo {
|
||||
accentColor: root.accent
|
||||
playing: root.activePlayer?.playbackState === MprisPlaybackState.Playing
|
||||
}
|
||||
}
|
||||
|
||||
function volumeIcon() {
|
||||
return root.player.getVolumeIcon();
|
||||
}
|
||||
|
||||
function outputIcon() {
|
||||
return root.player.getAudioDeviceIcon(AudioService.sink);
|
||||
}
|
||||
|
||||
ClippingRectangle {
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 1
|
||||
}
|
||||
radius: 30
|
||||
color: Theme.surfaceContainerHigh
|
||||
antialiasing: true
|
||||
|
||||
Image {
|
||||
id: backdropImage
|
||||
|
||||
anchors.fill: parent
|
||||
source: root.visible ? root.artUrl : ""
|
||||
asynchronous: true
|
||||
cache: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
sourceSize: Qt.size(320, 320)
|
||||
visible: backdropImage.status === Image.Ready
|
||||
opacity: 0.62
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: backdropImage.status === Image.Ready
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, 0.9)
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
position: 0.4
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, 0.46)
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
position: 1
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, 0.95)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.surfaceContainerHigh
|
||||
visible: backdropImage.status !== Image.Ready
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: backdropImage.status !== Image.Ready
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0
|
||||
color: Theme.withAlpha(root.accent, 0.16)
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
position: 0.55
|
||||
color: Theme.withAlpha(root.accent, 0.04)
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
position: 1
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "music_note"
|
||||
size: 120
|
||||
color: Theme.withAlpha(root.accent, 0.10)
|
||||
visible: backdropImage.status !== Image.Ready
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
visible: root.player.showNoPlayerNow
|
||||
z: 2
|
||||
|
||||
DankIcon {
|
||||
name: "music_note"
|
||||
size: Theme.iconSize * 3
|
||||
color: Theme.surfaceTextSecondary
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("No Active Players", "island media player: empty state")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceTextMedium
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: cardBody
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 18
|
||||
}
|
||||
visible: !root.player.noneAvailable && !root.player.showNoPlayerNow
|
||||
|
||||
Item {
|
||||
id: headerRow
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
height: 132
|
||||
clip: false
|
||||
|
||||
Item {
|
||||
id: artCluster
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
}
|
||||
width: 120
|
||||
height: 120
|
||||
clip: false
|
||||
z: 1
|
||||
|
||||
Loader {
|
||||
anchors.centerIn: parent
|
||||
width: 136
|
||||
height: 136
|
||||
z: 0
|
||||
active: root.visible && root.player.live && SettingsData.audioVisualizerEnabled
|
||||
sourceComponent: artBlobComponent
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: artFrame
|
||||
|
||||
anchors.fill: parent
|
||||
z: 1
|
||||
radius: 30
|
||||
color: "transparent"
|
||||
border.width: 1
|
||||
border.color: Theme.withAlpha(root.accent, 0.3)
|
||||
|
||||
MediaArtwork {
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 2
|
||||
}
|
||||
cornerRadius: 28
|
||||
placeholderIconSize: 46
|
||||
artUrl: root.artUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors {
|
||||
left: artCluster.right
|
||||
leftMargin: Theme.spacingL
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
topMargin: 2
|
||||
}
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.title
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
font.weight: Font.Bold
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 2
|
||||
lineHeight: 1.08
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.artist
|
||||
color: Theme.surfaceTextMedium
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: root.album
|
||||
color: Theme.surfaceTextSecondary
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
visible: text.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: iconRail
|
||||
|
||||
anchors {
|
||||
right: parent.right
|
||||
top: headerRow.bottom
|
||||
topMargin: Theme.spacingL
|
||||
bottom: parent.bottom
|
||||
}
|
||||
width: 44
|
||||
z: 4
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
TonalIconButton {
|
||||
id: volumeButton
|
||||
|
||||
width: 44
|
||||
height: 44
|
||||
radius: 22
|
||||
iconName: root.volumeIcon()
|
||||
iconSize: 20
|
||||
iconColor: root.player.volumeAvailable && root.player.currentVolume > 0 ? root.accent : Theme.surfaceTextMedium
|
||||
containerColor: Theme.withAlpha(Theme.surfaceText, 0.08)
|
||||
interactive: root.player.volumeAvailable
|
||||
onTriggered: root.player.toggleMute()
|
||||
onScrolled: notches => root.player.adjustVolume(notches * AudioService.wheelVolumeStep)
|
||||
|
||||
HoverHandler {
|
||||
enabled: root.player.menusEnabled && root.player.volumeAvailable
|
||||
onHoveredChanged: {
|
||||
if (hovered)
|
||||
root.player.triggerVolumeDropdown();
|
||||
else
|
||||
root.player.dropdownButtonExited();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TonalIconButton {
|
||||
id: outputButton
|
||||
|
||||
width: 44
|
||||
height: 44
|
||||
radius: 22
|
||||
iconName: root.outputIcon()
|
||||
iconSize: 20
|
||||
iconColor: Theme.surfaceText
|
||||
containerColor: Theme.withAlpha(Theme.surfaceText, 0.08)
|
||||
onTriggered: root.player.cycleNextSink()
|
||||
onScrolled: notches => AudioService.cycleAudioOutputDirection(notches < 0)
|
||||
|
||||
HoverHandler {
|
||||
enabled: root.player.menusEnabled
|
||||
onHoveredChanged: {
|
||||
if (hovered)
|
||||
root.player.triggerDevicesDropdown();
|
||||
else
|
||||
root.player.dropdownButtonExited();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TonalIconButton {
|
||||
id: sourceButton
|
||||
|
||||
width: 44
|
||||
height: 44
|
||||
radius: 22
|
||||
visible: (root.player.allPlayers?.length || 0) > 0
|
||||
iconName: "assistant_device"
|
||||
iconSize: 20
|
||||
iconColor: root.player.playersExpanded ? root.accent : Theme.surfaceText
|
||||
containerColor: Theme.withAlpha(root.accent, root.player.playersExpanded ? 0.16 : 0.08)
|
||||
onTriggered: {
|
||||
if (root.player.playersExpanded) {
|
||||
root.player.cycleNextPlayer();
|
||||
return;
|
||||
}
|
||||
root.player.triggerPlayersDropdown();
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
enabled: root.player.menusEnabled && sourceButton.visible
|
||||
onHoveredChanged: {
|
||||
if (hovered)
|
||||
root.player.triggerPlayersDropdown();
|
||||
else
|
||||
root.player.dropdownButtonExited();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: playColumn
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: iconRail.left
|
||||
rightMargin: Theme.spacingM
|
||||
top: headerRow.bottom
|
||||
topMargin: Theme.spacingL
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
Item {
|
||||
id: seekBlock
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: iconRail.width + Theme.spacingM
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 76
|
||||
}
|
||||
height: 40
|
||||
|
||||
DankSeekbar {
|
||||
anchors.top: parent.top
|
||||
width: parent.width
|
||||
height: 22
|
||||
activePlayer: root.activePlayer
|
||||
stableLength: root.player.stableLength
|
||||
accentColor: root.accent
|
||||
accentTrackColor: MediaAccentService.accentTrack
|
||||
accentSubtleColor: MediaAccentService.accentSubtle
|
||||
isSeeking: root.player.isSeeking
|
||||
onIsSeekingChanged: root.player.isSeeking = isSeeking
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors {
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
}
|
||||
text: Format.formatDuration(root.activePlayer?.position || 0)
|
||||
color: root.accent
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors {
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
text: root.player.stableLength > 0 ? Format.formatDuration(root.player.stableLength) : "--:--"
|
||||
color: Theme.surfaceTextSecondary
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
horizontalCenterOffset: (iconRail.width + Theme.spacingM) / 2
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 2
|
||||
}
|
||||
spacing: Theme.spacingS
|
||||
|
||||
TonalIconButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40
|
||||
height: 40
|
||||
radius: 20
|
||||
visible: !!root.activePlayer?.shuffleSupported
|
||||
iconName: "shuffle"
|
||||
iconSize: 18
|
||||
iconColor: root.activePlayer?.shuffle ? root.accent : Theme.surfaceText
|
||||
containerColor: Theme.withAlpha(root.accent, root.activePlayer?.shuffle ? 0.16 : 0)
|
||||
onTriggered: {
|
||||
if (root.activePlayer?.canControl && root.activePlayer?.shuffleSupported)
|
||||
root.activePlayer.shuffle = !root.activePlayer.shuffle;
|
||||
}
|
||||
}
|
||||
|
||||
TonalIconButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 52
|
||||
height: 52
|
||||
radius: 18
|
||||
iconName: "skip_previous"
|
||||
iconSize: 26
|
||||
containerColor: Theme.withAlpha(root.accent, 0.14)
|
||||
interactive: !!root.activePlayer?.canGoPrevious || !!root.activePlayer?.canSeek
|
||||
onTriggered: MprisController.previousOrRewind()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: playHero
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 68
|
||||
height: 68
|
||||
radius: root.activePlayer?.playbackState === MprisPlaybackState.Playing ? 22 : 34
|
||||
color: root.accent
|
||||
opacity: root.activePlayer?.canTogglePlaying ? 1 : 0.38
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: root.activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
||||
size: 34
|
||||
color: root.onAccent
|
||||
weight: 500
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
stateColor: root.onAccent
|
||||
disabled: !root.activePlayer || !root.activePlayer.canTogglePlaying
|
||||
onClicked: root.activePlayer.togglePlaying()
|
||||
}
|
||||
}
|
||||
|
||||
TonalIconButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 52
|
||||
height: 52
|
||||
radius: 18
|
||||
iconName: "skip_next"
|
||||
iconSize: 26
|
||||
containerColor: Theme.withAlpha(root.accent, 0.14)
|
||||
interactive: !!root.activePlayer?.canGoNext
|
||||
onTriggered: MprisController.next()
|
||||
}
|
||||
|
||||
TonalIconButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40
|
||||
height: 40
|
||||
radius: 20
|
||||
visible: !!root.activePlayer?.loopSupported
|
||||
iconName: {
|
||||
if (!root.activePlayer)
|
||||
return "repeat";
|
||||
return root.activePlayer.loopState === MprisLoopState.Track ? "repeat_one" : "repeat";
|
||||
}
|
||||
iconSize: 18
|
||||
iconColor: root.activePlayer && root.activePlayer.loopState !== MprisLoopState.None ? root.accent : Theme.surfaceText
|
||||
containerColor: Theme.withAlpha(root.accent, root.activePlayer && root.activePlayer.loopState !== MprisLoopState.None ? 0.16 : 0)
|
||||
onTriggered: root.player.cycleLoopState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component TonalIconButton: Rectangle {
|
||||
id: tonal
|
||||
|
||||
property string iconName: ""
|
||||
property real iconSize: 22
|
||||
property color iconColor: Theme.surfaceText
|
||||
property color containerColor: Theme.withAlpha(Theme.surfaceText, 0.08)
|
||||
property bool interactive: true
|
||||
|
||||
signal triggered
|
||||
signal scrolled(int notches)
|
||||
|
||||
color: tonal.containerColor
|
||||
opacity: tonal.interactive ? 1 : 0.38
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: tonal.iconName
|
||||
size: tonal.iconSize
|
||||
color: tonal.iconColor
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
stateColor: tonal.iconColor
|
||||
disabled: !tonal.interactive
|
||||
enabled: tonal.interactive
|
||||
onClicked: tonal.triggered()
|
||||
onWheel: wheelEvent => {
|
||||
wheelEvent.accepted = true;
|
||||
tonal.scrolled(wheelEvent.angleDelta.y > 0 ? 1 : -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,10 +25,15 @@ Item {
|
||||
property real contentOffsetY: 0
|
||||
property string section: ""
|
||||
property int barPosition: SettingsData.Position.Top
|
||||
property string chrome: "dash"
|
||||
property bool live: Window.window?.visible ?? false
|
||||
property bool menusEnabled: true
|
||||
readonly property bool islandChrome: chrome === "island"
|
||||
property bool compact: false
|
||||
|
||||
readonly property real contentTopMargin: compact ? Theme.spacingM : 20
|
||||
readonly property real contentHeight: compact ? 320 : 370
|
||||
readonly property real artAreaHeight: compact ? 150 : 200
|
||||
readonly property real sideButtonTop: contentTopMargin + artAreaHeight / 2 + 10
|
||||
readonly property real sideButtonStep: 55
|
||||
|
||||
readonly property color accent: MediaAccentService.accent
|
||||
readonly property color onAccent: MediaAccentService.onAccent
|
||||
@@ -121,7 +126,7 @@ Item {
|
||||
}
|
||||
|
||||
implicitWidth: SettingsData.showWeekNumber ? 736 : 700
|
||||
implicitHeight: chromeLoader.item ? chromeLoader.item.implicitHeight : 410
|
||||
implicitHeight: playerContent.height + playerContent.anchors.topMargin * 2
|
||||
|
||||
Connections {
|
||||
target: activePlayer
|
||||
@@ -197,18 +202,8 @@ Item {
|
||||
}
|
||||
|
||||
function dropdownAnchor(button) {
|
||||
if (!button)
|
||||
return null;
|
||||
if (root.islandChrome) {
|
||||
const rightEdge = root.mapToItem(null, root.width, 0).x;
|
||||
const point = button.mapToItem(null, 0, button.height / 2);
|
||||
return {
|
||||
"pos": Qt.point(rightEdge + Theme.spacingS, point.y),
|
||||
"rightEdge": true
|
||||
};
|
||||
}
|
||||
const buttonsOnRight = !root.isRightEdge;
|
||||
const btnY = button.mapToItem(root, 0, button.height / 2).y;
|
||||
const btnY = button.y + button.height / 2;
|
||||
return {
|
||||
"pos": Qt.point(buttonsOnRight ? (root.popoutX + root.popoutWidth) : root.popoutX, root.popoutY + root.contentOffsetY + btnY),
|
||||
"rightEdge": buttonsOnRight
|
||||
@@ -216,42 +211,27 @@ Item {
|
||||
}
|
||||
|
||||
function triggerVolumeDropdown() {
|
||||
if (!root.menusEnabled || !volumeAvailable)
|
||||
return;
|
||||
if (volumeExpanded)
|
||||
return;
|
||||
const button = chromeLoader.item?.volumeButton;
|
||||
const anchor = dropdownAnchor(button);
|
||||
if (!anchor)
|
||||
if (!root.menusEnabled || !volumeAvailable || volumeExpanded)
|
||||
return;
|
||||
const anchor = dropdownAnchor(volumeButton);
|
||||
hideDropdowns();
|
||||
volumeExpanded = true;
|
||||
showVolumeDropdown(anchor.pos, targetScreen, anchor.rightEdge, activePlayer, allPlayers);
|
||||
}
|
||||
|
||||
function triggerPlayersDropdown() {
|
||||
if (!root.menusEnabled)
|
||||
return;
|
||||
const button = chromeLoader.item?.playerSelectorButton;
|
||||
const anchor = dropdownAnchor(button);
|
||||
if (!anchor)
|
||||
return;
|
||||
if (playersExpanded)
|
||||
if (!root.menusEnabled || playersExpanded)
|
||||
return;
|
||||
const anchor = dropdownAnchor(playerSelectorButton);
|
||||
hideDropdowns();
|
||||
playersExpanded = true;
|
||||
showPlayersDropdown(anchor.pos, targetScreen, anchor.rightEdge, activePlayer, allPlayers);
|
||||
}
|
||||
|
||||
function triggerDevicesDropdown() {
|
||||
if (!root.menusEnabled)
|
||||
return;
|
||||
const button = chromeLoader.item?.audioDevicesButton;
|
||||
const anchor = dropdownAnchor(button);
|
||||
if (!anchor)
|
||||
return;
|
||||
if (devicesExpanded)
|
||||
if (!root.menusEnabled || devicesExpanded)
|
||||
return;
|
||||
const anchor = dropdownAnchor(audioDevicesButton);
|
||||
hideDropdowns();
|
||||
devicesExpanded = true;
|
||||
showAudioDevicesDropdown(anchor.pos, targetScreen, anchor.rightEdge);
|
||||
@@ -392,33 +372,8 @@ Item {
|
||||
onTriggered: activePlayer?.positionChanged()
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: chromeLoader
|
||||
|
||||
anchors.fill: parent
|
||||
sourceComponent: root.islandChrome ? islandChromeComponent : dashChromeComponent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: islandChromeComponent
|
||||
|
||||
MediaPlayerIslandChrome {
|
||||
player: root
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: dashChromeComponent
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
implicitHeight: playerContent.height + playerContent.anchors.topMargin * 2
|
||||
property alias volumeButton: volumeButton
|
||||
property alias playerSelectorButton: playerSelectorButton
|
||||
property alias audioDevicesButton: audioDevicesButton
|
||||
|
||||
Item {
|
||||
id: bgContainer
|
||||
Item {
|
||||
id: bgContainer
|
||||
anchors.fill: parent
|
||||
|
||||
// Fall back to the live mpris url so the background is never blank.
|
||||
@@ -564,15 +519,15 @@ Item {
|
||||
ColumnLayout {
|
||||
id: playerContent
|
||||
width: 484
|
||||
height: 370
|
||||
height: root.contentHeight
|
||||
spacing: Theme.spacingXS
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 20
|
||||
anchors.topMargin: root.contentTopMargin
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 200
|
||||
height: root.artAreaHeight
|
||||
clip: false
|
||||
|
||||
DankAlbumArt {
|
||||
@@ -876,21 +831,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (activePlayer && activePlayer.canControl && activePlayer.loopSupported) {
|
||||
switch (activePlayer.loopState) {
|
||||
case MprisLoopState.None:
|
||||
activePlayer.loopState = MprisLoopState.Playlist;
|
||||
break;
|
||||
case MprisLoopState.Playlist:
|
||||
activePlayer.loopState = MprisLoopState.Track;
|
||||
break;
|
||||
case MprisLoopState.Track:
|
||||
activePlayer.loopState = MprisLoopState.None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
onClicked: root.cycleLoopState()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -906,7 +847,7 @@ Item {
|
||||
height: 40
|
||||
radius: 20
|
||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||
y: 185
|
||||
y: root.sideButtonTop + root.sideButtonStep
|
||||
color: playerSelectorArea.containsMouse || playersExpanded ? root.accentPressed : Theme.withAlpha(root.accentPressed, 0)
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
@@ -927,39 +868,14 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (playersExpanded) {
|
||||
const players = (root.allPlayers || []).filter(p => p && !MprisController.isIdle(p));
|
||||
if (players.length > 1) {
|
||||
let currentIndex = -1;
|
||||
for (let i = 0; i < players.length; i++) {
|
||||
if (players[i] === root.activePlayer) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const nextIndex = (currentIndex + 1) % players.length;
|
||||
MprisController.setActivePlayer(players[nextIndex]);
|
||||
}
|
||||
root.cycleNextPlayer();
|
||||
return;
|
||||
}
|
||||
hideDropdowns();
|
||||
playersExpanded = true;
|
||||
const buttonsOnRight = !isRightEdge;
|
||||
const btnY = playerSelectorButton.y + playerSelectorButton.height / 2;
|
||||
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
||||
const screenY = popoutY + contentOffsetY + btnY;
|
||||
showPlayersDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
|
||||
root.triggerPlayersDropdown();
|
||||
}
|
||||
onEntered: {
|
||||
dropdownButtonEntered();
|
||||
if (playersExpanded)
|
||||
return;
|
||||
hideDropdowns();
|
||||
playersExpanded = true;
|
||||
const buttonsOnRight = !isRightEdge;
|
||||
const btnY = playerSelectorButton.y + playerSelectorButton.height / 2;
|
||||
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
||||
const screenY = popoutY + contentOffsetY + btnY;
|
||||
showPlayersDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
|
||||
root.triggerPlayersDropdown();
|
||||
}
|
||||
onExited: {
|
||||
if (playersExpanded)
|
||||
@@ -974,7 +890,7 @@ Item {
|
||||
height: 40
|
||||
radius: 20
|
||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||
y: 130
|
||||
y: root.sideButtonTop
|
||||
color: volumeButtonArea.containsMouse && volumeAvailable || volumeExpanded ? root.accentPressed : Theme.withAlpha(root.accentPressed, 0)
|
||||
border.color: volumeAvailable ? Theme.outlineStrong : Theme.outlineMedium
|
||||
border.width: 1
|
||||
@@ -995,23 +911,13 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: {
|
||||
dropdownButtonEntered();
|
||||
if (volumeExpanded)
|
||||
return;
|
||||
hideDropdowns();
|
||||
volumeExpanded = true;
|
||||
const buttonsOnRight = !isRightEdge;
|
||||
const btnY = volumeButton.y + volumeButton.height / 2;
|
||||
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
||||
const screenY = popoutY + contentOffsetY + btnY;
|
||||
showVolumeDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
|
||||
root.triggerVolumeDropdown();
|
||||
}
|
||||
onExited: {
|
||||
if (volumeExpanded)
|
||||
dropdownButtonExited();
|
||||
}
|
||||
onClicked: {
|
||||
toggleMute();
|
||||
}
|
||||
onClicked: root.toggleMute()
|
||||
property real wheelAccum: 0
|
||||
onWheel: wheelEvent => {
|
||||
wheelEvent.accepted = true;
|
||||
@@ -1031,7 +937,7 @@ Item {
|
||||
height: 40
|
||||
radius: 20
|
||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||
y: 240
|
||||
y: root.sideButtonTop + root.sideButtonStep * 2
|
||||
color: audioDevicesArea.containsMouse || devicesExpanded ? root.accentPressed : Theme.withAlpha(root.accentPressed, 0)
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
@@ -1050,67 +956,35 @@ Item {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onPressed: mouse => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
onWheel: wheelEvent => {
|
||||
const delta = wheelEvent.angleDelta.y;
|
||||
if (delta !== 0) {
|
||||
AudioService.cycleAudioOutputDirection(delta < 0);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
if (delta === 0)
|
||||
return;
|
||||
AudioService.cycleAudioOutputDirection(delta < 0);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
if (AudioService.sink?.audio) {
|
||||
SessionData.suppressOSDTemporarily();
|
||||
AudioService.sink.audio.muted = !AudioService.sink.audio.muted;
|
||||
}
|
||||
if (!AudioService.sink?.audio)
|
||||
return;
|
||||
SessionData.suppressOSDTemporarily();
|
||||
AudioService.sink.audio.muted = !AudioService.sink.audio.muted;
|
||||
return;
|
||||
}
|
||||
if (devicesExpanded) {
|
||||
const sinks = AudioService.getAvailableSinks();
|
||||
if (sinks && sinks.length > 1) {
|
||||
let currentIndex = -1;
|
||||
for (let i = 0; i < sinks.length; i++) {
|
||||
if (sinks[i]?.name === AudioService.sink?.name) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const nextIndex = (currentIndex + 1) % sinks.length;
|
||||
AudioService.setSink(sinks[nextIndex]);
|
||||
}
|
||||
root.cycleNextSink();
|
||||
return;
|
||||
}
|
||||
hideDropdowns();
|
||||
devicesExpanded = true;
|
||||
const buttonsOnRight = !isRightEdge;
|
||||
const btnY = audioDevicesButton.y + audioDevicesButton.height / 2;
|
||||
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
||||
const screenY = popoutY + contentOffsetY + btnY;
|
||||
showAudioDevicesDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight);
|
||||
root.triggerDevicesDropdown();
|
||||
}
|
||||
onEntered: {
|
||||
dropdownButtonEntered();
|
||||
if (devicesExpanded)
|
||||
return;
|
||||
hideDropdowns();
|
||||
devicesExpanded = true;
|
||||
const buttonsOnRight = !isRightEdge;
|
||||
const btnY = audioDevicesButton.y + audioDevicesButton.height / 2;
|
||||
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
||||
const screenY = popoutY + contentOffsetY + btnY;
|
||||
showAudioDevicesDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight);
|
||||
root.triggerDevicesDropdown();
|
||||
}
|
||||
onExited: {
|
||||
if (devicesExpanded)
|
||||
dropdownButtonExited();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
required property var controller
|
||||
required property string activityId
|
||||
required property Component tabComponent
|
||||
property real inset: Theme.spacingM
|
||||
property bool contentStaged: false
|
||||
|
||||
readonly property bool live: root.controller.expanded && root.controller.activeActivity === root.activityId
|
||||
readonly property var tab: tabLoader.item
|
||||
|
||||
clip: true
|
||||
|
||||
function focusFace() {
|
||||
root.forceActiveFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
function forwardKey(event) {
|
||||
if (!root.tab || typeof root.tab.handleKeyEvent !== "function")
|
||||
return false;
|
||||
return root.tab.handleKeyEvent(event) === true;
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: tabLoader
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: root.inset
|
||||
}
|
||||
active: root.contentStaged
|
||||
asynchronous: true
|
||||
visible: status === Loader.Ready
|
||||
sourceComponent: root.tabComponent
|
||||
onLoaded: {
|
||||
if (root.live)
|
||||
root.focusFace();
|
||||
}
|
||||
}
|
||||
|
||||
DankSpinner {
|
||||
anchors.centerIn: parent
|
||||
size: 40
|
||||
visible: !tabLoader.visible
|
||||
}
|
||||
|
||||
Keys.onPressed: event => event.accepted = root.forwardKey(event)
|
||||
|
||||
Component.onCompleted: root.contentStaged = true
|
||||
}
|
||||
@@ -3,62 +3,21 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.DankDash
|
||||
import qs.Widgets
|
||||
|
||||
FocusScope {
|
||||
DashTabFace {
|
||||
id: root
|
||||
|
||||
required property var controller
|
||||
|
||||
property bool contentStaged: false
|
||||
|
||||
readonly property real stageWidth: root.controller.homeExpandedTarget.width - 2
|
||||
readonly property real stageHeight: root.controller.homeExpandedTarget.height - 2
|
||||
readonly property real faceInset: Theme.spacingM
|
||||
readonly property var overviewTab: tabLoader.item
|
||||
|
||||
clip: true
|
||||
|
||||
function focusOverview() {
|
||||
root.forceActiveFocus();
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: tabLoader
|
||||
|
||||
x: root.faceInset
|
||||
width: root.stageWidth - root.faceInset * 2
|
||||
height: tabLoader.item?.implicitHeight || 410
|
||||
y: Math.round((root.stageHeight - height) / 2)
|
||||
active: root.contentStaged
|
||||
asynchronous: true
|
||||
visible: status === Loader.Ready
|
||||
sourceComponent: overviewTabComponent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: overviewTabComponent
|
||||
|
||||
activityId: "home"
|
||||
tabComponent: Component {
|
||||
OverviewTab {
|
||||
live: root.controller.expanded && root.controller.activeActivity === "home"
|
||||
live: root.live
|
||||
onCloseDash: root.controller.requestCollapse()
|
||||
onNavFocusRequested: root.focusOverview()
|
||||
onNavFocusRequested: root.focusFace()
|
||||
onSwitchToMediaTab: root.controller.requestActivity("media", true, true)
|
||||
onSwitchToWeatherTab: {
|
||||
if (SettingsData.weatherEnabled)
|
||||
root.controller.requestWeather(false);
|
||||
}
|
||||
Component.onCompleted: root.focusOverview()
|
||||
}
|
||||
}
|
||||
|
||||
DankSpinner {
|
||||
anchors.centerIn: parent
|
||||
size: 40
|
||||
visible: !tabLoader.visible
|
||||
}
|
||||
|
||||
Keys.onPressed: event => event.accepted = root.overviewTab?.handleKeyEvent(event) === true
|
||||
|
||||
Component.onCompleted: root.contentStaged = true
|
||||
}
|
||||
@@ -19,8 +19,9 @@ FocusScope {
|
||||
|
||||
clip: true
|
||||
|
||||
function focusSearch() {
|
||||
function focusFace() {
|
||||
launcherContent.searchField.forceActiveFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
function initializeSession() {
|
||||
@@ -42,7 +43,7 @@ FocusScope {
|
||||
launcherController.performSearch();
|
||||
|
||||
launcherContent.resetScroll();
|
||||
root.focusSearch();
|
||||
root.focusFace();
|
||||
if (!root.controller.launcherPendingQuery) {
|
||||
launcherContent.searchField.selectAll();
|
||||
return;
|
||||
|
||||
@@ -69,7 +69,7 @@ Item {
|
||||
}
|
||||
width: root.artworkSize
|
||||
height: width
|
||||
mediaModel: root.mediaModel
|
||||
artUrl: root.mediaModel.artUrl
|
||||
}
|
||||
|
||||
Row {
|
||||
|
||||
@@ -2,89 +2,49 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Modules.DankDash
|
||||
import qs.Widgets
|
||||
|
||||
FocusScope {
|
||||
DashTabFace {
|
||||
id: root
|
||||
|
||||
required property var controller
|
||||
property var effectiveScreen: null
|
||||
property real alignedX: 0
|
||||
property real alignedY: 0
|
||||
property real alignedWidth: 0
|
||||
property real alignedHeight: 0
|
||||
|
||||
property bool contentStaged: false
|
||||
property bool geometrySettled: false
|
||||
readonly property bool menusEnabled: root.controller.expanded && geometrySettled
|
||||
readonly property var playerTab: tabLoader.item
|
||||
readonly property bool menusEnabled: root.live && root.geometrySettled
|
||||
|
||||
signal showVolumeDropdown(point pos)
|
||||
signal showAudioDevicesDropdown(point pos)
|
||||
signal showPlayersDropdown(point pos)
|
||||
signal hideDropdowns
|
||||
signal dropdownRequested(int dropdownType, point pos)
|
||||
signal dropdownsHidden
|
||||
signal dropdownHoverStarted
|
||||
signal dropdownHoverEnded
|
||||
|
||||
clip: true
|
||||
|
||||
function focusPlayer() {
|
||||
root.forceActiveFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
function resetDropdownStates() {
|
||||
root.playerTab?.resetDropdownStates();
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: tabLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: root.contentStaged
|
||||
asynchronous: true
|
||||
visible: status === Loader.Ready
|
||||
sourceComponent: playerTabComponent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: playerTabComponent
|
||||
|
||||
activityId: "media"
|
||||
tabComponent: Component {
|
||||
MediaPlayerTab {
|
||||
chrome: "island"
|
||||
live: root.controller.expanded && root.controller.activeActivity === "media"
|
||||
compact: true
|
||||
live: root.live
|
||||
menusEnabled: root.menusEnabled
|
||||
targetScreen: root.effectiveScreen
|
||||
popoutX: root.alignedX
|
||||
popoutY: root.alignedY
|
||||
popoutWidth: root.alignedWidth
|
||||
popoutHeight: root.alignedHeight
|
||||
contentOffsetY: 0
|
||||
section: "right"
|
||||
menusEnabled: root.menusEnabled
|
||||
onShowVolumeDropdown: (pos, screen, rightEdge, player, players) => root.showVolumeDropdown(pos)
|
||||
onShowAudioDevicesDropdown: (pos, screen, rightEdge) => root.showAudioDevicesDropdown(pos)
|
||||
onShowPlayersDropdown: (pos, screen, rightEdge, player, players) => root.showPlayersDropdown(pos)
|
||||
onHideDropdowns: root.hideDropdowns()
|
||||
contentOffsetY: root.inset
|
||||
section: "left"
|
||||
onShowVolumeDropdown: pos => root.dropdownRequested(1, pos)
|
||||
onShowAudioDevicesDropdown: pos => root.dropdownRequested(2, pos)
|
||||
onShowPlayersDropdown: pos => root.dropdownRequested(3, pos)
|
||||
onHideDropdowns: root.dropdownsHidden()
|
||||
onDropdownButtonEntered: root.dropdownHoverStarted()
|
||||
onDropdownButtonExited: root.dropdownHoverEnded()
|
||||
Component.onCompleted: root.focusPlayer()
|
||||
}
|
||||
}
|
||||
|
||||
DankSpinner {
|
||||
anchors.centerIn: parent
|
||||
size: 40
|
||||
visible: !tabLoader.visible
|
||||
}
|
||||
|
||||
Keys.onPressed: event => event.accepted = root.playerTab?.handleKeyEvent(event) === true
|
||||
|
||||
Connections {
|
||||
target: root.controller
|
||||
|
||||
function onMediaDropdownOpenChanged() {
|
||||
if (!root.controller.mediaDropdownOpen)
|
||||
root.resetDropdownStates();
|
||||
root.tab?.resetDropdownStates();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: root.contentStaged = true
|
||||
}
|
||||
@@ -17,7 +17,7 @@ FocusScope {
|
||||
|
||||
clip: true
|
||||
|
||||
function focusList() {
|
||||
function focusFace() {
|
||||
root.forceActiveFocus();
|
||||
keyboardController.rebuildFlatNavigation();
|
||||
return true;
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.DankDash
|
||||
import qs.Widgets
|
||||
|
||||
FocusScope {
|
||||
DashTabFace {
|
||||
id: root
|
||||
|
||||
required property var controller
|
||||
property var effectiveScreen: null
|
||||
|
||||
property bool contentStaged: false
|
||||
|
||||
readonly property real stageWidth: root.controller.wallpaperExpandedTarget.width - 2
|
||||
readonly property real stageHeight: root.controller.wallpaperExpandedTarget.height - 2
|
||||
readonly property var wallpaperTab: tabLoader.item
|
||||
|
||||
clip: true
|
||||
|
||||
function focusGrid() {
|
||||
root.forceActiveFocus();
|
||||
activityId: "wallpaper"
|
||||
tabComponent: Component {
|
||||
WallpaperTab {
|
||||
active: true
|
||||
pagerCachePages: 0
|
||||
targetScreen: root.effectiveScreen
|
||||
parentPopout: hostContract
|
||||
}
|
||||
}
|
||||
|
||||
function beginSession() {
|
||||
root.wallpaperTab?.collapseSearch();
|
||||
root.focusGrid();
|
||||
root.tab?.collapseSearch();
|
||||
root.focusFace();
|
||||
}
|
||||
|
||||
QtObject {
|
||||
@@ -36,39 +31,6 @@ FocusScope {
|
||||
onCustomKeyboardFocusChanged: root.controller.keyboardYielded = hostContract.customKeyboardFocus !== null
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: tabLoader
|
||||
|
||||
x: Theme.spacingM
|
||||
y: Theme.spacingM
|
||||
width: root.stageWidth - Theme.spacingM * 2
|
||||
height: root.stageHeight - Theme.spacingM - Theme.spacingS
|
||||
active: root.contentStaged
|
||||
asynchronous: true
|
||||
visible: status === Loader.Ready
|
||||
sourceComponent: wallpaperTabComponent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: wallpaperTabComponent
|
||||
|
||||
WallpaperTab {
|
||||
active: true
|
||||
pagerCachePages: 0
|
||||
targetScreen: root.effectiveScreen
|
||||
parentPopout: hostContract
|
||||
Component.onCompleted: root.focusGrid()
|
||||
}
|
||||
}
|
||||
|
||||
DankSpinner {
|
||||
anchors.centerIn: parent
|
||||
size: 40
|
||||
visible: !tabLoader.visible
|
||||
}
|
||||
|
||||
Keys.onPressed: event => event.accepted = root.wallpaperTab?.handleKeyEvent(event) === true
|
||||
|
||||
Connections {
|
||||
target: root.controller
|
||||
|
||||
@@ -78,11 +40,7 @@ FocusScope {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.contentStaged = true;
|
||||
root.controller.markVisualsReady("wallpaper");
|
||||
}
|
||||
|
||||
Component.onCompleted: root.controller.markVisualsReady("wallpaper")
|
||||
Component.onDestruction: {
|
||||
root.controller.keyboardYielded = false;
|
||||
root.controller.setVisualsReady("wallpaper", false);
|
||||
|
||||
@@ -1,61 +1,18 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.DankDash
|
||||
import qs.Widgets
|
||||
|
||||
FocusScope {
|
||||
DashTabFace {
|
||||
id: root
|
||||
|
||||
required property var controller
|
||||
|
||||
property bool contentStaged: false
|
||||
|
||||
readonly property real stageWidth: root.controller.weatherExpandedTarget.width - 2
|
||||
readonly property real stageHeight: root.controller.weatherExpandedTarget.height - 2
|
||||
readonly property real faceInset: Theme.spacingM
|
||||
|
||||
clip: true
|
||||
|
||||
function focusWeather() {
|
||||
root.forceActiveFocus();
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: tabLoader
|
||||
|
||||
x: root.faceInset
|
||||
y: root.faceInset
|
||||
width: root.stageWidth - root.faceInset * 2
|
||||
height: root.stageHeight - root.faceInset * 2
|
||||
active: root.contentStaged
|
||||
asynchronous: true
|
||||
visible: status === Loader.Ready
|
||||
sourceComponent: weatherTabComponent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: weatherTabComponent
|
||||
|
||||
activityId: "weather"
|
||||
tabComponent: Component {
|
||||
WeatherTab {
|
||||
live: root.controller.expanded && root.controller.activeActivity === "weather"
|
||||
Component.onCompleted: root.focusWeather()
|
||||
live: root.live
|
||||
}
|
||||
}
|
||||
|
||||
DankSpinner {
|
||||
anchors.centerIn: parent
|
||||
size: 40
|
||||
visible: !tabLoader.visible
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.contentStaged = true;
|
||||
root.controller.markVisualsReady("weather");
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
root.controller.setVisualsReady("weather", false);
|
||||
}
|
||||
Component.onCompleted: root.controller.markVisualsReady("weather")
|
||||
Component.onDestruction: root.controller.setVisualsReady("weather", false)
|
||||
}
|
||||
@@ -353,17 +353,6 @@ Item {
|
||||
onCloseRequested: root.hideMediaDropdowns()
|
||||
onPanelEntered: root.stopMediaDropdownCloseTimer()
|
||||
onPanelExited: root.startMediaDropdownCloseTimer()
|
||||
onVolumeChanged: volume => {
|
||||
const player = MprisController.activePlayer;
|
||||
const identity = (player?.identity ?? "").toLowerCase();
|
||||
const chrome = identity.includes("chrome") || identity.includes("chromium");
|
||||
if (player && player.volumeSupported && !chrome)
|
||||
player.volume = volume;
|
||||
else if (AudioService.sink?.audio)
|
||||
AudioService.sink.audio.volume = volume;
|
||||
}
|
||||
onDeviceSelected: device => AudioService.setSink(device)
|
||||
onPlayerSelected: player => MprisController.setActivePlayer(player)
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -401,11 +390,9 @@ Item {
|
||||
alignedX: root.targetVisualX
|
||||
alignedY: root.targetVisualY
|
||||
alignedWidth: root.targetVisualWidth
|
||||
alignedHeight: root.targetVisualHeight
|
||||
onShowVolumeDropdown: pos => root.openMediaDropdown(1, pos)
|
||||
onShowAudioDevicesDropdown: pos => root.openMediaDropdown(2, pos)
|
||||
onShowPlayersDropdown: pos => root.openMediaDropdown(3, pos)
|
||||
onHideDropdowns: root.hideMediaDropdowns()
|
||||
onDropdownRequested: (dropdownType, pos) => root.openMediaDropdown(dropdownType, pos)
|
||||
onDropdownsHidden: root.hideMediaDropdowns()
|
||||
onDropdownHoverStarted: root.stopMediaDropdownCloseTimer()
|
||||
onDropdownHoverEnded: root.startMediaDropdownCloseTimer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,34 +78,20 @@ Item {
|
||||
return Math.max(incoming, outgoing);
|
||||
}
|
||||
|
||||
readonly property var focusableFaces: ({
|
||||
"home": homeExpandedLoader,
|
||||
"media": mediaExpandedLoader,
|
||||
"launcher": launcherExpandedLoader,
|
||||
"wallpaper": wallpaperExpandedLoader,
|
||||
"weather": weatherExpandedLoader,
|
||||
"notificationcenter": notificationCenterExpandedLoader
|
||||
})
|
||||
|
||||
function requestActivityFocus() {
|
||||
switch (root.activityId) {
|
||||
case "launcher":
|
||||
if (!launcherExpandedLoader.item)
|
||||
return false;
|
||||
launcherExpandedLoader.item.focusSearch();
|
||||
return true;
|
||||
case "home":
|
||||
if (!homeExpandedLoader.item)
|
||||
return false;
|
||||
homeExpandedLoader.item.focusOverview();
|
||||
return true;
|
||||
case "media":
|
||||
return mediaExpandedLoader.item?.focusPlayer() === true;
|
||||
case "wallpaper":
|
||||
if (!wallpaperExpandedLoader.item)
|
||||
return false;
|
||||
wallpaperExpandedLoader.item.focusGrid();
|
||||
return true;
|
||||
case "weather":
|
||||
if (!weatherExpandedLoader.item)
|
||||
return false;
|
||||
weatherExpandedLoader.item.focusWeather();
|
||||
return true;
|
||||
case "notificationcenter":
|
||||
return notificationCenterExpandedLoader.item?.focusList() === true;
|
||||
}
|
||||
return false;
|
||||
const face = root.focusableFaces[root.activityId]?.item;
|
||||
if (!face || typeof face.focusFace !== "function")
|
||||
return false;
|
||||
return face.focusFace() === true;
|
||||
}
|
||||
|
||||
function latchHomeExpanded() {
|
||||
|
||||
@@ -301,13 +301,11 @@ QtObject {
|
||||
}
|
||||
|
||||
readonly property var homeCompactTarget: pillTarget(homeCompactWidth, homeCompactFaceHeight)
|
||||
readonly property var homeExpandedTarget: sheetTarget(SettingsData.showWeekNumber ? 736 : 700, 452)
|
||||
readonly property var mediaCompactTarget: pillTarget(mediaCompactWidth, compactFaceHeight)
|
||||
readonly property var mediaExpandedTarget: sheetTarget(600, 352)
|
||||
readonly property var dashSheetTarget: sheetTarget(SettingsData.showWeekNumber ? 736 : 700, 452)
|
||||
readonly property var mediaExpandedTarget: sheetTarget(600, 384)
|
||||
readonly property var launcherExpandedTarget: sheetTarget(680, 560)
|
||||
readonly property var controlCenterExpandedTarget: sheetTarget(580, controlCenterHeight)
|
||||
readonly property var wallpaperExpandedTarget: sheetTarget(700, 452)
|
||||
readonly property var weatherExpandedTarget: sheetTarget(SettingsData.showWeekNumber ? 736 : 700, 452)
|
||||
readonly property var systemCompactTarget: pillTarget(SettingsData.osdAlwaysShowValue ? 330 : 282, compactFaceHeight)
|
||||
readonly property var systemExpandedTarget: sheetTarget(460, 176)
|
||||
readonly property var notificationCompactTarget: pillTarget(Math.ceil(Math.max(notificationCompactMinWidth, Math.min(notificationCompactMaxWidth, notificationContentWidth))), compactFaceHeight)
|
||||
@@ -348,16 +346,12 @@ QtObject {
|
||||
return launcherExpandedTarget;
|
||||
case "controlcenter":
|
||||
return controlCenterExpandedTarget;
|
||||
case "wallpaper":
|
||||
return wallpaperExpandedTarget;
|
||||
case "weather":
|
||||
return weatherExpandedTarget;
|
||||
case "notificationcenter":
|
||||
return notificationCenterExpandedTarget;
|
||||
case "media":
|
||||
return mediaExpandedTarget;
|
||||
}
|
||||
return homeExpandedTarget;
|
||||
return dashSheetTarget;
|
||||
}
|
||||
|
||||
readonly property var expandedTarget: expandedTargetFor(activeActivity)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.Mpris
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@@ -7,8 +7,7 @@ import qs.Common
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var mediaModel: null
|
||||
property string artUrl: mediaModel?.artUrl ?? ""
|
||||
property string artUrl: ""
|
||||
property real placeholderIconSize: Math.min(width, height) * 0.46
|
||||
property real cornerRadius: Math.min(width, height) * 0.22
|
||||
readonly property int artPixelSize: Math.max(1, Math.round(Math.max(width, height) * 2))
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property color accentColor: Theme.primary
|
||||
property bool playing: false
|
||||
property real animationScale: 1.0
|
||||
|
||||
readonly property bool onScreen: visible && enabled && (Window.window?.visible ?? false)
|
||||
readonly property bool available: CavaService.cavaAvailable && SettingsData.audioVisualizerEnabled
|
||||
readonly property bool blobActive: root.onScreen && root.playing && root.available
|
||||
|
||||
readonly property real blobBaseRadiusFactor: 0.43
|
||||
readonly property real blobAmplitudeFactor: 0.115
|
||||
readonly property real blobOvershoot: 1.15
|
||||
readonly property real blobEnergySensitivity: 1.15
|
||||
readonly property real cavaFullScale: 45
|
||||
readonly property real blobAttack: 0.75
|
||||
readonly property real blobRelease: 0.2
|
||||
readonly property real blobBeatBoost: 2.5
|
||||
readonly property real blobBeatKick: 4
|
||||
readonly property real blobOnsetThreshold: 1.4
|
||||
readonly property real blobSpringStiffness: 220
|
||||
readonly property real blobSpringDamping: 19
|
||||
readonly property real blobMorphSpeed: 0.05
|
||||
readonly property real blobMorphBoost: 1.7
|
||||
readonly property real blobSpinSpeed: 0.03
|
||||
readonly property var fluxWeights: [1.0, 1.0, 0.6, 0.6, 0.35, 0.35]
|
||||
|
||||
property var smoothedBands: [0, 0, 0, 0, 0, 0]
|
||||
property var slowBands: [0, 0, 0, 0, 0, 0]
|
||||
property var bandTargets: [0, 0, 0, 0, 0, 0]
|
||||
property var bandDisplay: [0, 0, 0, 0, 0, 0]
|
||||
property var prevLevels: [0, 0, 0, 0, 0, 0]
|
||||
property real fluxAvg: 0.02
|
||||
property real loudCtx: 0.1
|
||||
property int beatCooldown: 0
|
||||
property real energyTarget: 0
|
||||
property real energyPos: 0
|
||||
property real energyVel: 0
|
||||
|
||||
function updateBands() {
|
||||
const vals = CavaService.values;
|
||||
if (!vals || vals.length < 6)
|
||||
return;
|
||||
|
||||
const s = smoothedBands;
|
||||
const slow = slowBands;
|
||||
const out = bandTargets;
|
||||
const prev = prevLevels;
|
||||
const w = fluxWeights;
|
||||
let flux = 0;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const level = Math.min(Math.max(vals[i], 0), cavaFullScale) / cavaFullScale;
|
||||
flux += Math.max(0, level - prev[i]) * w[i];
|
||||
prev[i] = level;
|
||||
const alpha = level > s[i] ? blobAttack : blobRelease;
|
||||
s[i] += alpha * (level - s[i]);
|
||||
slow[i] += 0.05 * (level - slow[i]);
|
||||
const punch = Math.max(0, s[i] - slow[i]) * blobBeatBoost;
|
||||
out[i] = Math.min(1, (0.55 * s[i] + punch) * blobEnergySensitivity);
|
||||
}
|
||||
|
||||
const ratio = flux / Math.max(fluxAvg, 0.004);
|
||||
fluxAvg += 0.06 * (flux - fluxAvg);
|
||||
if (beatCooldown > 0) {
|
||||
beatCooldown--;
|
||||
} else if (ratio > blobOnsetThreshold && flux > 0.008) {
|
||||
energyVel += blobBeatKick * Math.min(2.5, ratio - 1);
|
||||
beatCooldown = 3;
|
||||
}
|
||||
|
||||
const loud = 0.7 * Math.max(prev[0], prev[1]) + 0.3 * Math.max(prev[2], prev[3]);
|
||||
loudCtx += 0.03 * (loud - loudCtx);
|
||||
const surge = Math.max(0, loud / Math.max(loudCtx, 0.05) - 1);
|
||||
energyTarget = Math.min(1, 0.5 * loud + 0.6 * Math.min(1, surge));
|
||||
}
|
||||
|
||||
function stepBlob(dt) {
|
||||
energyVel += (blobSpringStiffness * (energyTarget - energyPos) - blobSpringDamping * energyVel) * dt;
|
||||
energyPos = Math.max(0, Math.min(blobOvershoot, energyPos + energyVel * dt));
|
||||
blobEffect.energy = energyPos;
|
||||
|
||||
const d = bandDisplay;
|
||||
const t = bandTargets;
|
||||
const f = Math.min(1, dt * 14);
|
||||
for (let i = 0; i < 6; i++)
|
||||
d[i] += f * (t[i] - d[i]);
|
||||
blobEffect.bandsA = Qt.vector4d(d[0], d[1], d[2], d[3]);
|
||||
blobEffect.bandsB = Qt.vector2d(d[4], d[5]);
|
||||
|
||||
const speed = 1 + energyPos * blobMorphBoost;
|
||||
blobEffect.phase = (blobEffect.phase + dt * blobMorphSpeed * speed) % 1;
|
||||
blobEffect.spin = (blobEffect.spin + dt * blobSpinSpeed) % 6.28318530718;
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.blobActive
|
||||
sourceComponent: Component {
|
||||
Ref {
|
||||
service: CavaService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CavaService
|
||||
enabled: root.blobActive
|
||||
|
||||
function onValuesChanged() {
|
||||
root.updateBands();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
running: blobEffect.visible && root.onScreen
|
||||
interval: 33
|
||||
repeat: true
|
||||
onTriggered: root.stepBlob(0.033)
|
||||
}
|
||||
|
||||
ShaderEffect {
|
||||
id: blobEffect
|
||||
|
||||
readonly property real span: Math.min(root.width, root.height)
|
||||
|
||||
width: span * (root.blobBaseRadiusFactor + root.blobAmplitudeFactor * root.blobOvershoot) * 2 * root.animationScale + 4
|
||||
height: width
|
||||
anchors.centerIn: parent
|
||||
visible: root.blobActive || activation > 0.004
|
||||
|
||||
property real phase: 0
|
||||
property real spin: 0
|
||||
property real sizePx: width
|
||||
property real baseRadiusPx: span * root.blobBaseRadiusFactor * root.animationScale
|
||||
property real amplitudePx: span * root.blobAmplitudeFactor * root.animationScale
|
||||
property real activation: root.blobActive ? 1 : 0
|
||||
property real energy: 0
|
||||
property vector4d bandsA: Qt.vector4d(0, 0, 0, 0)
|
||||
property vector2d bandsB: Qt.vector2d(0, 0)
|
||||
property vector4d fillColor: Qt.vector4d(root.accentColor.r, root.accentColor.g, root.accentColor.b, root.accentColor.a)
|
||||
|
||||
Behavior on activation {
|
||||
NumberAnimation {
|
||||
duration: 550
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
fragmentShader: Qt.resolvedUrl("../../../Shaders/qsb/blob.frag.qsb")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user