feat: wallpaper switcher
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import QtQuick
|
||||
import Qt.labs.folderlistmodel
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Pipewire
|
||||
import "../.."
|
||||
|
||||
@@ -16,6 +19,8 @@ Item {
|
||||
property int menuLeft: 64
|
||||
property int menuTop: 140
|
||||
property int lineHeight: 38 + 40 + 1
|
||||
property int visibleRows: Math.max(1, Math.floor((height - menuTop - 64) / lineHeight))
|
||||
property int scrollOffset: 0
|
||||
|
||||
property int nameFontSize: 32
|
||||
property int stateFontSize: 28
|
||||
@@ -30,9 +35,14 @@ Item {
|
||||
property ShellStateManager manager: null
|
||||
property int activeSelection: 0
|
||||
property bool inBluetoothMenu: false
|
||||
property bool inWallpaperMenu: false
|
||||
|
||||
property bool isSelected: false
|
||||
|
||||
property string wallpapersDir: (Quickshell.env("HOME") || "") + "/Pictures/Wallpapers"
|
||||
property string wallpaperCachePath: (Quickshell.env("HOME") || "") + "/.cache/.wallpaper"
|
||||
property string currentWallpaperPath: ""
|
||||
|
||||
function clamp(v, lo, hi) {
|
||||
return Math.max(lo, Math.min(hi, v));
|
||||
}
|
||||
@@ -40,6 +50,8 @@ Item {
|
||||
function menuLength() {
|
||||
if (root.inBluetoothMenu && bluetoothRepeater)
|
||||
return bluetoothRepeater.count;
|
||||
if (root.inWallpaperMenu && wallpaperFolderModel)
|
||||
return wallpaperFolderModel.count;
|
||||
if (!menuModel)
|
||||
return 0;
|
||||
if (typeof menuModel.count === "function")
|
||||
@@ -69,7 +81,7 @@ Item {
|
||||
}
|
||||
|
||||
function currentAction() {
|
||||
if (root.inBluetoothMenu)
|
||||
if (root.inBluetoothMenu || root.inWallpaperMenu)
|
||||
return null;
|
||||
return menuLength() > 0 ? menuAt(activeSelection) : null;
|
||||
}
|
||||
@@ -78,9 +90,26 @@ Item {
|
||||
const length = menuLength();
|
||||
if (length === 0) {
|
||||
activeSelection = 0;
|
||||
scrollOffset = 0;
|
||||
return;
|
||||
}
|
||||
activeSelection = clamp(activeSelection, 0, length - 1);
|
||||
const maxOffset = Math.max(0, length - visibleRows);
|
||||
scrollOffset = clamp(scrollOffset, 0, maxOffset);
|
||||
}
|
||||
|
||||
function ensureVisible() {
|
||||
if (!root.inWallpaperMenu)
|
||||
return;
|
||||
const length = menuLength();
|
||||
if (length === 0) {
|
||||
scrollOffset = 0;
|
||||
return;
|
||||
}
|
||||
if (activeSelection < scrollOffset)
|
||||
scrollOffset = activeSelection;
|
||||
else if (activeSelection >= scrollOffset + visibleRows)
|
||||
scrollOffset = Math.max(0, activeSelection - visibleRows + 1);
|
||||
}
|
||||
|
||||
function bluetoothDisplayName(device) {
|
||||
@@ -105,11 +134,67 @@ Item {
|
||||
return item.device || null;
|
||||
}
|
||||
|
||||
function trimText(text) {
|
||||
return (text || "").replace(/^\s+|\s+$/g, "");
|
||||
}
|
||||
|
||||
function normalizedPath(path) {
|
||||
var value = trimText(path);
|
||||
if (value.indexOf("file://") === 0)
|
||||
value = value.slice(7);
|
||||
return value;
|
||||
}
|
||||
|
||||
function wallpaperNameAt(index) {
|
||||
if (!wallpaperFolderModel || index < 0 || index >= wallpaperFolderModel.count)
|
||||
return "";
|
||||
var name = wallpaperFolderModel.get(index, "fileName") || "";
|
||||
return name.replace(/\.[^/.]+$/, "");
|
||||
}
|
||||
|
||||
function wallpaperPathAt(index) {
|
||||
if (!wallpaperFolderModel || index < 0 || index >= wallpaperFolderModel.count)
|
||||
return "";
|
||||
return wallpaperFolderModel.get(index, "filePath") || "";
|
||||
}
|
||||
|
||||
function currentWallpaperIndex() {
|
||||
var current = normalizedPath(currentWallpaperPath);
|
||||
if (!current || !wallpaperFolderModel)
|
||||
return -1;
|
||||
for (var i = 0; i < wallpaperFolderModel.count; i++) {
|
||||
if (normalizedPath(wallpaperPathAt(i)) === current)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function refreshCurrentWallpaper() {
|
||||
root.currentWallpaperPath = normalizedPath(wallpaperFile.text());
|
||||
}
|
||||
|
||||
function applyWallpaper(path) {
|
||||
var normalized = normalizedPath(path);
|
||||
if (!normalized || normalized.length === 0)
|
||||
return;
|
||||
|
||||
root.currentWallpaperPath = normalized;
|
||||
wallpaperFile.setText(normalized + "\n");
|
||||
|
||||
wallpaperApplyProcess.running = false;
|
||||
wallpaperApplyProcess.environment = {
|
||||
"WALLPAPER_PATH": normalized
|
||||
};
|
||||
wallpaperApplyProcess.command = ["bash", "-lc", "rm -rf \"$HOME/.cache/wal\"; if [[ \"$(hostname)\" != \"gentoo\" ]]; then swww img \"$WALLPAPER_PATH\" --transition-type none; else HYPRPAPER_PID=\"$(pidof hyprpaper)\"; if [ ${#HYPRPAPER_PID} -lt 1 ]; then hyprctl dispatch exec hyprpaper; sleep 1; fi; hyprctl hyprpaper unload all; hyprctl hyprpaper preload \"$WALLPAPER_PATH\"; hyprctl hyprpaper wallpaper ,\"$WALLPAPER_PATH\"; fi"];
|
||||
wallpaperApplyProcess.running = true;
|
||||
}
|
||||
|
||||
PwObjectTracker {
|
||||
objects: [Pipewire.defaultAudioSink]
|
||||
}
|
||||
|
||||
property int bluetoothActionIndex: 1
|
||||
property int wallpaperActionIndex: 2
|
||||
property var actions: [
|
||||
{
|
||||
name: "Master Volume",
|
||||
@@ -132,6 +217,7 @@ Item {
|
||||
name: "Bluetooth",
|
||||
ent: function () {
|
||||
root.inBluetoothMenu = true;
|
||||
root.inWallpaperMenu = false;
|
||||
root.isSelected = false;
|
||||
root.activeSelection = 0;
|
||||
root.clampSelection();
|
||||
@@ -139,10 +225,55 @@ Item {
|
||||
getState: function () {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Wallpaper",
|
||||
ent: function () {
|
||||
root.inWallpaperMenu = true;
|
||||
root.inBluetoothMenu = false;
|
||||
root.isSelected = false;
|
||||
var currentIndex = root.currentWallpaperIndex();
|
||||
root.activeSelection = currentIndex >= 0 ? currentIndex : 0;
|
||||
root.scrollOffset = 0;
|
||||
root.clampSelection();
|
||||
root.ensureVisible();
|
||||
},
|
||||
getState: function () {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
property var menuModel: root.inBluetoothMenu ? Bluetooth.devices : actions
|
||||
property var menuModel: root.inBluetoothMenu ? Bluetooth.devices : (root.inWallpaperMenu ? wallpaperFolderModel : actions)
|
||||
|
||||
FolderListModel {
|
||||
id: wallpaperFolderModel
|
||||
folder: "file://" + root.wallpapersDir
|
||||
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.webp", "*.bmp", "*.gif"]
|
||||
showDirs: false
|
||||
showDotAndDotDot: false
|
||||
sortField: FolderListModel.Name
|
||||
sortReversed: false
|
||||
onCountChanged: {
|
||||
if (root.inWallpaperMenu) {
|
||||
root.clampSelection();
|
||||
root.ensureVisible();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: wallpaperFile
|
||||
path: root.wallpaperCachePath
|
||||
watchChanges: true
|
||||
onLoaded: root.refreshCurrentWallpaper()
|
||||
onFileChanged: reload()
|
||||
onLoadFailed: root.currentWallpaperPath = ""
|
||||
}
|
||||
|
||||
Process {
|
||||
id: wallpaperApplyProcess
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Bluetooth.devices
|
||||
@@ -165,8 +296,31 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: wallpaperFolderModel
|
||||
ignoreUnknownSignals: true
|
||||
function onModelReset() {
|
||||
if (root.inWallpaperMenu) {
|
||||
root.clampSelection();
|
||||
root.ensureVisible();
|
||||
}
|
||||
}
|
||||
function onRowsInserted() {
|
||||
if (root.inWallpaperMenu) {
|
||||
root.clampSelection();
|
||||
root.ensureVisible();
|
||||
}
|
||||
}
|
||||
function onRowsRemoved() {
|
||||
if (root.inWallpaperMenu) {
|
||||
root.clampSelection();
|
||||
root.ensureVisible();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.inBluetoothMenu ? "BLUETOOTH" : "CONFIG"
|
||||
text: root.inBluetoothMenu ? "BLUETOOTH" : (root.inWallpaperMenu ? "WALLPAPER" : "CONFIG")
|
||||
font.family: "8bitoperator JVE"
|
||||
font.pixelSize: 71
|
||||
renderType: Text.NativeRendering
|
||||
@@ -186,7 +340,8 @@ Item {
|
||||
width: root.width
|
||||
height: lineHeight
|
||||
x: 0
|
||||
y: menuTop + index * lineHeight
|
||||
y: menuTop + (root.inWallpaperMenu ? (index - root.scrollOffset) * lineHeight : index * lineHeight)
|
||||
visible: !root.inWallpaperMenu || (index >= root.scrollOffset && index < root.scrollOffset + root.visibleRows)
|
||||
|
||||
property var device: root.inBluetoothMenu ? modelData : null
|
||||
|
||||
@@ -202,7 +357,8 @@ Item {
|
||||
Text {
|
||||
x: 239
|
||||
y: 0
|
||||
text: root.inBluetoothMenu ? root.bluetoothDisplayName(modelData) : modelData.name
|
||||
text: root.inBluetoothMenu ? root.bluetoothDisplayName(modelData) : (root.inWallpaperMenu ? root.wallpaperNameAt(index) : modelData.name)
|
||||
width: root.width - 239 - (root.inWallpaperMenu ? 96 : 300)
|
||||
font.family: "8bitoperator JVE"
|
||||
font.pixelSize: 71
|
||||
font.letterSpacing: 1
|
||||
@@ -210,13 +366,16 @@ Item {
|
||||
font.hintingPreference: Font.PreferNoHinting
|
||||
smooth: false
|
||||
antialiasing: false
|
||||
color: (root.activeSelection == index && root.isSelected == true) ? "#fefe00" : "#ffffff"
|
||||
wrapMode: Text.NoWrap
|
||||
elide: Text.ElideRight
|
||||
color: (root.activeSelection == index && (root.isSelected == true || root.inBluetoothMenu || root.inWallpaperMenu)) ? "#fefe00" : "#ffffff"
|
||||
}
|
||||
|
||||
// Option state
|
||||
Text {
|
||||
x: menuLeft + stateColumnX
|
||||
y: 4
|
||||
visible: !root.inWallpaperMenu
|
||||
text: root.inBluetoothMenu ? root.bluetoothDisplayState(modelData) : (modelData.getState ? modelData.getState() : "")
|
||||
font.family: "8bitoperator JVE"
|
||||
font.pixelSize: 71
|
||||
@@ -225,7 +384,7 @@ Item {
|
||||
font.hintingPreference: Font.PreferNoHinting
|
||||
smooth: false
|
||||
antialiasing: false
|
||||
color: (root.activeSelection == index && root.isSelected == true) ? "#fefe00" : "#ffffff"
|
||||
color: (root.activeSelection == index && (root.isSelected == true || root.inBluetoothMenu || root.inWallpaperMenu)) ? "#fefe00" : "#ffffff"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,12 +396,16 @@ Item {
|
||||
function handleKey(key) {
|
||||
switch (key) {
|
||||
case Qt.Key_Up:
|
||||
if (root.inBluetoothMenu || root.isSelected === false)
|
||||
if (root.inBluetoothMenu || root.inWallpaperMenu || root.isSelected === false) {
|
||||
activeSelection = wrapIndex(activeSelection - 1);
|
||||
root.ensureVisible();
|
||||
}
|
||||
return true;
|
||||
case Qt.Key_Down:
|
||||
if (root.inBluetoothMenu || root.isSelected === false)
|
||||
if (root.inBluetoothMenu || root.inWallpaperMenu || root.isSelected === false) {
|
||||
activeSelection = wrapIndex(activeSelection + 1);
|
||||
root.ensureVisible();
|
||||
}
|
||||
return true;
|
||||
case Qt.Key_Left:
|
||||
{
|
||||
@@ -273,6 +436,10 @@ Item {
|
||||
}
|
||||
// device.connected = !device.connected;
|
||||
}
|
||||
} else if (root.inWallpaperMenu) {
|
||||
const wallpaperPath = wallpaperPathAt(activeSelection);
|
||||
if (wallpaperPath && wallpaperPath.length > 0)
|
||||
root.applyWallpaper(wallpaperPath);
|
||||
} else {
|
||||
const a = currentAction();
|
||||
if (a && a.ent) {
|
||||
@@ -286,7 +453,13 @@ Item {
|
||||
case Qt.Key_X:
|
||||
case Qt.Key_Shift:
|
||||
case Qt.Key_Escape:
|
||||
if (root.inBluetoothMenu) {
|
||||
if (root.inWallpaperMenu) {
|
||||
root.inWallpaperMenu = false;
|
||||
root.isSelected = false;
|
||||
root.scrollOffset = 0;
|
||||
root.activeSelection = root.wallpaperActionIndex;
|
||||
root.clampSelection();
|
||||
} else if (root.inBluetoothMenu) {
|
||||
root.inBluetoothMenu = false;
|
||||
root.isSelected = false;
|
||||
root.activeSelection = root.bluetoothActionIndex;
|
||||
@@ -307,6 +480,9 @@ Item {
|
||||
root.activeSelection = 0;
|
||||
root.isSelected = false;
|
||||
root.inBluetoothMenu = false;
|
||||
root.inWallpaperMenu = false;
|
||||
root.scrollOffset = 0;
|
||||
root.refreshCurrentWallpaper();
|
||||
ShellInputManager.registerHandler("quickSettings", handleKey);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user