arch/private_dot_config/DankMaterialShell/plugins/mediaPlayer/MediaPlayerTab.qml
lew b18328bbad Add .config/DankMaterialShell/firefox.css
Add .config/DankMaterialShell/plugin_settings.json
Add .config/DankMaterialShell/plugins/dankDesktopWeather.meta
Add .config/DankMaterialShell/plugins/dankHooks.meta
Add .config/DankMaterialShell/plugins/desktopCommand/LICENSE
Add .config/DankMaterialShell/plugins/desktopCommand/README.md
Add .config/DankMaterialShell/plugins/desktopCommand/Settings.qml
Add .config/DankMaterialShell/plugins/desktopCommand/Widget.qml
Add .config/DankMaterialShell/plugins/desktopCommand/assets/screenshot.jpg
Add .config/DankMaterialShell/plugins/desktopCommand/.git/HEAD
Add .config/DankMaterialShell/plugins/desktopCommand/.git/config
Add .config/DankMaterialShell/plugins/desktopCommand/.git/index
Add .config/DankMaterialShell/plugins/desktopCommand/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/desktopCommand/.git/objects/pack/pack-c2ca48eacecc3ab45931476641d058a89d755775.idx
Add .config/DankMaterialShell/plugins/desktopCommand/.git/objects/pack/pack-c2ca48eacecc3ab45931476641d058a89d755775.rev
Add .config/DankMaterialShell/plugins/desktopCommand/.git/objects/pack/pack-c2ca48eacecc3ab45931476641d058a89d755775.pack
Add .config/DankMaterialShell/plugins/desktopCommand/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/desktopCommand/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/desktopCommand/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/desktopCommand/.gitignore
Add .config/DankMaterialShell/plugins/desktopCommand/wrapCommand
Add .config/DankMaterialShell/plugins/desktopCommand/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/DankActionsSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/DankActionsWidget.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/DankBatteryAlerts.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/DankBatteryAlertsSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankDesktopWeather/DankDesktopWeather.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankDesktopWeather/DankDesktopWeatherSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankDesktopWeather/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/DankHooks.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/DankHooksSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/README.md
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/DankPomodoroSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/DankPomodoroWidget.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/LICENSE
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/README.md
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/HEAD
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/config
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/index
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.idx
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.rev
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-9aca069a8b76b40fcc472eba1ed9b8219a87776b.idx
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-9aca069a8b76b40fcc472eba1ed9b8219a87776b.rev
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-e6f6cdfe9914bfb4a5717ef6036821794d59ab4b.idx
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-e6f6cdfe9914bfb4a5717ef6036821794d59ab4b.rev
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.pack
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-9aca069a8b76b40fcc472eba1ed9b8219a87776b.pack
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-e6f6cdfe9914bfb4a5717ef6036821794d59ab4b.pack
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/heads/master
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/remotes/origin/master
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.gitignore
Add .config/DankMaterialShell/plugins/emojiLauncher/EmojiLauncher.qml
Add .config/DankMaterialShell/plugins/emojiLauncher/EmojiLauncherSettings.qml
Add .config/DankMaterialShell/plugins/emojiLauncher/LICENSE
Add .config/DankMaterialShell/plugins/emojiLauncher/README.md
Add .config/DankMaterialShell/plugins/emojiLauncher/catalog.js
Add .config/DankMaterialShell/plugins/emojiLauncher/data/emojis.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/data/math.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/data/nerdfont.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/HEAD
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/config
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/index
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.idx
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.rev
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.pack
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/emojiLauncher/plugin.json
Add .config/DankMaterialShell/plugins/emojiLauncher/screenshot.png
Add .config/DankMaterialShell/plugins/emojiLauncher/scripts/generate_catalog.py
Add .config/DankMaterialShell/plugins/mediaPlayer/MediaPlayerSettings.qml
Add .config/DankMaterialShell/plugins/mediaPlayer/MediaPlayerTab.qml
Add .config/DankMaterialShell/plugins/mediaPlayer/README.md
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/HEAD
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/config
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/index
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/objects/pack/pack-0b9cb33f7da23f6ff361ee3aa5117928714bc3be.idx
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/objects/pack/pack-0b9cb33f7da23f6ff361ee3aa5117928714bc3be.rev
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/objects/pack/pack-0b9cb33f7da23f6ff361ee3aa5117928714bc3be.pack
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/mediaPlayer/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/mediaPlayer/plugin.json
Add .config/DankMaterialShell/plugins/mediaPlayer/screenshot_8.png
Add .config/DankMaterialShell/plugins/dankDesktopWeather
Add .config/DankMaterialShell/plugins/dankHooks
Add .config/DankMaterialShell/settings.json
2026-01-07 15:09:11 +00:00

