feat: implement wallpaper cycling with user controls in sidebar
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Constants
|
||||
import qs.Services
|
||||
|
||||
RowLayout {
|
||||
implicitHeight: wallpaperImage.implicitHeight
|
||||
spacing: Style.marginS
|
||||
|
||||
UImageRounded {
|
||||
id: wallpaperImage
|
||||
|
||||
Layout.fillWidth: true
|
||||
height: Style.baseWidgetSize * 3.2 + Style.marginS * 3
|
||||
radius: Style.radiusM
|
||||
imagePath: BackgroundService.cachedPath
|
||||
fallbackIcon: "wallpaper"
|
||||
layer.enabled: true
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: BackgroundService.toggleChooser()
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
width: Style.marginS
|
||||
height: parent.height * WallpaperCycle.timeUntilNextCycle / WallpaperCycle.cycleInterval
|
||||
color: Colors.mPrimary
|
||||
radius: Style.radiusS
|
||||
}
|
||||
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
blurMax: Style.shadowBlurMax
|
||||
shadowBlur: Style.shadowBlur
|
||||
shadowOpacity: Style.shadowOpacity
|
||||
shadowColor: Colors.mShadow
|
||||
shadowHorizontalOffset: Style.shadowHorizontalOffset
|
||||
shadowVerticalOffset: Style.shadowVerticalOffset
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: buttonsColumn
|
||||
|
||||
spacing: Style.marginS
|
||||
|
||||
UIconButton {
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
iconName: "player-track-prev"
|
||||
colorFg: Colors.mBlue
|
||||
onClicked: WallpaperCycle.applyPrev()
|
||||
}
|
||||
|
||||
UIconButton {
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
iconName: "player-track-next"
|
||||
colorFg: Colors.mYellow
|
||||
onClicked: WallpaperCycle.applyNext()
|
||||
}
|
||||
|
||||
UIconButton {
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
iconName: WallpaperCycle.enabled ? "player-pause" : "player-play"
|
||||
colorFg: WallpaperCycle.enabled ? Colors.mGreen : Colors.mRed
|
||||
alwaysHover: !WallpaperCycle.enabled
|
||||
onClicked: WallpaperCycle.playPause()
|
||||
}
|
||||
|
||||
UIconButton {
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
iconName: WallpaperCycle.shuffle ? "arrows-shuffle" : "repeat"
|
||||
colorFg: Colors.mPurple
|
||||
alwaysHover: WallpaperCycle.shuffle
|
||||
onClicked: WallpaperCycle.toggleShuffle()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -69,6 +69,10 @@ Variants {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
WallpaperCard {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NotificationNoteToggleCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Constants
|
||||
import qs.Services
|
||||
import qs.Utils
|
||||
@@ -56,19 +57,24 @@ Singleton {
|
||||
if (!exists)
|
||||
return ;
|
||||
|
||||
loadWallpaperDebouncer.pendingPath = path;
|
||||
SettingsService.backgroundPath = path;
|
||||
loadWallpaperDebouncer.start();
|
||||
});
|
||||
}
|
||||
|
||||
function toggleChooser() {
|
||||
if (wallreelProcess.running)
|
||||
wallreelProcess.signal(2);
|
||||
else
|
||||
wallreelProcess.running = true;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
loadWallpaperDebouncer.pendingPath = SettingsService.backgroundPath;
|
||||
loadWallpaperDebouncer.start();
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onBackgroundPathChanged() {
|
||||
loadWallpaperDebouncer.pendingPath = SettingsService.backgroundPath;
|
||||
loadWallpaperDebouncer.start();
|
||||
}
|
||||
|
||||
@@ -78,15 +84,19 @@ Singleton {
|
||||
Timer {
|
||||
id: loadWallpaperDebouncer
|
||||
|
||||
property string pendingPath: ""
|
||||
|
||||
interval: 200
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
SettingsService.backgroundPath = pendingPath;
|
||||
root.loadBackground();
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: wallreelProcess
|
||||
|
||||
running: false
|
||||
command: ["wallreel"]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@ Singleton {
|
||||
property alias location: adapter.location
|
||||
property alias backgroundPath: adapter.backgroundPath
|
||||
property alias wifiEnabled: adapter.wifiEnabled
|
||||
property alias cycleWallpapers: adapter.cycleWallpapers
|
||||
property alias cycleWallpapers: cycleSettings.wallpapers
|
||||
property alias cycleShuffle: cycleSettings.shuffle
|
||||
property alias cycleInterval: cycleSettings.interval
|
||||
property alias cycleEnabled: cycleSettings.enabled
|
||||
|
||||
FileView {
|
||||
id: settingFile
|
||||
@@ -35,7 +38,14 @@ Singleton {
|
||||
property string location: "New York"
|
||||
property string backgroundPath: ""
|
||||
property bool wifiEnabled: true
|
||||
property list<string> cycleWallpapers: []
|
||||
property JsonObject cycle: JsonObject {
|
||||
id: cycleSettings
|
||||
|
||||
property list<string> wallpapers: []
|
||||
property bool shuffle: false
|
||||
property int interval: 900
|
||||
property bool enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,25 +7,133 @@ pragma Singleton
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property int cycleInterval: 900 // in seconds
|
||||
property var wallpapers: SettingsService.cycleWallpapers
|
||||
property int cycleInterval: SettingsService.cycleInterval
|
||||
property list<string> wallpapers: []
|
||||
property bool enabled: SettingsService.cycleEnabled
|
||||
property bool shuffle: SettingsService.cycleShuffle
|
||||
property int timeUntilNextCycle: SettingsService.cycleInterval
|
||||
property double nextCycleDeadlineMs: 0
|
||||
property int _startIndex: 0
|
||||
|
||||
function applyNext() {
|
||||
if (root.wallpapers.length === 0) {
|
||||
Logger.w("WallpaperCycle", "No wallpapers to cycle through, skipping.");
|
||||
return ;
|
||||
Connections {
|
||||
target: SettingsService
|
||||
|
||||
function onCycleWallpapersChanged() {
|
||||
_initPaths();
|
||||
_initTimer();
|
||||
}
|
||||
|
||||
function onCycleEnabledChanged() {
|
||||
_initTimer();
|
||||
}
|
||||
|
||||
function onCycleShuffleChanged() {
|
||||
_initPaths();
|
||||
_initTimer();
|
||||
}
|
||||
|
||||
function onCycleIntervalChanged() {
|
||||
if (enabled && cycleTimer.running) {
|
||||
_scheduleCycle(root.cycleInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
_initPaths();
|
||||
_initTimer();
|
||||
}
|
||||
|
||||
function _initPaths() {
|
||||
if (!shuffle) {
|
||||
wallpapers = SettingsService.cycleWallpapers;
|
||||
} else {
|
||||
wallpapers = _shuffle(SettingsService.cycleWallpapers);
|
||||
}
|
||||
Logger.d("WallpaperCycle", "Initialized wallpapers: " + wallpapers.length + " paths" + (shuffle ? " (shuffled)" : ""));
|
||||
}
|
||||
|
||||
function _initTimer() {
|
||||
if (enabled && root.wallpapers.length > 0) {
|
||||
const remainingSeconds = Math.max(1, nextCycleDeadlineMs > 0
|
||||
? _secondsUntilDeadline()
|
||||
: root.timeUntilNextCycle);
|
||||
_scheduleCycle(remainingSeconds);
|
||||
} else {
|
||||
if (cycleTimer.running && nextCycleDeadlineMs > 0) {
|
||||
timeUntilNextCycle = _secondsUntilDeadline();
|
||||
}
|
||||
cycleTimer.stop();
|
||||
const current = SettingsService.backgroundPath;
|
||||
let index = -1;
|
||||
if (current) {
|
||||
for (let i = 0; i < root.wallpapers.length; i++) {
|
||||
if (root.wallpapers[i] === current) {
|
||||
index = i;
|
||||
break;
|
||||
nextCycleDeadlineMs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function _scheduleCycle(secondsFromNow) {
|
||||
const seconds = Math.max(1, Math.floor(secondsFromNow));
|
||||
timeUntilNextCycle = seconds;
|
||||
nextCycleDeadlineMs = Date.now() + (seconds * 1000);
|
||||
cycleTimer.interval = seconds * 1000;
|
||||
cycleTimer.stop();
|
||||
cycleTimer.start();
|
||||
Logger.d("WallpaperCycle", "Scheduled next cycle in " + seconds + " seconds.");
|
||||
}
|
||||
|
||||
function _secondsUntilDeadline() {
|
||||
return Math.max(0, Math.ceil((nextCycleDeadlineMs - Date.now()) / 1000));
|
||||
}
|
||||
|
||||
function _shuffle(paths) {
|
||||
const shuffled = paths.slice();
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
function playPause() {
|
||||
SettingsService.cycleEnabled = !SettingsService.cycleEnabled;
|
||||
}
|
||||
|
||||
function toggleShuffle() {
|
||||
SettingsService.cycleShuffle = !SettingsService.cycleShuffle;
|
||||
}
|
||||
|
||||
function applyPrev(resetCountdown) {
|
||||
if (resetCountdown === undefined)
|
||||
resetCountdown = true;
|
||||
|
||||
if (root.wallpapers.length === 0) {
|
||||
Logger.w("WallpaperCycle", "No wallpapers to cycle through, skipping.");
|
||||
return false;
|
||||
}
|
||||
cycleTimer.stop();
|
||||
let index = _findIndex(SettingsService.backgroundPath);
|
||||
if (index === -1) {
|
||||
Logger.w("WallpaperCycle", "Current wallpaper not found in cycle list, starting from the end.");
|
||||
index = root.wallpapers.length - 1;
|
||||
} else {
|
||||
index = (index - 1 + root.wallpapers.length) % root.wallpapers.length;
|
||||
}
|
||||
const prevWallpaper = root.wallpapers[index];
|
||||
Logger.i("WallpaperCycle", "Cycling to previous wallpaper: " + prevWallpaper);
|
||||
_apply(prevWallpaper);
|
||||
if (enabled && resetCountdown) {
|
||||
_scheduleCycle(root.cycleInterval);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function applyNext(resetCountdown) {
|
||||
if (resetCountdown === undefined)
|
||||
resetCountdown = true;
|
||||
|
||||
if (root.wallpapers.length === 0) {
|
||||
Logger.w("WallpaperCycle", "No wallpapers to cycle through, skipping.");
|
||||
return false;
|
||||
}
|
||||
cycleTimer.stop();
|
||||
let index = _findIndex(SettingsService.backgroundPath);
|
||||
if (index === -1) {
|
||||
Logger.w("WallpaperCycle", "Current wallpaper not found in cycle list, starting from the beginning.");
|
||||
index = 0;
|
||||
@@ -35,21 +143,48 @@ Singleton {
|
||||
const nextWallpaper = root.wallpapers[index];
|
||||
Logger.i("WallpaperCycle", "Cycling to next wallpaper: " + nextWallpaper);
|
||||
_apply(nextWallpaper);
|
||||
cycleTimer.start();
|
||||
if (enabled && resetCountdown) {
|
||||
_scheduleCycle(root.cycleInterval);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function _apply(path) {
|
||||
Quickshell.execDetached(["sh", "-c", "wallreel -a '" + path + "'"]);
|
||||
Logger.d("WallpaperCycle", "Applying wallpaper: " + path);
|
||||
Quickshell.execDetached(["wallreel", "-a", path]);
|
||||
}
|
||||
|
||||
function _findIndex(path) {
|
||||
for (let i = 0; i < root.wallpapers.length; i++) {
|
||||
if (root.wallpapers[i] === path)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: cycleTimer
|
||||
|
||||
running: true
|
||||
repeat: true
|
||||
interval: root.cycleInterval * 1000
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.applyNext();
|
||||
const applied = root.applyNext(false);
|
||||
if (applied && root.enabled) {
|
||||
_scheduleCycle(root.cycleInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: updateTimeTimer
|
||||
|
||||
running: cycleTimer.running
|
||||
repeat: true
|
||||
interval: 1000
|
||||
onTriggered: {
|
||||
if (nextCycleDeadlineMs > 0) {
|
||||
timeUntilNextCycle = _secondsUntilDeadline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user