198 lines
7.2 KiB
QML
198 lines
7.2 KiB
QML
// WallpaperPopup.qml - A popup for browsing and setting wallpapers from /usr/share/wallpapers
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import Quickshell.Wayland
|
|
import ".."
|
|
|
|
PanelWindow {
|
|
id: root
|
|
required property var screen
|
|
|
|
property bool logicalOpen: GlobalStates.popups.active === "wallpaper"
|
|
property bool _keepVisible: false
|
|
visible: logicalOpen || _keepVisible
|
|
|
|
onLogicalOpenChanged: {
|
|
if (logicalOpen) {
|
|
closeTimer.stop()
|
|
_keepVisible = false
|
|
if (wallpaperModel.count === 0) scanProc.running = true
|
|
openY.start()
|
|
openOpacity.start()
|
|
focusTimer.start()
|
|
} else {
|
|
_keepVisible = true
|
|
closeY.start()
|
|
closeOpacity.start()
|
|
closeTimer.restart()
|
|
}
|
|
}
|
|
|
|
Timer { id: closeTimer; interval: 280; onTriggered: root._keepVisible = false }
|
|
Timer { id: focusTimer; interval: 50; onTriggered: grid.forceActiveFocus() }
|
|
|
|
WlrLayershell.layer: WlrLayer.Overlay
|
|
WlrLayershell.namespace: "quickshell-popup-wallpaper"
|
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
|
anchors { top: true; left: true; right: true; bottom: true }
|
|
color: Qt.rgba(0, 0, 0, 0)
|
|
|
|
MouseArea { anchors.fill: parent; onClicked: GlobalStates.popups.close() }
|
|
|
|
NumberAnimation { id: openY; target: box; property: "y"; to: 1; duration: 250; easing.type: Easing.OutCubic }
|
|
NumberAnimation { id: closeY; target: box; property: "y"; to: -340; duration: 200; easing.type: Easing.InCubic }
|
|
NumberAnimation { id: openOpacity; target: box; property: "opacity"; to: Theme.popupOpacity; duration: 200 }
|
|
NumberAnimation { id: closeOpacity; target: box; property: "opacity"; to: 0.0; duration: 200 }
|
|
|
|
// ── Named-pipe IPC ────────────────────────────────────────
|
|
// Trigger from Hyprland keybind: echo 1 > /tmp/qs-wallpaper-ipc
|
|
// Trigger from another QML popup: Exec.run(["bash", "-c", "echo 1 > /tmp/qs-wallpaper-ipc"])
|
|
Process {
|
|
id: ipcReader
|
|
running: true
|
|
command: ["bash", "-c",
|
|
"rm -f /tmp/qs-wallpaper-ipc && " +
|
|
"mkfifo /tmp/qs-wallpaper-ipc && " +
|
|
"while true; do cat /tmp/qs-wallpaper-ipc; done"]
|
|
stdout: SplitParser {
|
|
onRead: () => GlobalStates.popups.toggle("wallpaper", 0)
|
|
}
|
|
onExited: running = true
|
|
}
|
|
|
|
// ── Wallpaper scanner ─────────────────────────────────────
|
|
ListModel { id: wallpaperModel }
|
|
|
|
Process {
|
|
id: scanProc
|
|
running: false
|
|
command: ["bash", "-c",
|
|
"find /usr/share/wallpapers -type f " +
|
|
"\\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \\) " +
|
|
"| sort"]
|
|
stdout: SplitParser {
|
|
onRead: (line) => {
|
|
var p = line.trim()
|
|
if (p) wallpaperModel.append({ path: p })
|
|
}
|
|
}
|
|
}
|
|
|
|
Process { id: setProc; running: false }
|
|
|
|
// ── Popup box ─────────────────────────────────────────────
|
|
Rectangle {
|
|
id: box
|
|
|
|
// anchorX = 0 → centered; anchorX > 0 → centered under widget
|
|
x: {
|
|
var ax = GlobalStates.popups.anchorX
|
|
var cx = ax > 0 ? ax - width / 2 : parent.width / 2 - width / 2
|
|
return Math.max(4, Math.min(parent.width - width - 4, cx))
|
|
}
|
|
y: -340
|
|
opacity: 0.0
|
|
|
|
width: 324
|
|
height: 500
|
|
|
|
color: Theme.popupBackground
|
|
radius: Theme.radius * 2
|
|
clip: true
|
|
border.color: Theme.popupBorderColor
|
|
border.width: Theme.popupBorderWidth
|
|
|
|
MouseArea { anchors.fill: parent }
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
visible: wallpaperModel.count === 0
|
|
text: "Scanning..."
|
|
font { family: Theme.fontSans; pixelSize: Theme.fontSize }
|
|
color: Theme.pillText
|
|
opacity: 0.5
|
|
}
|
|
|
|
GridView {
|
|
id: grid
|
|
anchors { fill: parent; margins: 8 }
|
|
clip: true
|
|
cellWidth: Math.floor(width / 2)
|
|
cellHeight: 104
|
|
focus: true
|
|
keyNavigationEnabled: true
|
|
currentIndex: 0
|
|
|
|
Keys.onReturnPressed: {
|
|
var item = wallpaperModel.get(grid.currentIndex)
|
|
if (item) {
|
|
setProc.command = ["waypaper", "--wallpaper", item.path]
|
|
setProc.running = true
|
|
GlobalStates.popups.close()
|
|
}
|
|
}
|
|
Keys.onEscapePressed: GlobalStates.popups.close()
|
|
|
|
model: wallpaperModel
|
|
|
|
highlight: Rectangle {
|
|
z: 2
|
|
radius: Theme.radius + 1
|
|
color: "transparent"
|
|
border.color: Theme.accent ?? "#cba6f7"
|
|
border.width: 2
|
|
}
|
|
highlightFollowsCurrentItem: true
|
|
highlightMoveDuration: 80
|
|
|
|
delegate: Item {
|
|
required property string path
|
|
required property int index
|
|
|
|
width: grid.cellWidth - 4
|
|
height: 100
|
|
|
|
Rectangle {
|
|
anchors { fill: parent; margins: 2 }
|
|
radius: Theme.radius
|
|
color: "black"
|
|
clip: true
|
|
|
|
Image {
|
|
anchors.fill: parent
|
|
source: "file://" + path
|
|
fillMode: Image.PreserveAspectCrop
|
|
sourceSize: Qt.size(152, 96)
|
|
smooth: true
|
|
asynchronous: true
|
|
opacity: status === Image.Ready ? 1.0 : 0.0
|
|
Behavior on opacity { NumberAnimation { duration: 150 } }
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Theme.radius
|
|
color: hover.containsMouse ? Qt.rgba(1, 1, 1, 0.2) : "transparent"
|
|
Behavior on color { ColorAnimation { duration: 100 } }
|
|
}
|
|
|
|
MouseArea {
|
|
id: hover
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
grid.currentIndex = index
|
|
setProc.command = ["waypaper", "--wallpaper", path]
|
|
setProc.running = true
|
|
GlobalStates.popups.close()
|
|
}
|
|
onEntered: grid.currentIndex = index
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|