693 lines
25 KiB
QML

import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Mpris
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Plugins
DesktopPluginComponent {
id: root
// settings data here
property real backgroundOpacity: (pluginData.backgroundOpacity ?? 80) / 100
property real borderOpacity: (pluginData.borderOpacity ?? 100) / 100
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
opacity: showNoPlayerNow ? 0 : 1
Behavior on opacity { NumberAnimation { duration: 300 } }
property MprisPlayer activePlayer: MprisController.activePlayer
property var allPlayers: MprisController.availablePlayers
property bool isSwitching: false
property string _lastArtUrl: ""
property string _bgArtSource: ""
property string activeTrackArtFile: ""
function loadArtwork(url) {
if (!url)
return;
if (url.startsWith("http://") || url.startsWith("https://")) {
const filename = "/tmp/.dankshell/trackart_" + Date.now() + ".jpg";
activeTrackArtFile = filename;
cleanupProcess.command = ["sh", "-c", "mkdir -p /tmp/.dankshell && find /tmp/.dankshell -name 'trackart_*' ! -name '" + filename.split('/').pop() + "' -delete"];
cleanupProcess.running = true;
imageDownloader.command = ["curl", "-L", "-s", "--user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", "-o", filename, url];
imageDownloader.targetFile = filename;
imageDownloader.running = true;
return;
}
_bgArtSource = url;
}
function maybeFinishSwitch() {
if (activePlayer && activePlayer.trackTitle !== "") {
isSwitching = false;
_switchHold = false;
_stalePositionDetected = false;
}
}
function getDisplayPosition() {
if (!activePlayer) return 0;
const rawPos = Math.max(0, activePlayer.position || 0);
const length = Math.max(1, activePlayer.length || 1);
// If we detected stale position, show 0 until proper data arrives
if (_stalePositionDetected) {
return 0;
}
// Handle stale position data when switching videos
if (isSwitching && rawPos >= length * 0.9) {
return 0;
}
const pos = activePlayer.length ? rawPos % Math.max(1, activePlayer.length) : rawPos;
return pos;
}
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return minutes + ":" + (secs < 10 ? "0" : "") + secs;
}
Component.onCompleted: {
// Initialize with current player state if available
if (activePlayer) {
// Get actual position after MPRIS fully loads
Qt.callLater(() => {
try {
const actualPos = activePlayer.position || 0;
const length = activePlayer.length || 1;
root._positionSnapshot = actualPos;
if (progressSeekbar && actualPos > 0) {
progressSeekbar.value = Math.min(1, actualPos / length);
}
} catch (e) {
// Handle MPRIS errors
}
});
}
}
// Derived "no players" state: always correct, no timers.
readonly property int _playerCount: allPlayers ? allPlayers.length : 0
readonly property bool _noneAvailable: _playerCount === 0
readonly property bool _trulyIdle: activePlayer && activePlayer.playbackState === MprisPlaybackState.Stopped && !activePlayer.trackTitle && !activePlayer.trackArtist
readonly property bool showNoPlayerNow: (!_switchHold) && (_noneAvailable || _trulyIdle)
property bool _switchHold: false
Timer {
id: _switchHoldTimer
interval: 650
repeat: false
onTriggered: {
_switchHold = false;
if (isSwitching) {
isSwitching = false;
}
}
}
onActivePlayerChanged: {
root._positionSnapshot = 0;
root._forceUpdate = !root._forceUpdate;
if (!activePlayer) {
isSwitching = false;
_switchHold = false;
return;
}
isSwitching = true;
_switchHold = true;
_switchHoldTimer.restart();
if (activePlayer.trackArtUrl)
loadArtwork(activePlayer.trackArtUrl);
// Get actual current position after a short delay to allow MPRIS to sync
Qt.callLater(() => {
try {
const actualPos = activePlayer.position || 0;
root._positionSnapshot = actualPos;
if (progressSeekbar && actualPos > 0) {
progressSeekbar.value = Math.min(1, actualPos / Math.max(1, activePlayer.length || 1));
isSwitching = false;
}
} catch (e) {
// Handle errors gracefully
}
});
}
// Responsive sizing with min/max constraints
property real userScale: 1.0
readonly property real minWidth: 320
readonly property real maxWidth: 800
readonly property real minHeight: 160
readonly property real maxHeight: 400
readonly property real baseWidth: 380
readonly property real baseHeight: 200
implicitWidth: Math.max(minWidth, Math.min(maxWidth, baseWidth * userScale))
implicitHeight: Math.max(minHeight, Math.min(maxHeight, baseHeight * userScale))
Connections {
target: activePlayer
function onTrackTitleChanged() {
root._positionSnapshot = 0;
root._forceUpdate = !root._forceUpdate;
// Force immediate position reset for new track
if (activePlayer.position > 0 && activePlayer.length > 0) {
const progressRatio = activePlayer.position / activePlayer.length;
if (progressRatio > 0.9) {
// Likely stale data - force reset
root._stalePositionDetected = true;
}
}
_switchHoldTimer.restart();
maybeFinishSwitch();
// Reset progress bar immediately on track change
if (progressSeekbar) {
progressSeekbar.value = 0;
}
}
function onTrackArtUrlChanged() {
if (activePlayer?.trackArtUrl) {
_lastArtUrl = activePlayer.trackArtUrl;
loadArtwork(activePlayer.trackArtUrl);
}
}
function onPositionChanged() {
try {
if (root._stalePositionDetected && activePlayer.position < activePlayer.length * 0.5) {
// Position updated properly now
root._stalePositionDetected = false;
root._forceUpdate = !root._forceUpdate;
}
} catch (e) {
// MPRIS service disappeared - reset state
root._stalePositionDetected = false;
}
}
}
Connections {
target: MprisController
function onAvailablePlayersChanged() {
const count = (MprisController.availablePlayers?.length || 0);
if (count === 0) {
isSwitching = false;
_switchHold = false;
} else {
_switchHold = true;
_switchHoldTimer.restart();
}
}
}
Process {
id: imageDownloader
running: false
property string targetFile: ""
onExited: exitCode => {
if (exitCode === 0 && targetFile)
_bgArtSource = "file://" + targetFile;
}
}
Process {
id: cleanupProcess
running: false
}
property bool isSeeking: false
property real _positionSnapshot: 0
property bool _forceUpdate: false
property real _animationTick: 0
property bool _stalePositionDetected: false
Timer {
id: positionUpdateTimer
interval: 100
running: true
repeat: true
onTriggered: {
// Update snapshot to trigger binding re-evaluation
if (activePlayer) {
try {
const newPosition = activePlayer.position || 0;
root._positionSnapshot = newPosition;
// Force progress bar refresh when switching
if (isSwitching || _stalePositionDetected) {
if (progressSeekbar) {
progressSeekbar.value = progressSeekbar.calculateProgress();
}
}
} catch (e) {
// Handle MPRIS service errors gracefully
root._positionSnapshot = 0;
}
}
}
}
// Use animation to drive constant updates for smooth progress bar
NumberAnimation {
id: progressUpdateAnimation
target: root
property: "_animationTick"
from: 0
to: 10000
duration: 10000
loops: Animation.Infinite
running: activePlayer?.playbackState === MprisPlaybackState.Playing && !isSeeking
}
Item {
id: bgContainer
anchors.fill: parent
visible: _bgArtSource !== ""
Image {
id: bgImage
anchors.centerIn: parent
width: Math.max(parent.width, parent.height) * 1.1
height: width
// source: _bgArtSource
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: true
visible: false
onStatusChanged: {
if (status === Image.Ready)
maybeFinishSwitch();
}
}
Item {
id: blurredBg
anchors.fill: parent
visible: false
MultiEffect {
anchors.centerIn: parent
width: bgImage.width
height: bgImage.height
source: bgImage
blurEnabled: true
blurMax: 64
blur: 2
saturation: -0.2
brightness: -0.25
}
}
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 {
anchors.fill: parent
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surface, root.backgroundOpacity)
}
}
// --- Ocean Wave Background ---
Canvas {
id: waveCanvas
anchors.fill: parent
z: 1 // Ensures it stays behind the main content
opacity: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? 0.4 : 0.1
property real phase: 0
// This timer drives the animation "movement"
Timer {
interval: 16 // ~60 FPS
running: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing
repeat: true
onTriggered: {
waveCanvas.phase += 0.05;
waveCanvas.requestPaint();
}
}
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
// Draw two overlapping waves for a "deep" ocean feel
drawWave(ctx, "#40" + Theme.primary.toString().substring(1), 0.5, 15, phase);
drawWave(ctx, "#60" + Theme.primary.toString().substring(1), 0.8, 10, phase * 0.7);
drawWave(ctx, "#70" + Theme.primary.toString().substring(1), 0.8, 10, phase * 0.9);
}
function drawWave(ctx, color, speed, amplitude, currentPhase) {
ctx.beginPath();
ctx.fillStyle = color;
var waveHeight = height * 0.7; // Position waves at the bottom 30%
ctx.moveTo(0, height);
for (var x = 0; x <= width; x += 5) {
// Sine wave calculation: y = amplitude * sin(frequency * x + phase)
var y = waveHeight + Math.sin(x * 0.02 + currentPhase) * amplitude;
ctx.lineTo(x, y);
}
ctx.lineTo(width, height);
ctx.closePath();
ctx.fill();
}
}
// --- End Ocean Wave Background ---
Column {
anchors.centerIn: parent
spacing: Theme.spacingM
visible: showNoPlayerNow
DankIcon {
name: "music_note"
size: Theme.iconSize * 3
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.horizontalCenter: parent.horizontalCenter
}
}
// Main content container - Layout with thumbnail
Item {
anchors.fill: parent
anchors.margins: Theme.spacingM * userScale
visible: !_noneAvailable && (!showNoPlayerNow)
Row {
anchors.fill: parent
spacing: Theme.spacingM * userScale
// Album Thumbnail Section (Left)
Rectangle {
id: thumbnailContainer
width: parent.height * 0.85
height: parent.height * 0.85
anchors.verticalCenter: parent.verticalCenter
radius: 6 * userScale
color: "transparent"
clip: true
property real albumRotation: 0
NumberAnimation {
id: rotationAnimation
target: thumbnailContainer
property: "albumRotation"
from: 0
to: 360
duration: 20000
running: activePlayer?.playbackState === MprisPlaybackState.Playing
loops: Animation.Infinite
}
DankAlbumArt {
id: albumArt
width: parent.width * 0.76
height: parent.height * 0.76
anchors.centerIn: parent
activePlayer: root.activePlayer
rotation: thumbnailContainer.albumRotation
}
// Subtle border
Rectangle {
anchors.fill: parent
radius: parent.radius
color: "transparent"
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, root.borderOpacity)
border.width: 1
}
}
// Content Section (Right)
Column {
width: parent.width - thumbnailContainer.width - parent.spacing
height: parent.height
spacing: Theme.spacingS * userScale
// Song Info Section (Top)
Column {
id: songInfo
width: parent.width
spacing: 2 * userScale
StyledText {
text: activePlayer?.trackTitle || "The (Overdue) Collapse of Wind..."
font.pixelSize: Theme.fontSizeMedium * 1.1 * userScale
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width
elide: Text.ElideRight
maximumLineCount: 1
}
StyledText {
text: activePlayer?.trackArtist || "Catalyst"
font.pixelSize: Theme.fontSizeSmall * userScale
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
width: parent.width
elide: Text.ElideRight
maximumLineCount: 1
}
}
// Spacer
Item {
width: parent.width
height: Theme.spacingXS * userScale
}
// Controls Row (Middle)
Row {
id: controlsRow
width: parent.width
spacing: Theme.spacingS * userScale
// Previous Button
Rectangle {
width: 32 * userScale
height: 32 * userScale
radius: 4 * userScale
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: "skip_previous"
size: 28 * userScale
color: Theme.primary
}
MouseArea {
id: prevBtnArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!activePlayer)
return;
if (activePlayer.position > 8 && activePlayer.canSeek) {
activePlayer.position = 0;
} else {
activePlayer.previous();
}
}
}
}
// Play/Pause Button
Rectangle {
width: 32 * userScale
height: 32 * userScale
radius: 4 * userScale
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
size: 28 * userScale
color: Theme.primary
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: activePlayer && activePlayer.togglePlaying()
}
}
// Next Button
Rectangle {
width: 32 * userScale
height: 32 * userScale
radius: 4 * userScale
color: "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: "skip_next"
size: 28 * userScale
color: Theme.primary
}
MouseArea {
id: nextBtnArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: activePlayer && activePlayer.next()
}
}
// Spacer to push buttons to right
Item {
width: parent.width - (32 * 3 * userScale) - (Theme.spacingS * 3 * userScale)
height: 1
}
}
// Seekbar Section (Bottom)
Column {
id: seekbarSection
width: parent.width
spacing: 2 * userScale
// Integrated DankSeekbar
DankSeekbar {
id: progressSeekbar
width: parent.width
height: 16 * userScale // Consistent with previous design
activePlayer: root.activePlayer
// Keep the Tab's seeking state in sync with the component
onIsSeekingChanged: root.isSeeking = isSeeking
function calculateProgress() {
if (!root.activePlayer || root.activePlayer.length <= 0) return 0;
const rawPos = Math.max(0, root.activePlayer.position || 0);
const length = Math.max(1, root.activePlayer.length || 1);
// If we detected stale position, show 0 until proper data arrives
if (root._stalePositionDetected) {
// Check if position is now valid
if (rawPos < length * 0.8) {
root._stalePositionDetected = false;
root.isSwitching = false;
} else {
return 0;
}
}
// Reset if position seems stale (at end for new video)
if (root.isSwitching && rawPos >= length * 0.9) {
root._stalePositionDetected = true;
return 0;
}
// Force position refresh when switching videos
if (root.isSwitching && rawPos > 0 && rawPos < length * 0.8) {
root.isSwitching = false;
}
return Math.min(1, rawPos / length);
}
// Connect position updates to seekbar value directly
Timer {
interval: 50
running: true
repeat: true
onTriggered: {
if (progressSeekbar && activePlayer) {
try {
progressSeekbar.value = progressSeekbar.calculateProgress();
} catch (e) {
// Handle MPRIS errors
}
}
}
}
}
// Time labels
Item {
width: parent.width
height: 12 * userScale
StyledText {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: {
// Force dependency on position updates
root._positionSnapshot;
return formatTime(getDisplayPosition());
}
font.pixelSize: Theme.fontSizeSmall * 0.9 * userScale
color: Theme.surfaceVariantText
}
StyledText {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: {
if (!activePlayer || !activePlayer.length)
return "0:00";
const dur = Math.max(0, activePlayer.length || 0);
const minutes = Math.floor(dur / 60);
const seconds = Math.floor(dur % 60);
return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
}
font.pixelSize: Theme.fontSizeSmall * 0.9 * userScale
color: Theme.surfaceVariantText
}
}
}
}
}
}
}