mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 03:28:28 -04:00
qs/media: replace FBOs with ClippingRectangle and rewrite seekbar as a
shader
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -591,24 +591,11 @@ DankModal {
|
|||||||
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
|
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
|
||||||
border.width: isSelected ? 2 : 0
|
border.width: isSelected ? 2 : 0
|
||||||
|
|
||||||
Rectangle {
|
ClippingRectangle {
|
||||||
id: gridProgressMask
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
visible: false
|
color: "transparent"
|
||||||
layer.enabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: gridButtonRect.isHolding
|
visible: gridButtonRect.isHolding
|
||||||
layer.enabled: gridButtonRect.isHolding
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
maskEnabled: true
|
|
||||||
maskSource: gridProgressMask
|
|
||||||
maskSpreadAtMin: 1
|
|
||||||
maskThresholdMin: 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -729,24 +716,11 @@ DankModal {
|
|||||||
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
|
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
|
||||||
border.width: isSelected ? 2 : 0
|
border.width: isSelected ? 2 : 0
|
||||||
|
|
||||||
Rectangle {
|
ClippingRectangle {
|
||||||
id: listProgressMask
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
visible: false
|
color: "transparent"
|
||||||
layer.enabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: listButtonRect.isHolding
|
visible: listButtonRect.isHolding
|
||||||
layer.enabled: listButtonRect.isHolding
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
maskEnabled: true
|
|
||||||
maskSource: listProgressMask
|
|
||||||
maskSpreadAtMin: 1
|
|
||||||
maskThresholdMin: 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|||||||
@@ -240,8 +240,10 @@ DankPopout {
|
|||||||
Connections {
|
Connections {
|
||||||
target: root
|
target: root
|
||||||
function onShouldBeVisibleChanged() {
|
function onShouldBeVisibleChanged() {
|
||||||
if (root.shouldBeVisible)
|
if (!root.shouldBeVisible)
|
||||||
|
return;
|
||||||
mainContainer.forceActiveFocus();
|
mainContainer.forceActiveFocus();
|
||||||
|
tabBar.snapIndicator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ Item {
|
|||||||
property string section: ""
|
property string section: ""
|
||||||
property int barPosition: SettingsData.Position.Top
|
property int barPosition: SettingsData.Position.Top
|
||||||
|
|
||||||
|
readonly property color accent: MediaAccentService.accent
|
||||||
|
readonly property color onAccent: MediaAccentService.onAccent
|
||||||
|
readonly property color accentHover: MediaAccentService.accentHover
|
||||||
|
readonly property color accentPressed: MediaAccentService.accentPressed
|
||||||
|
|
||||||
signal showVolumeDropdown(point pos, var screen, bool rightEdge, var player, var players)
|
signal showVolumeDropdown(point pos, var screen, bool rightEdge, var player, var players)
|
||||||
signal showAudioDevicesDropdown(point pos, var screen, bool rightEdge)
|
signal showAudioDevicesDropdown(point pos, var screen, bool rightEdge)
|
||||||
signal showPlayersDropdown(point pos, var screen, bool rightEdge, var player, var players)
|
signal showPlayersDropdown(point pos, var screen, bool rightEdge, var player, var players)
|
||||||
@@ -298,8 +303,16 @@ Item {
|
|||||||
Component.onCompleted: syncArt()
|
Component.onCompleted: syncArt()
|
||||||
|
|
||||||
function syncArt() {
|
function syncArt() {
|
||||||
if (curArt === "" || layerA.art == curArt || layerB.art == curArt)
|
if (curArt === "")
|
||||||
return;
|
return;
|
||||||
|
const frontArt = _showA ? layerA.art : layerB.art;
|
||||||
|
const backArt = _showA ? layerB.art : layerA.art;
|
||||||
|
if (frontArt == curArt)
|
||||||
|
return;
|
||||||
|
if (backArt == curArt) {
|
||||||
|
_showA = !_showA;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (_showA)
|
if (_showA)
|
||||||
layerB.art = curArt;
|
layerB.art = curArt;
|
||||||
else
|
else
|
||||||
@@ -564,13 +577,13 @@ Item {
|
|||||||
height: 40
|
height: 40
|
||||||
radius: 20
|
radius: 20
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: shuffleArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.primaryHover, 0)
|
color: shuffleArea.containsMouse ? root.accentHover : Theme.withAlpha(root.accent, 0)
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "shuffle"
|
name: "shuffle"
|
||||||
size: 20
|
size: 20
|
||||||
color: activePlayer && activePlayer.shuffle ? Theme.primary : Theme.surfaceText
|
color: activePlayer && activePlayer.shuffle ? root.accent : Theme.surfaceText
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -626,13 +639,13 @@ Item {
|
|||||||
height: 50
|
height: 50
|
||||||
radius: 25
|
radius: 25
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: Theme.primary
|
color: root.accent
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
name: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
||||||
size: 28
|
size: 28
|
||||||
color: Theme.background
|
color: root.onAccent
|
||||||
weight: 500
|
weight: 500
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,7 +709,7 @@ Item {
|
|||||||
height: 40
|
height: 40
|
||||||
radius: 20
|
radius: 20
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: repeatArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.primaryHover, 0)
|
color: repeatArea.containsMouse ? root.accentHover : Theme.withAlpha(root.accent, 0)
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@@ -713,7 +726,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
size: 20
|
size: 20
|
||||||
color: activePlayer && activePlayer.loopState !== MprisLoopState.None ? Theme.primary : Theme.surfaceText
|
color: activePlayer && activePlayer.loopState !== MprisLoopState.None ? root.accent : Theme.surfaceText
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -752,7 +765,7 @@ Item {
|
|||||||
radius: 20
|
radius: 20
|
||||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||||
y: 185
|
y: 185
|
||||||
color: playerSelectorArea.containsMouse || playersExpanded ? Theme.primaryPressed : Theme.withAlpha(Theme.primaryPressed, 0)
|
color: playerSelectorArea.containsMouse || playersExpanded ? root.accentPressed : Theme.withAlpha(root.accentPressed, 0)
|
||||||
border.color: Theme.outlineStrong
|
border.color: Theme.outlineStrong
|
||||||
border.width: 1
|
border.width: 1
|
||||||
z: 100
|
z: 100
|
||||||
@@ -819,7 +832,7 @@ Item {
|
|||||||
radius: 20
|
radius: 20
|
||||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||||
y: 130
|
y: 130
|
||||||
color: volumeButtonArea.containsMouse && volumeAvailable || volumeExpanded ? Theme.primaryPressed : Theme.withAlpha(Theme.primaryPressed, 0)
|
color: volumeButtonArea.containsMouse && volumeAvailable || volumeExpanded ? root.accentPressed : Theme.withAlpha(root.accentPressed, 0)
|
||||||
border.color: volumeAvailable ? Theme.outlineStrong : Theme.outlineMedium
|
border.color: volumeAvailable ? Theme.outlineStrong : Theme.outlineMedium
|
||||||
border.width: 1
|
border.width: 1
|
||||||
z: 101
|
z: 101
|
||||||
@@ -831,7 +844,7 @@ Item {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: getVolumeIcon()
|
name: getVolumeIcon()
|
||||||
size: 18
|
size: 18
|
||||||
color: volumeAvailable && currentVolume > 0 ? Theme.primary : Theme.withAlpha(Theme.surfaceText, volumeAvailable ? 1.0 : 0.5)
|
color: volumeAvailable && currentVolume > 0 ? root.accent : Theme.withAlpha(Theme.surfaceText, volumeAvailable ? 1.0 : 0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -882,7 +895,7 @@ Item {
|
|||||||
radius: 20
|
radius: 20
|
||||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||||
y: 240
|
y: 240
|
||||||
color: audioDevicesArea.containsMouse || devicesExpanded ? Theme.primaryPressed : Theme.withAlpha(Theme.primaryPressed, 0)
|
color: audioDevicesArea.containsMouse || devicesExpanded ? root.accentPressed : Theme.withAlpha(root.accentPressed, 0)
|
||||||
border.color: Theme.outlineStrong
|
border.color: Theme.outlineStrong
|
||||||
border.width: 1
|
border.width: 1
|
||||||
z: 100
|
z: 100
|
||||||
|
|||||||
@@ -154,13 +154,13 @@ Card {
|
|||||||
width: 32
|
width: 32
|
||||||
height: 32
|
height: 32
|
||||||
radius: 16
|
radius: 16
|
||||||
color: Theme.primary
|
color: MediaAccentService.accent
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
name: activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
|
||||||
size: 16
|
size: 16
|
||||||
color: Theme.background
|
color: MediaAccentService.onAccent
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -567,24 +567,11 @@ Rectangle {
|
|||||||
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
|
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
|
||||||
border.width: isSelected ? 2 : 0
|
border.width: isSelected ? 2 : 0
|
||||||
|
|
||||||
Rectangle {
|
ClippingRectangle {
|
||||||
id: gridProgressMask
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
visible: false
|
color: "transparent"
|
||||||
layer.enabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: gridButtonRect.isHolding
|
visible: gridButtonRect.isHolding
|
||||||
layer.enabled: gridButtonRect.isHolding
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
maskEnabled: true
|
|
||||||
maskSource: gridProgressMask
|
|
||||||
maskSpreadAtMin: 1
|
|
||||||
maskThresholdMin: 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -700,24 +687,11 @@ Rectangle {
|
|||||||
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
|
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
|
||||||
border.width: isSelected ? 2 : 0
|
border.width: isSelected ? 2 : 0
|
||||||
|
|
||||||
Rectangle {
|
ClippingRectangle {
|
||||||
id: listProgressMask
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
visible: false
|
color: "transparent"
|
||||||
layer.enabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: listButtonRect.isHolding
|
visible: listButtonRect.isHolding
|
||||||
layer.enabled: listButtonRect.isHolding
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
maskEnabled: true
|
|
||||||
maskSource: listProgressMask
|
|
||||||
maskSpreadAtMin: 1
|
|
||||||
maskThresholdMin: 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import qs.Common
|
|||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
import Quickshell.Services.Mpris
|
import Quickshell.Services.Mpris
|
||||||
|
import Quickshell.Widgets
|
||||||
|
|
||||||
DankOSD {
|
DankOSD {
|
||||||
id: root
|
id: root
|
||||||
@@ -185,10 +186,11 @@ DankOSD {
|
|||||||
visible: false
|
visible: false
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
ClippingRectangle {
|
||||||
id: blurredBg
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: false
|
radius: Theme.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
opacity: 0.7
|
||||||
|
|
||||||
MultiEffect {
|
MultiEffect {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@@ -203,24 +205,6 @@ DankOSD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: bgMask
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
visible: false
|
|
||||||
layer.enabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
MultiEffect {
|
|
||||||
anchors.fill: parent
|
|
||||||
source: blurredBg
|
|
||||||
maskEnabled: true
|
|
||||||
maskSource: bgMask
|
|
||||||
maskThresholdMin: 0.5
|
|
||||||
maskSpreadAtMin: 1.0
|
|
||||||
opacity: 0.7
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import QtCore
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.FileBrowser
|
import qs.Modals.FileBrowser
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -520,9 +520,14 @@ Item {
|
|||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceVariant
|
color: Theme.surfaceVariant
|
||||||
|
|
||||||
Image {
|
ClippingRectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 1
|
anchors.margins: 1
|
||||||
|
radius: Theme.cornerRadius - 1
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
source: {
|
source: {
|
||||||
var wp = Theme.wallpaperPath;
|
var wp = Theme.wallpaperPath;
|
||||||
if (!wp || wp === "" || wp.startsWith("#"))
|
if (!wp || wp === "" || wp.startsWith("#"))
|
||||||
@@ -536,12 +541,6 @@ Item {
|
|||||||
sourceSize.width: 120
|
sourceSize.width: 120
|
||||||
sourceSize.height: 120
|
sourceSize.height: 120
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
maskEnabled: true
|
|
||||||
maskSource: autoWallpaperMask
|
|
||||||
maskThresholdMin: 0.5
|
|
||||||
maskSpreadAtMin: 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,16 +552,6 @@ Item {
|
|||||||
visible: Theme.wallpaperPath && Theme.wallpaperPath.startsWith("#")
|
visible: Theme.wallpaperPath && Theme.wallpaperPath.startsWith("#")
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: autoWallpaperMask
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 1
|
|
||||||
radius: Theme.cornerRadius - 1
|
|
||||||
color: "black"
|
|
||||||
visible: false
|
|
||||||
layer.enabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") ? "error" : "palette"
|
name: (ToastService.wallpaperErrorStatus === "error" || ToastService.wallpaperErrorStatus === "matugen_missing") ? "error" : "palette"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.FileBrowser
|
import qs.Modals.FileBrowser
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -75,9 +75,14 @@ Item {
|
|||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceVariant
|
color: Theme.surfaceVariant
|
||||||
|
|
||||||
Image {
|
ClippingRectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 1
|
anchors.margins: 1
|
||||||
|
radius: Theme.cornerRadius - 1
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
source: {
|
source: {
|
||||||
var wp = root.currentWallpaper;
|
var wp = root.currentWallpaper;
|
||||||
if (wp === "" || wp.startsWith("#"))
|
if (wp === "" || wp.startsWith("#"))
|
||||||
@@ -91,12 +96,6 @@ Item {
|
|||||||
sourceSize.width: 160
|
sourceSize.width: 160
|
||||||
sourceSize.height: 160
|
sourceSize.height: 160
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
maskEnabled: true
|
|
||||||
maskSource: wallpaperMask
|
|
||||||
maskThresholdMin: 0.5
|
|
||||||
maskSpreadAtMin: 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,16 +107,6 @@ Item {
|
|||||||
visible: root.currentWallpaper !== "" && root.currentWallpaper.startsWith("#")
|
visible: root.currentWallpaper !== "" && root.currentWallpaper.startsWith("#")
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: wallpaperMask
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 1
|
|
||||||
radius: Theme.cornerRadius - 1
|
|
||||||
color: "black"
|
|
||||||
visible: false
|
|
||||||
layer.enabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "image"
|
name: "image"
|
||||||
@@ -421,9 +410,14 @@ Item {
|
|||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceVariant
|
color: Theme.surfaceVariant
|
||||||
|
|
||||||
Image {
|
ClippingRectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 1
|
anchors.margins: 1
|
||||||
|
radius: Theme.cornerRadius - 1
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
source: {
|
source: {
|
||||||
var wp = SessionData.wallpaperPathLight;
|
var wp = SessionData.wallpaperPathLight;
|
||||||
if (wp === "" || wp.startsWith("#"))
|
if (wp === "" || wp.startsWith("#"))
|
||||||
@@ -440,12 +434,6 @@ Item {
|
|||||||
sourceSize.width: 160
|
sourceSize.width: 160
|
||||||
sourceSize.height: 160
|
sourceSize.height: 160
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
maskEnabled: true
|
|
||||||
maskSource: lightMask
|
|
||||||
maskThresholdMin: 0.5
|
|
||||||
maskSpreadAtMin: 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,16 +451,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: lightMask
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 1
|
|
||||||
radius: Theme.cornerRadius - 1
|
|
||||||
color: "black"
|
|
||||||
visible: false
|
|
||||||
layer.enabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "light_mode"
|
name: "light_mode"
|
||||||
@@ -611,9 +589,14 @@ Item {
|
|||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceVariant
|
color: Theme.surfaceVariant
|
||||||
|
|
||||||
Image {
|
ClippingRectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 1
|
anchors.margins: 1
|
||||||
|
radius: Theme.cornerRadius - 1
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
source: {
|
source: {
|
||||||
var wp = SessionData.wallpaperPathDark;
|
var wp = SessionData.wallpaperPathDark;
|
||||||
if (wp === "" || wp.startsWith("#"))
|
if (wp === "" || wp.startsWith("#"))
|
||||||
@@ -630,12 +613,6 @@ Item {
|
|||||||
sourceSize.width: 160
|
sourceSize.width: 160
|
||||||
sourceSize.height: 160
|
sourceSize.height: 160
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
maskEnabled: true
|
|
||||||
maskSource: darkMask
|
|
||||||
maskThresholdMin: 0.5
|
|
||||||
maskSpreadAtMin: 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,16 +630,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: darkMask
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 1
|
|
||||||
radius: Theme.cornerRadius - 1
|
|
||||||
color: "black"
|
|
||||||
visible: false
|
|
||||||
layer.enabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "dark_mode"
|
name: "dark_mode"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Effects
|
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -58,23 +58,6 @@ Item {
|
|||||||
visible: intersectsViewport
|
visible: intersectsViewport
|
||||||
opacity: (monitorObj?.id ?? -1) == widgetMonitorId ? 1 : 0.4
|
opacity: (monitorObj?.id ?? -1) == widgetMonitorId ? 1 : 0.4
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: maskRect
|
|
||||||
width: root.width
|
|
||||||
height: root.height
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
visible: false
|
|
||||||
layer.enabled: true
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
maskEnabled: true
|
|
||||||
maskSource: maskRect
|
|
||||||
maskSpreadAtMin: 1
|
|
||||||
maskThresholdMin: 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on x {
|
Behavior on x {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
duration: Theme.variantDuration(Theme.expressiveDurations.expressiveDefaultSpatial, overviewOpen)
|
||||||
@@ -104,6 +87,11 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClippingRectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
ScreencopyView {
|
ScreencopyView {
|
||||||
id: windowPreview
|
id: windowPreview
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -113,9 +101,7 @@ Item {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: pressed ? Theme.withAlpha(Theme.surfaceContainerHigh, 0.5) :
|
color: pressed ? Theme.withAlpha(Theme.surfaceContainerHigh, 0.5) : hovered ? Theme.withAlpha(Theme.surfaceVariant, 0.3) : Theme.withAlpha(Theme.surfaceContainer, 0.1)
|
||||||
hovered ? Theme.withAlpha(Theme.surfaceVariant, 0.3) :
|
|
||||||
Theme.withAlpha(Theme.surfaceContainer, 0.1)
|
|
||||||
border.color: Theme.withAlpha(Theme.outline, 0.3)
|
border.color: Theme.withAlpha(Theme.outline, 0.3)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
}
|
}
|
||||||
@@ -129,7 +115,7 @@ Item {
|
|||||||
Image {
|
Image {
|
||||||
id: windowIcon
|
id: windowIcon
|
||||||
property var iconSize: {
|
property var iconSize: {
|
||||||
return Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / (root.monitorData?.scale ?? 1)
|
return Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / (root.monitorData?.scale ?? 1);
|
||||||
}
|
}
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
source: root.iconPath
|
source: root.iconPath
|
||||||
@@ -155,3 +141,4 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
// Accent color extracted from the current track's album art via ColorQuantizer,
|
||||||
|
// falling back to Theme.primary when no usable accent is available.
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property bool hasAccent: _accent !== null
|
||||||
|
readonly property color accent: _accent !== null ? _accent : Theme.primary
|
||||||
|
|
||||||
|
readonly property color onAccent: {
|
||||||
|
const c = accent;
|
||||||
|
const lum = 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b;
|
||||||
|
return lum > 0.6 ? Qt.rgba(0, 0, 0, 1) : Qt.rgba(1, 1, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property color accentHover: Theme.withAlpha(accent, 0.12)
|
||||||
|
readonly property color accentPressed: Theme.withAlpha(accent, Theme.transparentBlurLayers ? 0.24 : 0.16)
|
||||||
|
|
||||||
|
// Plain-named alias: underscore-prefixed props with onChanged handlers crash config load.
|
||||||
|
readonly property string artUrl: TrackArtService.resolvedArtUrl
|
||||||
|
onArtUrlChanged: {
|
||||||
|
if (artUrl === "")
|
||||||
|
_accent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
property var _accent: null
|
||||||
|
|
||||||
|
ColorQuantizer {
|
||||||
|
id: quantizer
|
||||||
|
source: root.artUrl
|
||||||
|
depth: 4
|
||||||
|
rescaleSize: 64
|
||||||
|
onColorsChanged: root._accent = root._pickAccent(colors)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _pickAccent(colors) {
|
||||||
|
if (!colors || colors.length === 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let best = null;
|
||||||
|
let bestScore = -1;
|
||||||
|
for (let i = 0; i < colors.length; i++) {
|
||||||
|
const c = colors[i];
|
||||||
|
const s = c.hsvSaturation;
|
||||||
|
const v = c.hsvValue;
|
||||||
|
if (v < 0.22 || v > 0.96 || s < 0.22)
|
||||||
|
continue;
|
||||||
|
const score = s * (1 - Math.abs(v - 0.68));
|
||||||
|
if (score > bestScore) {
|
||||||
|
bestScore = score;
|
||||||
|
best = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!best)
|
||||||
|
return null;
|
||||||
|
return _normalize(best);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _normalize(c) {
|
||||||
|
const hue = c.hsvHue < 0 ? 0 : c.hsvHue;
|
||||||
|
const s = Math.min(1, c.hsvSaturation * 1.05);
|
||||||
|
const v = Math.max(c.hsvValue, 0.62);
|
||||||
|
return Qt.hsva(hue, s, v, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 qt_TexCoord0;
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
layout(std140, binding = 0) uniform buf {
|
||||||
|
mat4 qt_Matrix;
|
||||||
|
float qt_Opacity;
|
||||||
|
float widthPx;
|
||||||
|
float heightPx;
|
||||||
|
float value;
|
||||||
|
float actualValue;
|
||||||
|
float phase;
|
||||||
|
float ampPx;
|
||||||
|
float wavelengthPx;
|
||||||
|
float lineWidthPx;
|
||||||
|
float showActual;
|
||||||
|
vec4 fillColor;
|
||||||
|
vec4 trackColor;
|
||||||
|
vec4 playheadColor;
|
||||||
|
vec4 actualColor;
|
||||||
|
} ubuf;
|
||||||
|
|
||||||
|
const float TAU = 6.28318530718;
|
||||||
|
const float AA = 0.75; // pixel-space antialias band
|
||||||
|
|
||||||
|
// Signed distance to a rounded box centered at the origin.
|
||||||
|
float sdRoundBar(vec2 p, vec2 halfSize, float r) {
|
||||||
|
vec2 q = abs(p) - halfSize + vec2(r);
|
||||||
|
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Composite a straight-alpha color over a premultiplied accumulator.
|
||||||
|
vec4 blendOver(vec4 dst, vec3 rgb, float a) {
|
||||||
|
return vec4(rgb * a + dst.rgb * (1.0 - a), a + dst.a * (1.0 - a));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float w = ubuf.widthPx;
|
||||||
|
float h = ubuf.heightPx;
|
||||||
|
vec2 px = vec2(qt_TexCoord0.x * w, qt_TexCoord0.y * h);
|
||||||
|
|
||||||
|
float mid = h * 0.5;
|
||||||
|
float halfW = ubuf.lineWidthPx * 0.5;
|
||||||
|
float k = TAU / max(ubuf.wavelengthPx, 1e-3);
|
||||||
|
|
||||||
|
float playX = clamp(ubuf.value, 0.0, 1.0) * w;
|
||||||
|
float actualX = clamp(ubuf.actualValue, 0.0, 1.0) * w;
|
||||||
|
bool seeking = ubuf.showActual > 0.5;
|
||||||
|
|
||||||
|
float loX = min(playX, actualX);
|
||||||
|
float hiX = max(playX, actualX);
|
||||||
|
float fillEnd = seeking ? loX : playX; // filled progress ends here
|
||||||
|
float actStart = seeking ? loX : playX; // seek-preview segment
|
||||||
|
float actEnd = seeking ? hiX : playX;
|
||||||
|
float trackStart = seeking ? hiX : playX; // unplayed remainder
|
||||||
|
|
||||||
|
// Perpendicular distance to the animated sine stroke.
|
||||||
|
float ang = k * px.x + ubuf.phase;
|
||||||
|
float wy = mid + ubuf.ampPx * sin(ang);
|
||||||
|
float slope = ubuf.ampPx * k * cos(ang);
|
||||||
|
float dWave = abs(px.y - wy) / sqrt(1.0 + slope * slope);
|
||||||
|
float aaW = AA;
|
||||||
|
float waveStroke = 1.0 - smoothstep(halfW - aaW, halfW + aaW, dWave);
|
||||||
|
|
||||||
|
// Straight remainder line.
|
||||||
|
float dLine = abs(px.y - mid);
|
||||||
|
float aaL = AA;
|
||||||
|
float lineStroke = 1.0 - smoothstep(halfW - aaL, halfW + aaL, dLine);
|
||||||
|
|
||||||
|
vec4 col = vec4(0.0);
|
||||||
|
|
||||||
|
// 1. Track (unplayed remainder), to the right of the progress head.
|
||||||
|
{
|
||||||
|
float m = lineStroke * step(trackStart, px.x);
|
||||||
|
col = blendOver(col, ubuf.trackColor.rgb, ubuf.trackColor.a * m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Seek-preview segment (only while seeking).
|
||||||
|
if (seeking) {
|
||||||
|
float m = waveStroke * step(actStart, px.x) * step(px.x, actEnd);
|
||||||
|
col = blendOver(col, ubuf.actualColor.rgb, ubuf.actualColor.a * m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Filled progress wave.
|
||||||
|
{
|
||||||
|
float m = waveStroke * step(halfW, px.x) * step(px.x, fillEnd);
|
||||||
|
// Rounded start cap.
|
||||||
|
float capS = length(px - vec2(halfW, mid + ubuf.ampPx * sin(k * halfW + ubuf.phase))) - halfW;
|
||||||
|
float capM = 1.0 - smoothstep(-aaW, aaW, capS);
|
||||||
|
m = max(m, capM * step(halfW - 1.0, px.x));
|
||||||
|
col = blendOver(col, ubuf.fillColor.rgb, ubuf.fillColor.a * m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Actual-position marker (only while seeking).
|
||||||
|
if (seeking) {
|
||||||
|
float amH = max(ubuf.lineWidthPx + 4.0, 10.0);
|
||||||
|
float d = sdRoundBar(px - vec2(actualX, mid), vec2(1.0, amH * 0.5), 1.0);
|
||||||
|
float aa = AA;
|
||||||
|
float m = 1.0 - smoothstep(-aa, aa, d);
|
||||||
|
col = blendOver(col, ubuf.actualColor.rgb, ubuf.actualColor.a * m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Playhead pill (on top).
|
||||||
|
{
|
||||||
|
float phW = 3.5;
|
||||||
|
float phH = max(ubuf.lineWidthPx + 12.0, 16.0);
|
||||||
|
float d = sdRoundBar(px - vec2(playX, mid), vec2(phW * 0.5, phH * 0.5), phW * 0.5);
|
||||||
|
float aa = AA;
|
||||||
|
float m = 1.0 - smoothstep(-aa, aa, d);
|
||||||
|
col = blendOver(col, ubuf.playheadColor.rgb, ubuf.playheadColor.a * m);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragColor = col * ubuf.qt_Opacity;
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -152,7 +152,8 @@ Item {
|
|||||||
property real energy: 0
|
property real energy: 0
|
||||||
property vector4d bandsA: Qt.vector4d(0, 0, 0, 0)
|
property vector4d bandsA: Qt.vector4d(0, 0, 0, 0)
|
||||||
property vector2d bandsB: Qt.vector2d(0, 0)
|
property vector2d bandsB: Qt.vector2d(0, 0)
|
||||||
property vector4d fillColor: Qt.vector4d(Theme.primary.r, Theme.primary.g, Theme.primary.b, Theme.primary.a)
|
readonly property color accentColor: MediaAccentService.accent
|
||||||
|
property vector4d fillColor: Qt.vector4d(accentColor.r, accentColor.g, accentColor.b, accentColor.a)
|
||||||
|
|
||||||
Behavior on activation {
|
Behavior on activation {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
@@ -173,7 +174,7 @@ Item {
|
|||||||
|
|
||||||
imageSource: artUrl || lastValidArtUrl || ""
|
imageSource: artUrl || lastValidArtUrl || ""
|
||||||
fallbackIcon: "album"
|
fallbackIcon: "album"
|
||||||
border.color: Theme.primary
|
border.color: MediaAccentService.accent
|
||||||
border.width: 2
|
border.width: 2
|
||||||
|
|
||||||
onImageSourceChanged: {
|
onImageSourceChanged: {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Window
|
import QtQuick.Window
|
||||||
import QtQuick.Effects
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
@@ -65,17 +65,22 @@ Rectangle {
|
|||||||
border.color: "transparent"
|
border.color: "transparent"
|
||||||
border.width: 0
|
border.width: 0
|
||||||
|
|
||||||
|
ClippingRectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 2
|
||||||
|
radius: Math.min(width, height) / 2
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
// Probes as AnimatedImage to read frameCount; retires once staticImage is ready.
|
// Probes as AnimatedImage to read frameCount; retires once staticImage is ready.
|
||||||
AnimatedImage {
|
AnimatedImage {
|
||||||
id: probe
|
id: probe
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 2
|
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
smooth: true
|
smooth: true
|
||||||
mipmap: true
|
mipmap: true
|
||||||
cache: root.cacheImages
|
cache: root.cacheImages
|
||||||
visible: false
|
visible: root.activeImage === probe && probe.status === Image.Ready && root.imageSource !== ""
|
||||||
source: root.shouldProbe && (root.isAnimated || staticImage.status !== Image.Ready) ? root.imageSource : ""
|
source: root.shouldProbe && (root.isAnimated || staticImage.status !== Image.Ready) ? root.imageSource : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,13 +88,12 @@ Rectangle {
|
|||||||
Image {
|
Image {
|
||||||
id: staticImage
|
id: staticImage
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 2
|
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
smooth: true
|
smooth: true
|
||||||
mipmap: true
|
mipmap: true
|
||||||
cache: root.cacheImages
|
cache: root.cacheImages
|
||||||
visible: false
|
visible: root.activeImage === staticImage && staticImage.status === Image.Ready && root.imageSource !== ""
|
||||||
sourceSize.width: Math.max(width * 2, 128)
|
sourceSize.width: Math.max(width * 2, 128)
|
||||||
sourceSize.height: Math.max(height * 2, 128)
|
sourceSize.height: Math.max(height * 2, 128)
|
||||||
source: {
|
source: {
|
||||||
@@ -100,33 +104,6 @@ Rectangle {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiEffect {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 2
|
|
||||||
source: root.activeImage
|
|
||||||
maskEnabled: true
|
|
||||||
maskSource: circularMask
|
|
||||||
visible: root.activeImage.status === Image.Ready && root.imageSource !== ""
|
|
||||||
maskThresholdMin: 0.5
|
|
||||||
maskSpreadAtMin: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: circularMask
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width - 4
|
|
||||||
height: parent.height - 4
|
|
||||||
layer.enabled: true
|
|
||||||
layer.smooth: true
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: width / 2
|
|
||||||
color: "black"
|
|
||||||
antialiasing: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AppIconRenderer {
|
AppIconRenderer {
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ Item {
|
|||||||
value: root.value
|
value: root.value
|
||||||
actualValue: root.playerValue
|
actualValue: root.playerValue
|
||||||
showActualPlaybackState: root.isSeeking
|
showActualPlaybackState: root.isSeeking
|
||||||
|
fillColor: MediaAccentService.accent
|
||||||
|
playheadColor: MediaAccentService.accent
|
||||||
actualProgressColor: Theme.onSurface_38
|
actualProgressColor: Theme.onSurface_38
|
||||||
isPlaying: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing
|
isPlaying: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing
|
||||||
|
|
||||||
@@ -179,8 +181,8 @@ Item {
|
|||||||
Item {
|
Item {
|
||||||
property real lineWidth: 3
|
property real lineWidth: 3
|
||||||
property color trackColor: Theme.withAlpha(Theme.surfaceVariant, 0.40)
|
property color trackColor: Theme.withAlpha(Theme.surfaceVariant, 0.40)
|
||||||
property color fillColor: Theme.primary
|
property color fillColor: MediaAccentService.accent
|
||||||
property color playheadColor: Theme.primary
|
property color playheadColor: MediaAccentService.accent
|
||||||
property color actualProgressColor: Theme.onSurface_38
|
property color actualProgressColor: Theme.onSurface_38
|
||||||
readonly property real midY: height / 2
|
readonly property real midY: height / 2
|
||||||
|
|
||||||
|
|||||||
@@ -256,6 +256,11 @@ FocusScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function snapIndicator() {
|
||||||
|
indicator.initialSetupComplete = false;
|
||||||
|
updateIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
onCurrentIndexChanged: {
|
onCurrentIndexChanged: {
|
||||||
Qt.callLater(updateIndicator);
|
Qt.callLater(updateIndicator);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Shapes
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
|
||||||
|
// Wave progress indicator: track, animated fill, seek preview and playhead are
|
||||||
|
// all drawn in a single fragment shader (Shaders/frag/wave_progress.frag).
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
@@ -19,19 +20,6 @@ Item {
|
|||||||
property color playheadColor: Theme.primary
|
property color playheadColor: Theme.primary
|
||||||
property color actualProgressColor: Theme.onSurface_38
|
property color actualProgressColor: Theme.onSurface_38
|
||||||
|
|
||||||
property real dpr: (root.window ? root.window.devicePixelRatio : 1)
|
|
||||||
function snap(v) {
|
|
||||||
return Math.round(v * dpr) / dpr;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real playX: snap(root.width * root.value)
|
|
||||||
readonly property real actualX: snap(root.width * root.actualValue)
|
|
||||||
readonly property real midY: snap(height / 2)
|
|
||||||
readonly property bool previewAhead: root.showActualPlaybackState && root.value > root.actualValue
|
|
||||||
readonly property bool previewBehind: root.showActualPlaybackState && root.value < root.actualValue
|
|
||||||
readonly property real previewGapStartX: Math.min(root.playX, root.actualX)
|
|
||||||
readonly property real previewGapEndX: Math.max(root.playX, root.actualX)
|
|
||||||
|
|
||||||
Behavior on currentAmp {
|
Behavior on currentAmp {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: 300
|
duration: 300
|
||||||
@@ -39,236 +27,34 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onIsPlayingChanged: currentAmp = isPlaying ? amp : 0
|
onIsPlayingChanged: currentAmp = isPlaying ? amp : 0
|
||||||
|
Component.onCompleted: currentAmp = isPlaying ? amp : 0
|
||||||
|
|
||||||
Shape {
|
ShaderEffect {
|
||||||
id: flatTrack
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
antialiasing: true
|
blending: true
|
||||||
preferredRendererType: Shape.CurveRenderer
|
|
||||||
layer.enabled: true
|
|
||||||
|
|
||||||
ShapePath {
|
readonly property real widthPx: width
|
||||||
strokeColor: root.trackColor
|
readonly property real heightPx: height
|
||||||
strokeWidth: snap(root.lineWidth)
|
readonly property real value: root.value
|
||||||
capStyle: ShapePath.RoundCap
|
readonly property real actualValue: root.actualValue
|
||||||
joinStyle: ShapePath.RoundJoin
|
readonly property real phase: root.phase
|
||||||
fillColor: "transparent"
|
readonly property real ampPx: root.currentAmp
|
||||||
PathMove {
|
readonly property real wavelengthPx: root.wavelength
|
||||||
id: flatStart
|
readonly property real lineWidthPx: root.lineWidth
|
||||||
x: Math.min(root.width, snap(root.playX + playhead.width / 2))
|
readonly property real showActual: root.showActualPlaybackState ? 1.0 : 0.0
|
||||||
y: root.midY
|
readonly property color fillColor: root.fillColor
|
||||||
}
|
readonly property color trackColor: root.trackColor
|
||||||
PathLine {
|
readonly property color playheadColor: root.playheadColor
|
||||||
id: flatEnd
|
readonly property color actualColor: root.actualProgressColor
|
||||||
x: root.width
|
|
||||||
y: root.midY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
fragmentShader: Qt.resolvedUrl("../Shaders/qsb/wave_progress.frag.qsb")
|
||||||
id: waveClip
|
|
||||||
anchors.fill: parent
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
readonly property real startX: snap(root.lineWidth / 2)
|
|
||||||
readonly property real aaBias: (0.25 / root.dpr)
|
|
||||||
readonly property real endX: root.previewAhead ? Math.max(startX, Math.min(root.actualX - aaBias, width)) : Math.max(startX, Math.min(root.playX - startX - aaBias, width))
|
|
||||||
readonly property real gapStartX: root.previewAhead ? Math.max(startX, Math.min(root.actualX + aaBias, width)) : Math.max(startX, Math.min(root.playX + playhead.width / 2, width))
|
|
||||||
readonly property real gapEndX: root.previewAhead ? Math.max(gapStartX, Math.min(root.playX - playhead.width / 2 - aaBias, width)) : Math.max(gapStartX, Math.min(root.actualX - aaBias, width))
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: mask
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
x: 0
|
|
||||||
width: waveClip.endX
|
|
||||||
color: "transparent"
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Shape {
|
|
||||||
id: waveShape
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
width: parent.width + 4 * root.wavelength
|
|
||||||
antialiasing: true
|
|
||||||
preferredRendererType: Shape.CurveRenderer
|
|
||||||
x: waveOffsetX
|
|
||||||
|
|
||||||
ShapePath {
|
|
||||||
id: wavePath
|
|
||||||
strokeColor: root.fillColor
|
|
||||||
strokeWidth: snap(root.lineWidth)
|
|
||||||
capStyle: ShapePath.RoundCap
|
|
||||||
joinStyle: ShapePath.RoundJoin
|
|
||||||
fillColor: "transparent"
|
|
||||||
PathSvg {
|
|
||||||
id: waveSvg
|
|
||||||
path: ""
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: actualMask
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
x: waveClip.gapStartX
|
|
||||||
width: Math.max(0, waveClip.gapEndX - waveClip.gapStartX)
|
|
||||||
color: "transparent"
|
|
||||||
clip: true
|
|
||||||
visible: (root.previewBehind || root.previewAhead) && width > 0
|
|
||||||
|
|
||||||
Shape {
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
width: root.width + 4 * root.wavelength
|
|
||||||
antialiasing: true
|
|
||||||
preferredRendererType: Shape.CurveRenderer
|
|
||||||
x: waveOffsetX
|
|
||||||
|
|
||||||
ShapePath {
|
|
||||||
strokeColor: root.actualProgressColor
|
|
||||||
strokeWidth: snap(root.lineWidth)
|
|
||||||
capStyle: ShapePath.RoundCap
|
|
||||||
joinStyle: ShapePath.RoundJoin
|
|
||||||
fillColor: "transparent"
|
|
||||||
PathSvg {
|
|
||||||
path: waveSvg.path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: startCap
|
|
||||||
width: snap(root.lineWidth)
|
|
||||||
height: snap(root.lineWidth)
|
|
||||||
radius: width / 2
|
|
||||||
color: root.fillColor
|
|
||||||
x: waveClip.startX - width / 2
|
|
||||||
y: waveY(waveClip.startX) - height / 2
|
|
||||||
visible: waveClip.endX > waveClip.startX
|
|
||||||
z: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: endCap
|
|
||||||
width: snap(root.lineWidth)
|
|
||||||
height: snap(root.lineWidth)
|
|
||||||
radius: width / 2
|
|
||||||
color: root.fillColor
|
|
||||||
x: waveClip.endX - width / 2
|
|
||||||
y: waveY(waveClip.endX) - height / 2
|
|
||||||
visible: waveClip.endX > waveClip.startX
|
|
||||||
z: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: actualEndCap
|
|
||||||
width: snap(root.lineWidth)
|
|
||||||
height: snap(root.lineWidth)
|
|
||||||
radius: width / 2
|
|
||||||
color: root.actualProgressColor
|
|
||||||
x: waveClip.gapEndX - width / 2
|
|
||||||
y: waveY(waveClip.gapEndX) - height / 2
|
|
||||||
visible: (root.previewBehind || root.previewAhead) && actualMask.width > 0
|
|
||||||
z: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: actualMarker
|
|
||||||
width: 2
|
|
||||||
height: Math.max(root.lineWidth + 4, 10)
|
|
||||||
radius: width / 2
|
|
||||||
color: root.actualProgressColor
|
|
||||||
x: root.actualX - width / 2
|
|
||||||
y: root.midY - height / 2
|
|
||||||
visible: root.showActualPlaybackState
|
|
||||||
z: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: playhead
|
|
||||||
width: 3.5
|
|
||||||
height: Math.max(root.lineWidth + 12, 16)
|
|
||||||
radius: width / 2
|
|
||||||
color: root.playheadColor
|
|
||||||
x: root.playX - width / 2
|
|
||||||
y: root.midY - height / 2
|
|
||||||
z: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
property real k: (2 * Math.PI) / Math.max(1e-6, wavelength)
|
|
||||||
function wrapMod(a, m) {
|
|
||||||
let r = a % m;
|
|
||||||
return r < 0 ? r + m : r;
|
|
||||||
}
|
|
||||||
function waveY(x, amplitude = root.currentAmp, phaseOffset = root.phase) {
|
|
||||||
return root.midY + amplitude * Math.sin((x / root.wavelength) * 2 * Math.PI + phaseOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property real waveOffsetX: -wrapMod(phase / k, wavelength)
|
|
||||||
|
|
||||||
FrameAnimation {
|
FrameAnimation {
|
||||||
running: root.visible && (root.isPlaying || root.currentAmp > 0)
|
running: root.visible && (root.isPlaying || root.currentAmp > 0)
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (root.isPlaying)
|
if (root.isPlaying)
|
||||||
root.phase += 0.03 * frameTime * 60;
|
root.phase += 0.03 * frameTime * 60;
|
||||||
startCap.y = waveY(waveClip.startX) - startCap.height / 2;
|
|
||||||
endCap.y = waveY(waveClip.endX) - endCap.height / 2;
|
|
||||||
actualEndCap.y = waveY(waveClip.gapEndX) - actualEndCap.height / 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildStaticWave() {
|
|
||||||
const start = waveClip.startX - 2 * root.wavelength;
|
|
||||||
const end = width + 2 * root.wavelength;
|
|
||||||
if (end <= start) {
|
|
||||||
waveSvg.path = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const kLocal = k;
|
|
||||||
const halfPeriod = root.wavelength / 2;
|
|
||||||
function y0(x) {
|
|
||||||
return root.midY + root.currentAmp * Math.sin(kLocal * x);
|
|
||||||
}
|
|
||||||
function dy0(x) {
|
|
||||||
return root.currentAmp * Math.cos(kLocal * x) * kLocal;
|
|
||||||
}
|
|
||||||
|
|
||||||
let x0 = start;
|
|
||||||
let d = `M ${x0} ${y0(x0)}`;
|
|
||||||
while (x0 < end) {
|
|
||||||
const x1 = Math.min(x0 + halfPeriod, end);
|
|
||||||
const dx = x1 - x0;
|
|
||||||
const yA = y0(x0), yB = y0(x1);
|
|
||||||
const dyA = dy0(x0), dyB = dy0(x1);
|
|
||||||
const c1x = x0 + dx / 3;
|
|
||||||
const c1y = yA + (dyA * dx) / 3;
|
|
||||||
const c2x = x1 - dx / 3;
|
|
||||||
const c2y = yB - (dyB * dx) / 3;
|
|
||||||
d += ` C ${c1x} ${c1y} ${c2x} ${c2y} ${x1} ${yB}`;
|
|
||||||
x0 = x1;
|
|
||||||
}
|
|
||||||
waveSvg.path = d;
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
currentAmp = isPlaying ? amp : 0;
|
|
||||||
buildStaticWave();
|
|
||||||
}
|
|
||||||
onWidthChanged: {
|
|
||||||
flatEnd.x = width;
|
|
||||||
buildStaticWave();
|
|
||||||
}
|
|
||||||
onHeightChanged: buildStaticWave()
|
|
||||||
onCurrentAmpChanged: buildStaticWave()
|
|
||||||
onWavelengthChanged: {
|
|
||||||
k = (2 * Math.PI) / Math.max(1e-6, wavelength);
|
|
||||||
buildStaticWave();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user