mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-02 02:22:06 -04:00
Compare commits
5 Commits
32c063aab8
...
f2df53afcd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2df53afcd | ||
|
|
4179fcee83 | ||
|
|
a0c9af1ee7 | ||
|
|
049266271a | ||
|
|
0eabda3164 |
@@ -39,11 +39,10 @@ type LayerSurface struct {
|
|||||||
wlSurface *client.Surface
|
wlSurface *client.Surface
|
||||||
layerSurf *wlr_layer_shell.ZwlrLayerSurfaceV1
|
layerSurf *wlr_layer_shell.ZwlrLayerSurfaceV1
|
||||||
viewport *wp_viewporter.WpViewport
|
viewport *wp_viewporter.WpViewport
|
||||||
wlPool *client.ShmPool
|
wlPools [2]*client.ShmPool
|
||||||
wlBuffer *client.Buffer
|
wlBuffers [2]*client.Buffer
|
||||||
bufferBusy bool
|
slotBusy [2]bool
|
||||||
oldPool *client.ShmPool
|
needsRedraw bool
|
||||||
oldBuffer *client.Buffer
|
|
||||||
scopyBuffer *client.Buffer
|
scopyBuffer *client.Buffer
|
||||||
configured bool
|
configured bool
|
||||||
hidden bool
|
hidden bool
|
||||||
@@ -136,6 +135,7 @@ func (p *Picker) Run() (*Color, error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.flushRedraws()
|
||||||
p.checkDone()
|
p.checkDone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +164,15 @@ func (p *Picker) checkDone() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Picker) flushRedraws() {
|
||||||
|
for _, ls := range p.surfaces {
|
||||||
|
if !ls.needsRedraw {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.redrawSurface(ls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Picker) connect() error {
|
func (p *Picker) connect() error {
|
||||||
display, err := client.Connect("")
|
display, err := client.Connect("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -507,47 +516,45 @@ func (p *Picker) captureForSurface(ls *LayerSurface) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Picker) redrawSurface(ls *LayerSurface) {
|
func (p *Picker) redrawSurface(ls *LayerSurface) {
|
||||||
|
slot := ls.state.FrontIndex()
|
||||||
|
if ls.slotBusy[slot] {
|
||||||
|
ls.needsRedraw = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var renderBuf *ShmBuffer
|
var renderBuf *ShmBuffer
|
||||||
if ls.hidden {
|
switch {
|
||||||
|
case ls.hidden:
|
||||||
renderBuf = ls.state.RedrawScreenOnly()
|
renderBuf = ls.state.RedrawScreenOnly()
|
||||||
} else {
|
default:
|
||||||
renderBuf = ls.state.Redraw()
|
renderBuf = ls.state.Redraw()
|
||||||
}
|
}
|
||||||
if renderBuf == nil {
|
if renderBuf == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ls.oldBuffer != nil {
|
ls.needsRedraw = false
|
||||||
ls.oldBuffer.Destroy()
|
|
||||||
ls.oldBuffer = nil
|
if ls.wlPools[slot] == nil {
|
||||||
}
|
pool, err := p.shm.CreatePool(renderBuf.Fd(), int32(renderBuf.Size()))
|
||||||
if ls.oldPool != nil {
|
if err != nil {
|
||||||
ls.oldPool.Destroy()
|
return
|
||||||
ls.oldPool = nil
|
}
|
||||||
|
ls.wlPools[slot] = pool
|
||||||
|
|
||||||
|
wlBuffer, err := pool.CreateBuffer(0, int32(renderBuf.Width), int32(renderBuf.Height), int32(renderBuf.Stride), uint32(ls.state.ScreenFormat()))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ls.wlBuffers[slot] = wlBuffer
|
||||||
|
|
||||||
|
s := slot
|
||||||
|
wlBuffer.SetReleaseHandler(func(e client.BufferReleaseEvent) {
|
||||||
|
ls.slotBusy[s] = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ls.oldPool = ls.wlPool
|
ls.slotBusy[slot] = true
|
||||||
ls.oldBuffer = ls.wlBuffer
|
|
||||||
ls.wlPool = nil
|
|
||||||
ls.wlBuffer = nil
|
|
||||||
|
|
||||||
pool, err := p.shm.CreatePool(renderBuf.Fd(), int32(renderBuf.Size()))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ls.wlPool = pool
|
|
||||||
|
|
||||||
wlBuffer, err := pool.CreateBuffer(0, int32(renderBuf.Width), int32(renderBuf.Height), int32(renderBuf.Stride), uint32(ls.state.ScreenFormat()))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ls.wlBuffer = wlBuffer
|
|
||||||
|
|
||||||
lsRef := ls
|
|
||||||
wlBuffer.SetReleaseHandler(func(e client.BufferReleaseEvent) {
|
|
||||||
lsRef.bufferBusy = false
|
|
||||||
})
|
|
||||||
ls.bufferBusy = true
|
|
||||||
|
|
||||||
logicalW, logicalH := ls.state.LogicalSize()
|
logicalW, logicalH := ls.state.LogicalSize()
|
||||||
if logicalW == 0 || logicalH == 0 {
|
if logicalW == 0 || logicalH == 0 {
|
||||||
@@ -566,7 +573,7 @@ func (p *Picker) redrawSurface(ls *LayerSurface) {
|
|||||||
}
|
}
|
||||||
_ = ls.wlSurface.SetBufferScale(bufferScale)
|
_ = ls.wlSurface.SetBufferScale(bufferScale)
|
||||||
}
|
}
|
||||||
_ = ls.wlSurface.Attach(wlBuffer, 0, 0)
|
_ = ls.wlSurface.Attach(ls.wlBuffers[slot], 0, 0)
|
||||||
_ = ls.wlSurface.Damage(0, 0, int32(logicalW), int32(logicalH))
|
_ = ls.wlSurface.Damage(0, 0, int32(logicalW), int32(logicalH))
|
||||||
_ = ls.wlSurface.Commit()
|
_ = ls.wlSurface.Commit()
|
||||||
|
|
||||||
@@ -634,7 +641,7 @@ func (p *Picker) setupPointerHandlers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.activeSurface.state.OnPointerMotion(e.SurfaceX, e.SurfaceY)
|
p.activeSurface.state.OnPointerMotion(e.SurfaceX, e.SurfaceY)
|
||||||
p.redrawSurface(p.activeSurface)
|
p.activeSurface.needsRedraw = true
|
||||||
})
|
})
|
||||||
|
|
||||||
p.pointer.SetLeaveHandler(func(e client.PointerLeaveEvent) {
|
p.pointer.SetLeaveHandler(func(e client.PointerLeaveEvent) {
|
||||||
@@ -655,7 +662,7 @@ func (p *Picker) setupPointerHandlers() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.activeSurface.state.OnPointerMotion(e.SurfaceX, e.SurfaceY)
|
p.activeSurface.state.OnPointerMotion(e.SurfaceX, e.SurfaceY)
|
||||||
p.redrawSurface(p.activeSurface)
|
p.activeSurface.needsRedraw = true
|
||||||
})
|
})
|
||||||
|
|
||||||
p.pointer.SetButtonHandler(func(e client.PointerButtonEvent) {
|
p.pointer.SetButtonHandler(func(e client.PointerButtonEvent) {
|
||||||
@@ -679,17 +686,13 @@ func (p *Picker) cleanup() {
|
|||||||
if ls.scopyBuffer != nil {
|
if ls.scopyBuffer != nil {
|
||||||
ls.scopyBuffer.Destroy()
|
ls.scopyBuffer.Destroy()
|
||||||
}
|
}
|
||||||
if ls.oldBuffer != nil {
|
for i := range ls.wlBuffers {
|
||||||
ls.oldBuffer.Destroy()
|
if ls.wlBuffers[i] != nil {
|
||||||
}
|
ls.wlBuffers[i].Destroy()
|
||||||
if ls.oldPool != nil {
|
}
|
||||||
ls.oldPool.Destroy()
|
if ls.wlPools[i] != nil {
|
||||||
}
|
ls.wlPools[i].Destroy()
|
||||||
if ls.wlBuffer != nil {
|
}
|
||||||
ls.wlBuffer.Destroy()
|
|
||||||
}
|
|
||||||
if ls.wlPool != nil {
|
|
||||||
ls.wlPool.Destroy()
|
|
||||||
}
|
}
|
||||||
if ls.viewport != nil {
|
if ls.viewport != nil {
|
||||||
ls.viewport.Destroy()
|
ls.viewport.Destroy()
|
||||||
|
|||||||
@@ -274,6 +274,12 @@ func (s *SurfaceState) FrontRenderBuffer() *ShmBuffer {
|
|||||||
return s.renderBufs[s.front]
|
return s.renderBufs[s.front]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SurfaceState) FrontIndex() int {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return s.front
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SurfaceState) SwapBuffers() {
|
func (s *SurfaceState) SwapBuffers() {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.front ^= 1
|
s.front ^= 1
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ Singleton {
|
|||||||
|
|
||||||
property string vpnLastConnected: ""
|
property string vpnLastConnected: ""
|
||||||
|
|
||||||
|
property string lastPlayerIdentity: ""
|
||||||
|
|
||||||
property var deviceMaxVolumes: ({})
|
property var deviceMaxVolumes: ({})
|
||||||
property var hiddenOutputDeviceNames: []
|
property var hiddenOutputDeviceNames: []
|
||||||
property var hiddenInputDeviceNames: []
|
property var hiddenInputDeviceNames: []
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ var SPEC = {
|
|||||||
|
|
||||||
vpnLastConnected: { def: "" },
|
vpnLastConnected: { def: "" },
|
||||||
|
|
||||||
|
lastPlayerIdentity: { def: "" },
|
||||||
|
|
||||||
deviceMaxVolumes: { def: {} },
|
deviceMaxVolumes: { def: {} },
|
||||||
hiddenOutputDeviceNames: { def: [] },
|
hiddenOutputDeviceNames: { def: [] },
|
||||||
hiddenInputDeviceNames: { def: [] },
|
hiddenInputDeviceNames: { def: [] },
|
||||||
|
|||||||
@@ -1438,12 +1438,21 @@ Item {
|
|||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onClicked: {
|
onClicked: {
|
||||||
systemUpdateLoader.active = true;
|
systemUpdateLoader.active = true;
|
||||||
|
if (!systemUpdateLoader.item)
|
||||||
|
return;
|
||||||
|
const popout = systemUpdateLoader.item;
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
const effectiveBarConfig = topBarContent.barConfig;
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
||||||
if (systemUpdateLoader.item && systemUpdateLoader.item.setBarContext) {
|
if (popout.setBarContext) {
|
||||||
systemUpdateLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
popout.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
||||||
}
|
}
|
||||||
systemUpdateLoader.item?.toggle();
|
if (popout.setTriggerPosition) {
|
||||||
|
const globalPos = visualContent.mapToItem(null, 0, 0);
|
||||||
|
const currentScreen = parentScreen || Screen;
|
||||||
|
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barWindow.effectiveBarThickness, visualWidth, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
||||||
|
popout.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
||||||
|
}
|
||||||
|
PopoutManager.requestPopout(popout, undefined, "systemUpdate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ DankPopout {
|
|||||||
if (currentPlayer && currentPlayer !== player && currentPlayer.canPause) {
|
if (currentPlayer && currentPlayer !== player && currentPlayer.canPause) {
|
||||||
currentPlayer.pause();
|
currentPlayer.pause();
|
||||||
}
|
}
|
||||||
MprisController.activePlayer = player;
|
MprisController.setActivePlayer(player);
|
||||||
root.__hideDropdowns();
|
root.__hideDropdowns();
|
||||||
}
|
}
|
||||||
onDeviceSelected: device => {
|
onDeviceSelected: device => {
|
||||||
|
|||||||
@@ -4,12 +4,61 @@ pragma ComponentBehavior: Bound
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Services.Mpris
|
import Quickshell.Services.Mpris
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
|
readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
|
||||||
property MprisPlayer activePlayer: availablePlayers.find(p => p.isPlaying) ?? availablePlayers.find(p => p.canControl && p.canPlay) ?? null
|
property MprisPlayer activePlayer: null
|
||||||
|
|
||||||
|
onAvailablePlayersChanged: _resolveActivePlayer()
|
||||||
|
Component.onCompleted: _resolveActivePlayer()
|
||||||
|
|
||||||
|
Instantiator {
|
||||||
|
model: root.availablePlayers
|
||||||
|
delegate: Connections {
|
||||||
|
required property MprisPlayer modelData
|
||||||
|
target: modelData
|
||||||
|
function onIsPlayingChanged() {
|
||||||
|
if (modelData.isPlaying)
|
||||||
|
root._resolveActivePlayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _resolveActivePlayer(): void {
|
||||||
|
const playing = availablePlayers.find(p => p.isPlaying);
|
||||||
|
if (playing) {
|
||||||
|
activePlayer = playing;
|
||||||
|
_persistIdentity(playing.identity);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (activePlayer && availablePlayers.indexOf(activePlayer) >= 0)
|
||||||
|
return;
|
||||||
|
const savedId = SessionData.lastPlayerIdentity;
|
||||||
|
if (savedId) {
|
||||||
|
const match = availablePlayers.find(p => p.identity === savedId);
|
||||||
|
if (match) {
|
||||||
|
activePlayer = match;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
activePlayer = availablePlayers.find(p => p.canControl && p.canPlay) ?? null;
|
||||||
|
if (activePlayer)
|
||||||
|
_persistIdentity(activePlayer.identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActivePlayer(player: MprisPlayer): void {
|
||||||
|
activePlayer = player;
|
||||||
|
if (player)
|
||||||
|
_persistIdentity(player.identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _persistIdentity(identity: string): void {
|
||||||
|
if (identity && SessionData.lastPlayerIdentity !== identity)
|
||||||
|
SessionData.set("lastPlayerIdentity", identity);
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 1000
|
interval: 1000
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import QtQuick
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Services.Pipewire
|
import Quickshell.Services.Pipewire
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -58,6 +59,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool screensharingActive: {
|
readonly property bool screensharingActive: {
|
||||||
|
if (CompositorService.isNiri && NiriService.hasActiveCast) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if (!Pipewire.ready || !Pipewire.nodes?.values) {
|
if (!Pipewire.ready || !Pipewire.nodes?.values) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -74,6 +79,12 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.properties && node.properties["media.class"] === "Stream/Output/Video") {
|
||||||
|
if (looksLikeScreencast(node)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (node.properties && node.properties["media.class"] === "Stream/Input/Audio") {
|
if (node.properties && node.properties["media.class"] === "Stream/Input/Audio") {
|
||||||
const mediaName = (node.properties["media.name"] || "").toLowerCase()
|
const mediaName = (node.properties["media.name"] || "").toLowerCase()
|
||||||
const appName = (node.properties["application.name"] || "").toLowerCase()
|
const appName = (node.properties["application.name"] || "").toLowerCase()
|
||||||
@@ -110,8 +121,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
const appName = (node.properties && node.properties["application.name"] || "").toLowerCase()
|
const appName = (node.properties && node.properties["application.name"] || "").toLowerCase()
|
||||||
const nodeName = (node.name || "").toLowerCase()
|
const nodeName = (node.name || "").toLowerCase()
|
||||||
const combined = appName + " " + nodeName
|
const mediaName = (node.properties && node.properties["media.name"] || "").toLowerCase()
|
||||||
return /xdg-desktop-portal|xdpw|screencast|screen|gnome shell|kwin|obs/.test(combined)
|
const combined = appName + " " + nodeName + " " + mediaName
|
||||||
|
return /xdg-desktop-portal|xdpw|screencast|screen-cast|screen|gnome shell|kwin|obs|niri/.test(combined)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMicrophoneStatus() {
|
function getMicrophoneStatus() {
|
||||||
|
|||||||
@@ -231,7 +231,10 @@ Singleton {
|
|||||||
return;
|
return;
|
||||||
isChecking = true;
|
isChecking = true;
|
||||||
hasError = false;
|
hasError = false;
|
||||||
if (updChecker.length > 0) {
|
if (pkgManager === "paru" || pkgManager === "yay") {
|
||||||
|
const repoCmd = updChecker.length > 0 ? updChecker : `${pkgManager} -Qu`;
|
||||||
|
updateChecker.command = ["sh", "-c", `(${repoCmd} 2>/dev/null; ${pkgManager} -Qua 2>/dev/null) || true`];
|
||||||
|
} else if (updChecker.length > 0) {
|
||||||
updateChecker.command = [updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.params);
|
updateChecker.command = [updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.params);
|
||||||
} else {
|
} else {
|
||||||
updateChecker.command = [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.params);
|
updateChecker.command = [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.params);
|
||||||
|
|||||||
@@ -8,13 +8,122 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property MprisPlayer activePlayer
|
property MprisPlayer activePlayer
|
||||||
property real value: {
|
property real seekPreviewRatio: -1
|
||||||
if (!activePlayer || activePlayer.length <= 0) return 0
|
readonly property real playerValue: {
|
||||||
const pos = (activePlayer.position || 0) % Math.max(1, activePlayer.length)
|
if (!activePlayer || activePlayer.length <= 0)
|
||||||
const calculatedRatio = pos / activePlayer.length
|
return 0;
|
||||||
return Math.max(0, Math.min(1, calculatedRatio))
|
const pos = (activePlayer.position || 0) % Math.max(1, activePlayer.length);
|
||||||
|
const calculatedRatio = pos / activePlayer.length;
|
||||||
|
return Math.max(0, Math.min(1, calculatedRatio));
|
||||||
}
|
}
|
||||||
|
property real value: seekPreviewRatio >= 0 ? seekPreviewRatio : playerValue
|
||||||
property bool isSeeking: false
|
property bool isSeeking: false
|
||||||
|
property bool isDraggingSeek: false
|
||||||
|
property real committedSeekRatio: -1
|
||||||
|
property int previewSettleChecksRemaining: 0
|
||||||
|
property real dragThreshold: 4
|
||||||
|
property int holdIndicatorDelay: 180
|
||||||
|
|
||||||
|
function clampRatio(ratio) {
|
||||||
|
return Math.max(0, Math.min(1, ratio));
|
||||||
|
}
|
||||||
|
|
||||||
|
function ratioForPosition(position) {
|
||||||
|
if (!activePlayer || activePlayer.length <= 0)
|
||||||
|
return 0;
|
||||||
|
return clampRatio(position / activePlayer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionForRatio(ratio) {
|
||||||
|
if (!activePlayer || activePlayer.length <= 0)
|
||||||
|
return 0;
|
||||||
|
const rawPosition = clampRatio(ratio) * activePlayer.length;
|
||||||
|
return Math.min(rawPosition, activePlayer.length * 0.99);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePreviewFromMouse(mouseX, width) {
|
||||||
|
if (!activePlayer || activePlayer.length <= 0 || width <= 0)
|
||||||
|
return;
|
||||||
|
seekPreviewRatio = clampRatio(mouseX / width);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCommittedSeekPreview() {
|
||||||
|
previewSettleTimer.stop();
|
||||||
|
committedSeekRatio = -1;
|
||||||
|
previewSettleChecksRemaining = 0;
|
||||||
|
if (!isSeeking)
|
||||||
|
seekPreviewRatio = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function beginCommittedSeekPreview(position) {
|
||||||
|
seekPreviewRatio = ratioForPosition(position);
|
||||||
|
committedSeekRatio = seekPreviewRatio;
|
||||||
|
previewSettleChecksRemaining = 15;
|
||||||
|
previewSettleTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSeekPressed(mouse, width, mouseArea, holdTimer) {
|
||||||
|
isSeeking = true;
|
||||||
|
isDraggingSeek = false;
|
||||||
|
mouseArea.pressX = mouse.x;
|
||||||
|
clearCommittedSeekPreview();
|
||||||
|
holdTimer.restart();
|
||||||
|
if (activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
|
||||||
|
updatePreviewFromMouse(mouse.x, width);
|
||||||
|
mouseArea.pendingSeekPosition = positionForRatio(seekPreviewRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSeekReleased(mouseArea, holdTimer) {
|
||||||
|
holdTimer.stop();
|
||||||
|
isSeeking = false;
|
||||||
|
isDraggingSeek = false;
|
||||||
|
if (mouseArea.pendingSeekPosition >= 0 && activePlayer && activePlayer.canSeek && activePlayer.length > 0) {
|
||||||
|
const clamped = Math.min(mouseArea.pendingSeekPosition, activePlayer.length * 0.99);
|
||||||
|
activePlayer.position = clamped;
|
||||||
|
mouseArea.pendingSeekPosition = -1;
|
||||||
|
beginCommittedSeekPreview(clamped);
|
||||||
|
} else {
|
||||||
|
seekPreviewRatio = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSeekPositionChanged(mouse, width, mouseArea) {
|
||||||
|
if (mouseArea.pressed && isSeeking && activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
|
||||||
|
if (!isDraggingSeek && Math.abs(mouse.x - mouseArea.pressX) >= dragThreshold)
|
||||||
|
isDraggingSeek = true;
|
||||||
|
updatePreviewFromMouse(mouse.x, width);
|
||||||
|
mouseArea.pendingSeekPosition = positionForRatio(seekPreviewRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSeekCanceled(mouseArea, holdTimer) {
|
||||||
|
holdTimer.stop();
|
||||||
|
isSeeking = false;
|
||||||
|
isDraggingSeek = false;
|
||||||
|
mouseArea.pendingSeekPosition = -1;
|
||||||
|
clearCommittedSeekPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: previewSettleTimer
|
||||||
|
interval: 80
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
if (root.isSeeking || root.committedSeekRatio < 0) {
|
||||||
|
stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewSettled = Math.abs(root.playerValue - root.committedSeekRatio) <= 0.0015;
|
||||||
|
if (previewSettled || root.previewSettleChecksRemaining <= 0) {
|
||||||
|
root.clearCommittedSeekPreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.previewSettleChecksRemaining -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
implicitHeight: 20
|
implicitHeight: 20
|
||||||
|
|
||||||
@@ -29,58 +138,35 @@ Item {
|
|||||||
|
|
||||||
M3WaveProgress {
|
M3WaveProgress {
|
||||||
value: root.value
|
value: root.value
|
||||||
|
actualValue: root.playerValue
|
||||||
|
showActualPlaybackState: root.isSeeking
|
||||||
|
actualProgressColor: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.45)
|
||||||
isPlaying: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing
|
isPlaying: activePlayer && activePlayer.playbackState === MprisPlaybackState.Playing
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
id: waveMouseArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
enabled: activePlayer && activePlayer.canSeek && activePlayer.length > 0
|
enabled: activePlayer && activePlayer.canSeek && activePlayer.length > 0
|
||||||
|
|
||||||
property real pendingSeekPosition: -1
|
property real pendingSeekPosition: -1
|
||||||
|
property real pressX: 0
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: waveSeekDebounceTimer
|
id: waveHoldIndicatorTimer
|
||||||
interval: 150
|
interval: root.holdIndicatorDelay
|
||||||
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (parent.pendingSeekPosition >= 0 && activePlayer && activePlayer.canSeek && activePlayer.length > 0) {
|
if (parent.pressed && root.isSeeking)
|
||||||
const clamped = Math.min(parent.pendingSeekPosition, activePlayer.length * 0.99)
|
root.isDraggingSeek = true;
|
||||||
activePlayer.position = clamped
|
|
||||||
parent.pendingSeekPosition = -1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressed: (mouse) => {
|
onPressed: mouse => root.handleSeekPressed(mouse, parent.width, waveMouseArea, waveHoldIndicatorTimer)
|
||||||
root.isSeeking = true
|
onReleased: root.handleSeekReleased(waveMouseArea, waveHoldIndicatorTimer)
|
||||||
if (activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
|
onPositionChanged: mouse => root.handleSeekPositionChanged(mouse, parent.width, waveMouseArea)
|
||||||
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
onCanceled: root.handleSeekCanceled(waveMouseArea, waveHoldIndicatorTimer)
|
||||||
pendingSeekPosition = r * activePlayer.length
|
|
||||||
waveSeekDebounceTimer.restart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onReleased: {
|
|
||||||
root.isSeeking = false
|
|
||||||
waveSeekDebounceTimer.stop()
|
|
||||||
if (pendingSeekPosition >= 0 && activePlayer && activePlayer.canSeek && activePlayer.length > 0) {
|
|
||||||
const clamped = Math.min(pendingSeekPosition, activePlayer.length * 0.99)
|
|
||||||
activePlayer.position = clamped
|
|
||||||
pendingSeekPosition = -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onPositionChanged: (mouse) => {
|
|
||||||
if (pressed && root.isSeeking && activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
|
|
||||||
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
|
||||||
pendingSeekPosition = r * activePlayer.length
|
|
||||||
waveSeekDebounceTimer.restart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onClicked: (mouse) => {
|
|
||||||
if (activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
|
|
||||||
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
|
||||||
activePlayer.position = r * activePlayer.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,6 +179,7 @@ Item {
|
|||||||
property color trackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.40)
|
property color trackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.40)
|
||||||
property color fillColor: Theme.primary
|
property color fillColor: Theme.primary
|
||||||
property color playheadColor: Theme.primary
|
property color playheadColor: Theme.primary
|
||||||
|
property color actualProgressColor: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.45)
|
||||||
readonly property real midY: height / 2
|
readonly property real midY: height / 2
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -110,7 +197,22 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
color: parent.fillColor
|
color: parent.fillColor
|
||||||
radius: height / 2
|
radius: height / 2
|
||||||
Behavior on width { NumberAnimation { duration: 80 } }
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: root.isDraggingSeek
|
||||||
|
width: 2
|
||||||
|
height: Math.max(parent.lineWidth + 4, 10)
|
||||||
|
radius: width / 2
|
||||||
|
color: parent.actualProgressColor
|
||||||
|
x: Math.max(0, Math.min(parent.width, parent.width * root.playerValue)) - width / 2
|
||||||
|
y: parent.midY - height / 2
|
||||||
|
z: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -122,59 +224,37 @@ Item {
|
|||||||
x: Math.max(0, Math.min(parent.width, parent.width * root.value)) - width / 2
|
x: Math.max(0, Math.min(parent.width, parent.width * root.value)) - width / 2
|
||||||
y: parent.midY - height / 2
|
y: parent.midY - height / 2
|
||||||
z: 3
|
z: 3
|
||||||
Behavior on x { NumberAnimation { duration: 80 } }
|
Behavior on x {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 80
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
id: flatMouseArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
enabled: activePlayer && activePlayer.canSeek && activePlayer.length > 0
|
enabled: activePlayer && activePlayer.canSeek && activePlayer.length > 0
|
||||||
|
|
||||||
property real pendingSeekPosition: -1
|
property real pendingSeekPosition: -1
|
||||||
|
property real pressX: 0
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: flatSeekDebounceTimer
|
id: flatHoldIndicatorTimer
|
||||||
interval: 150
|
interval: root.holdIndicatorDelay
|
||||||
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (parent.pendingSeekPosition >= 0 && activePlayer && activePlayer.canSeek && activePlayer.length > 0) {
|
if (parent.pressed && root.isSeeking)
|
||||||
const clamped = Math.min(parent.pendingSeekPosition, activePlayer.length * 0.99)
|
root.isDraggingSeek = true;
|
||||||
activePlayer.position = clamped
|
|
||||||
parent.pendingSeekPosition = -1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressed: (mouse) => {
|
onPressed: mouse => root.handleSeekPressed(mouse, parent.width, flatMouseArea, flatHoldIndicatorTimer)
|
||||||
root.isSeeking = true
|
onReleased: root.handleSeekReleased(flatMouseArea, flatHoldIndicatorTimer)
|
||||||
if (activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
|
onPositionChanged: mouse => root.handleSeekPositionChanged(mouse, parent.width, flatMouseArea)
|
||||||
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
onCanceled: root.handleSeekCanceled(flatMouseArea, flatHoldIndicatorTimer)
|
||||||
pendingSeekPosition = r * activePlayer.length
|
|
||||||
flatSeekDebounceTimer.restart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onReleased: {
|
|
||||||
root.isSeeking = false
|
|
||||||
flatSeekDebounceTimer.stop()
|
|
||||||
if (pendingSeekPosition >= 0 && activePlayer && activePlayer.canSeek && activePlayer.length > 0) {
|
|
||||||
const clamped = Math.min(pendingSeekPosition, activePlayer.length * 0.99)
|
|
||||||
activePlayer.position = clamped
|
|
||||||
pendingSeekPosition = -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onPositionChanged: (mouse) => {
|
|
||||||
if (pressed && root.isSeeking && activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
|
|
||||||
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
|
||||||
pendingSeekPosition = r * activePlayer.length
|
|
||||||
flatSeekDebounceTimer.restart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onClicked: (mouse) => {
|
|
||||||
if (activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
|
|
||||||
const r = Math.max(0, Math.min(1, mouse.x / parent.width))
|
|
||||||
activePlayer.position = r * activePlayer.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property real value: 0
|
property real value: 0
|
||||||
|
property real actualValue: value
|
||||||
|
property bool showActualPlaybackState: false
|
||||||
property real lineWidth: 2
|
property real lineWidth: 2
|
||||||
property real wavelength: 20
|
property real wavelength: 20
|
||||||
property real amp: 1.6
|
property real amp: 1.6
|
||||||
@@ -15,6 +17,7 @@ Item {
|
|||||||
property color trackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.40)
|
property color trackColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.40)
|
||||||
property color fillColor: Theme.primary
|
property color fillColor: Theme.primary
|
||||||
property color playheadColor: Theme.primary
|
property color playheadColor: Theme.primary
|
||||||
|
property color actualProgressColor: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.45)
|
||||||
|
|
||||||
property real dpr: (root.window ? root.window.devicePixelRatio : 1)
|
property real dpr: (root.window ? root.window.devicePixelRatio : 1)
|
||||||
function snap(v) {
|
function snap(v) {
|
||||||
@@ -22,7 +25,12 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readonly property real playX: snap(root.width * root.value)
|
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 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 {
|
||||||
@@ -65,7 +73,9 @@ Item {
|
|||||||
|
|
||||||
readonly property real startX: snap(root.lineWidth / 2)
|
readonly property real startX: snap(root.lineWidth / 2)
|
||||||
readonly property real aaBias: (0.25 / root.dpr)
|
readonly property real aaBias: (0.25 / root.dpr)
|
||||||
readonly property real endX: Math.max(startX, Math.min(root.playX - startX - aaBias, width))
|
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 {
|
Rectangle {
|
||||||
id: mask
|
id: mask
|
||||||
@@ -100,6 +110,37 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
Rectangle {
|
||||||
id: startCap
|
id: startCap
|
||||||
width: snap(root.lineWidth)
|
width: snap(root.lineWidth)
|
||||||
@@ -107,7 +148,7 @@ Item {
|
|||||||
radius: width / 2
|
radius: width / 2
|
||||||
color: root.fillColor
|
color: root.fillColor
|
||||||
x: waveClip.startX - width / 2
|
x: waveClip.startX - width / 2
|
||||||
y: root.midY - height / 2 + root.currentAmp * Math.sin((waveClip.startX / root.wavelength) * 2 * Math.PI + root.phase)
|
y: waveY(waveClip.startX) - height / 2
|
||||||
visible: waveClip.endX > waveClip.startX
|
visible: waveClip.endX > waveClip.startX
|
||||||
z: 2
|
z: 2
|
||||||
}
|
}
|
||||||
@@ -119,10 +160,34 @@ Item {
|
|||||||
radius: width / 2
|
radius: width / 2
|
||||||
color: root.fillColor
|
color: root.fillColor
|
||||||
x: waveClip.endX - width / 2
|
x: waveClip.endX - width / 2
|
||||||
y: root.midY - height / 2 + root.currentAmp * Math.sin((waveClip.endX / root.wavelength) * 2 * Math.PI + root.phase)
|
y: waveY(waveClip.endX) - height / 2
|
||||||
visible: waveClip.endX > waveClip.startX
|
visible: waveClip.endX > waveClip.startX
|
||||||
z: 2
|
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 {
|
Rectangle {
|
||||||
@@ -141,6 +206,10 @@ Item {
|
|||||||
let r = a % m;
|
let r = a % m;
|
||||||
return r < 0 ? r + m : r;
|
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)
|
readonly property real waveOffsetX: -wrapMod(phase / k, wavelength)
|
||||||
|
|
||||||
FrameAnimation {
|
FrameAnimation {
|
||||||
@@ -148,8 +217,9 @@ Item {
|
|||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (root.isPlaying)
|
if (root.isPlaying)
|
||||||
root.phase += 0.03 * frameTime * 60;
|
root.phase += 0.03 * frameTime * 60;
|
||||||
startCap.y = root.midY - startCap.height / 2 + root.currentAmp * Math.sin((waveClip.startX / root.wavelength) * 2 * Math.PI + root.phase);
|
startCap.y = waveY(waveClip.startX) - startCap.height / 2;
|
||||||
endCap.y = root.midY - endCap.height / 2 + root.currentAmp * Math.sin((waveClip.endX / root.wavelength) * 2 * Math.PI + root.phase);
|
endCap.y = waveY(waveClip.endX) - endCap.height / 2;
|
||||||
|
actualEndCap.y = waveY(waveClip.gapEndX) - actualEndCap.height / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user