Files
Domipoke 955801c666 noctalia
2026-06-25 19:58:23 +02:00

415 lines
14 KiB
QML

import "src"
import "src/Utils.js" as Utils
import qs.Commons
import qs.Widgets
import qs.Services.UI
import QtQuick
import QtMultimedia
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Qt5Compat.GraphicalEffects
import Qt.labs.folderlistmodel
PanelWindow {
id: root
property int animationCardsDuration: pluginApi?.pluginSettings?.animation_cards_duration ?? pluginApi?.manifest?.metadata?.defaultSettings?.animation_cards_duration
property int animationWindowDuration: pluginApi?.pluginSettings?.animation_window_duration ?? pluginApi?.manifest?.metadata?.defaultSettings?.animation_window_duration
property bool animateWindow: pluginApi?.pluginSettings?.animate_window ?? pluginApi?.manifest?.metadata?.defaultSettings?.animate_window ?? true
property color backgroundColor: pluginApi?.pluginSettings?.background_color ?? pluginApi?.manifest?.metadata?.defaultSettings?.background_color
property real backgroundOpacity: pluginApi?.pluginSettings?.background_opacity ?? pluginApi?.manifest?.metadata?.defaultSettings?.background_opacity
property int cardHeight: pluginApi?.pluginSettings?.card_height ?? pluginApi?.manifest?.metadata?.defaultSettings?.card_height
property int cardRadius: Style.radiusM
property int cardSpacing: pluginApi?.pluginSettings?.card_spacing ?? pluginApi?.manifest?.metadata?.defaultSettings?.card_spacing
property int cardStripWidth: pluginApi?.pluginSettings?.card_strip_width ?? pluginApi?.manifest?.metadata?.defaultSettings?.card_strip_width
property int cardsShown: pluginApi?.pluginSettings?.cards_shown ?? pluginApi?.manifest?.metadata?.defaultSettings?.cards_shown
property real centerWidthRatio: pluginApi?.pluginSettings?.center_width_ratio ?? pluginApi?.manifest?.metadata?.defaultSettings?.center_width_ratio
property int currentIndex: cardDeck.currentIndex
property string currentCardColor: filteredFiles[currentIndex]?.filterColor ?? ""
property var imageFilter: pluginApi?.manifest?.metadata?.defaultSettings?.image_filter
property var videoFilter: pluginApi?.manifest?.metadata?.defaultSettings?.video_filter
property var filteredFiles: []
property var availableColors: []
property bool hideHelp: pluginApi?.pluginSettings?.hide_help ?? pluginApi?.manifest?.metadata?.defaultSettings?.hide_help ?? true
property bool hideTopBar: pluginApi?.pluginSettings?.hide_top_bar ?? pluginApi?.manifest?.metadata?.defaultSettings?.hide_top_bar ?? false
property bool livePreview: pluginApi?.pluginSettings?.live_preview ?? pluginApi?.manifest?.metadata?.defaultSettings?.live_preview
property bool loading: thumbnailService.loading
property int pendingProcesses: 0
property var pluginApi: null
property string selectedFilter: pluginApi?.pluginSettings?.selected_filter || pluginApi?.manifest?.metadata?.defaultSettings?.selected_filter
property string selectedColorFilter: ""
property real shearFactor: pluginApi?.pluginSettings?.shear_factor ?? pluginApi?.manifest?.metadata?.defaultSettings?.shear_factor
property int thumbnailRevision: thumbnailService.thumbnailRevision
property int topBarHeight: pluginApi?.pluginSettings?.top_bar_height ?? pluginApi?.manifest?.metadata?.defaultSettings?.top_bar_height
property int topBarRadius: Style.radiusM
signal quitRequested
function applyCurrentCard() {
var f = filteredFiles[cardDeck.currentIndex];
if (!f)
return;
applicant.command = ["bash", "-c", Utils.wallpaperCommand(f)];
applicant.running = true;
var wallpaperPath = f.isVideo ? f.thumbnail : f.filePath;
WallpaperService.changeWallpaper(wallpaperPath);
}
function applyFilterToFiles() {
var currentFile = filteredFiles[cardDeck.currentIndex]?.filePath ?? "";
var all = thumbnailService.files ?? [];
var result = all;
if (selectedFilter === "images")
result = result.filter(f => !f.isVideo);
else if (selectedFilter === "videos")
result = result.filter(f => f.isVideo);
var colors = {};
for (var c = 0; c < result.length; c++)
colors[result[c].filterColor] = true;
availableColors = Object.keys(colors);
if (selectedColorFilter !== "")
result = result.filter(f => f.filterColor === selectedColorFilter);
filteredFiles = result;
var newIdx = 0;
if (currentFile !== "") {
for (var i = 0; i < result.length; i++) {
if (result[i].filePath === currentFile) {
newIdx = i;
break;
}
}
}
cardDeck.jumpTo(newIdx);
}
function close() {
if (exitAnimation.running)
return;
if (root.animateWindow) {
exitAnimation.start();
} else {
root.quitRequested();
}
}
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.layer: WlrLayer.Overlay
aboveWindows: true
color: "transparent"
exclusionMode: "Ignore"
exclusiveZone: 0
implicitHeight: screen.height
implicitWidth: screen.width
screen: pluginApi.panelOpenScreen
onCurrentIndexChanged: {
if (root.livePreview)
applyCurrentCard();
}
onSelectedFilterChanged: applyFilterToFiles()
onSelectedColorFilterChanged: applyFilterToFiles()
onVisibleChanged: {
if (visible) {
if (root.animateWindow) {
enterAnimation.start();
} else {
content.opacity = 1;
content.scale = 1;
background.opacity = root.backgroundOpacity;
}
}
}
Process {
id: applicant
command: []
running: false
}
Connections {
function onFilesChanged() {
root.applyFilterToFiles();
}
target: thumbnailService
}
ThumbnailService {
id: thumbnailService
cacheDir: root.pluginApi?.Settings.cacheDir + "/wallcards"
imageFilter: root.imageFilter
videoFilter: root.videoFilter
wallpaperDir: root.pluginApi?.Settings.data.wallpaper.directory
}
ParallelAnimation {
id: enterAnimation
NumberAnimation {
duration: root.animationWindowDuration
easing.type: Easing.OutCubic
from: 0.85
property: "scale"
target: content
to: 1.0
}
NumberAnimation {
duration: root.animationWindowDuration
easing.type: Easing.OutCubic
from: 0
property: "opacity"
target: content
to: 1
}
NumberAnimation {
duration: root.animationWindowDuration
easing.type: Easing.OutCubic
from: 0
property: "opacity"
target: background
to: root.backgroundOpacity
}
}
ParallelAnimation {
id: exitAnimation
property int duration: root.animationWindowDuration * 0.4
onFinished: root.quitRequested()
NumberAnimation {
duration: exitAnimation.duration
easing.type: Easing.InCubic
property: "opacity"
target: content
to: 0
}
NumberAnimation {
duration: exitAnimation.duration
easing.type: Easing.InCubic
property: "scale"
target: content
to: 0.85
}
NumberAnimation {
duration: exitAnimation.duration
easing.type: Easing.InCubic
property: "opacity"
target: background
to: 0
}
}
Rectangle {
id: background
anchors.fill: parent
color: root.backgroundColor
opacity: 0
MouseArea {
anchors.fill: parent
onClicked: root.close()
}
}
Item {
id: content
anchors.fill: parent
focus: true
opacity: 0
scale: 0.85
Keys.onPressed: event => {
if (event.modifiers & Qt.ShiftModifier) {
if (event.key === Qt.Key_H) {
root.cardHeight = Math.max(root.cardHeight - 10, parent.height * 0.10);
event.accepted = true;
return;
} else if (event.key === Qt.Key_L) {
root.cardHeight = Math.min(root.cardHeight + 10, parent.height * 0.75);
event.accepted = true;
return;
} else if (event.key === Qt.Key_J) {
cardDeck.centerWidthRatio = Math.max(cardDeck.centerWidthRatio - 0.01, 0.2);
event.accepted = true;
return;
} else if (event.key === Qt.Key_K) {
cardDeck.centerWidthRatio = Math.min(cardDeck.centerWidthRatio + 0.01, 0.6);
event.accepted = true;
return;
} else if (event.key === Qt.Key_N) {
root.cardsShown = Math.max(root.cardsShown - 2, 5);
event.accepted = true;
return;
} else if (event.key === Qt.Key_P) {
root.cardsShown = Math.min(root.cardsShown + 2, 15);
event.accepted = true;
return;
}
}
if (event.modifiers & Qt.ControlModifier) {
if (event.key === Qt.Key_K) {
root.cardSpacing = Math.max(root.cardSpacing + 2, 0);
event.accepted = true;
return;
} else if (event.key === Qt.Key_J) {
root.cardSpacing = Math.max(root.cardSpacing - 2, 0);
event.accepted = true;
return;
} else if (event.key === Qt.Key_L) {
root.cardStripWidth = Math.min(root.cardStripWidth + 5, 300);
event.accepted = true;
return;
} else if (event.key === Qt.Key_H) {
root.cardStripWidth = Math.max(root.cardStripWidth - 5, 20);
event.accepted = true;
return;
} else if (event.key === Qt.Key_S) {
root.pluginApi.pluginSettings.card_height = root.cardHeight;
root.pluginApi.pluginSettings.center_width_ratio = cardDeck.centerWidthRatio;
root.pluginApi.pluginSettings.cards_shown = root.cardsShown;
root.pluginApi.pluginSettings.card_spacing = root.cardSpacing;
root.pluginApi.pluginSettings.card_strip_width = root.cardStripWidth;
root.pluginApi.pluginSettings.hide_top_bar = root.hideTopBar;
root.pluginApi.pluginSettings.live_preview = root.livePreview;
root.pluginApi.pluginSettings.selected_filter = root.selectedFilter;
root.pluginApi.pluginSettings.hide_help = root.hideHelp;
root.pluginApi.pluginSettings.animate_window = root.animateWindow;
root.pluginApi.saveSettings();
event.accepted = true;
return;
}
}
function handleKey(event, bindings) {
const action = bindings[event.key];
if (action) {
action();
event.accepted = true;
}
}
const bindings = {
[Qt.Key_Question]: () => sideBar.expanded = !sideBar.expanded,
[Qt.Key_Q]: () => root.close(),
[Qt.Key_Return]: () => {
root.applyCurrentCard();
root.close();
},
[Qt.Key_Space]: () => root.applyCurrentCard(),
[Qt.Key_Escape]: () => root.close(),
[Qt.Key_A]: () => root.selectedFilter = "all",
[Qt.Key_I]: () => root.selectedFilter = "images",
[Qt.Key_V]: () => root.selectedFilter = "videos",
[Qt.Key_T]: () => root.hideTopBar = !root.hideTopBar,
[Qt.Key_F]: () => {
if (root.selectedColorFilter !== "")
root.selectedColorFilter = "";
else
root.selectedColorFilter = root.currentCardColor;
},
[Qt.Key_R]: () => {
cardDeck.randomJump();
topBar.flashShuffle();
},
[Qt.Key_P]: () => root.livePreview = !root.livePreview,
[Qt.Key_K]: () => cardDeck.navigateTo(cardDeck.currentIndex + 1),
[Qt.Key_J]: () => cardDeck.navigateTo(cardDeck.currentIndex - 1),
[Qt.Key_L]: () => cardDeck.navigateTo(cardDeck.currentIndex + root.cardsShown - 2),
[Qt.Key_H]: () => cardDeck.navigateTo(cardDeck.currentIndex - root.cardsShown + 2)
};
const action = bindings[event.key];
if (action) {
action();
event.accepted = true;
}
}
LoadingBar {
id: loadingBar
anchors.centerIn: parent
pending: thumbnailService.pendingProcesses
total: thumbnailService.fileCount
}
TopBar {
id: topBar
anchors.bottom: cardDeck.top
anchors.bottomMargin: root.topBarHeight / 3
anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenterOffset: shearFactor * -root.topBarHeight * 4 / 3
animationDuration: root.animationCardsDuration
availableColors: root.availableColors
colorOrder: thumbnailService.colorOrder
colorOrderColors: thumbnailService.colorOrderColors
currentCardColor: root.currentCardColor
currentIndex: cardDeck.currentIndex
filteredCount: root.filteredFiles.length
height: root.topBarHeight
livePreview: root.livePreview
pluginApi: root.pluginApi
radius: root.topBarRadius
selectedColorFilter: root.selectedColorFilter
selectedFilter: root.selectedFilter
shearFactor: root.shearFactor
visible: !loadingBar.visible && !root.hideTopBar
width: cardDeck.width
onColorFilterSelected: key => root.selectedColorFilter = key
onFilterSelected: key => root.selectedFilter = key
onLivePreviewToggled: root.livePreview = !root.livePreview
onShuffleRequested: cardDeck.randomJump()
}
SideBar {
id: sideBar
anchors.left: parent.left
anchors.leftMargin: Style.marginL
anchors.verticalCenter: parent.verticalCenter
hideHelp: root.hideHelp
visible: !loadingBar.visible
}
CardDeck {
id: cardDeck
anchors.centerIn: parent
animationDuration: root.animationCardsDuration
cardRadius: root.cardRadius
cardSpacing: root.cardSpacing
cardStripWidth: root.cardStripWidth
cardsShown: root.cardsShown
centerWidthRatio: root.centerWidthRatio
filteredCount: root.filteredFiles.length
filteredModel: root.filteredFiles
height: root.cardHeight
shearFactor: root.shearFactor
visible: !loadingBar.visible
onApplyRequested: root.applyCurrentCard()
}
}
}