mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-04 12:47:51 +02:00
274 lines
7.7 KiB
QML
274 lines
7.7 KiB
QML
import Quickshell
|
|
import Quickshell.Io
|
|
import Quickshell.Wayland
|
|
import QtQuick
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property string omarchyPath: ""
|
|
property var shell: null
|
|
property var manifest: null
|
|
|
|
readonly property string home: Quickshell.env("HOME")
|
|
readonly property string currentBackgroundLink: home + "/.config/omarchy/current/background"
|
|
|
|
property string currentBackground: ""
|
|
property string displayedBackground: ""
|
|
property string incomingBackground: ""
|
|
property string oldBackground: ""
|
|
property bool finishingTransition: false
|
|
property int backgroundVersion: 0
|
|
property real revealProgress: 1
|
|
|
|
function imageUrl(path) {
|
|
if (!path) return ""
|
|
return "file://" + path
|
|
}
|
|
|
|
function refreshBackground() {
|
|
if (!readlinkProc.running) readlinkProc.running = true
|
|
}
|
|
|
|
function setBackground(path) {
|
|
path = String(path || "").trim()
|
|
if (!path || path === currentBackground) return
|
|
currentBackground = path
|
|
backgroundVersion += 1
|
|
|
|
if (!displayedBackground) {
|
|
displayedBackground = path
|
|
revealProgress = 1
|
|
return
|
|
}
|
|
|
|
revealAnimation.stop()
|
|
finishingTransition = false
|
|
oldBackground = displayedBackground
|
|
incomingBackground = path
|
|
revealProgress = 0
|
|
}
|
|
|
|
function openSelector() {
|
|
if (!bgSwitchProc.running) bgSwitchProc.running = true
|
|
}
|
|
|
|
function openThemeSwitcher() {
|
|
if (!themeSwitchProc.running) themeSwitchProc.running = true
|
|
}
|
|
|
|
Process {
|
|
id: bgSwitchProc
|
|
command: ["bash", "-lc", "background=$(omarchy-theme-bg-switcher); [[ -n $background ]] && omarchy-theme-bg-set \"$background\""]
|
|
onExited: root.refreshBackground()
|
|
}
|
|
|
|
Process {
|
|
id: themeSwitchProc
|
|
command: ["bash", "-lc", "theme=$(omarchy-theme-switcher); [[ -n $theme ]] && omarchy-theme-set \"$theme\""]
|
|
onExited: root.refreshBackground()
|
|
}
|
|
|
|
Process {
|
|
id: readlinkProc
|
|
command: ["readlink", "-f", root.currentBackgroundLink]
|
|
stdout: StdioCollector {
|
|
onStreamFinished: root.setBackground(String(text || "").trim())
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
interval: 100
|
|
running: true
|
|
repeat: true
|
|
onTriggered: root.refreshBackground()
|
|
}
|
|
|
|
NumberAnimation {
|
|
id: revealAnimation
|
|
target: root
|
|
property: "revealProgress"
|
|
from: 0
|
|
to: 1
|
|
duration: 420
|
|
easing.type: Easing.InOutCubic
|
|
onFinished: {
|
|
if (root.incomingBackground) {
|
|
root.displayedBackground = root.incomingBackground
|
|
root.finishingTransition = true
|
|
}
|
|
root.revealProgress = 1
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: refreshBackground()
|
|
|
|
Variants {
|
|
model: Quickshell.screens
|
|
|
|
PanelWindow {
|
|
id: panel
|
|
required property var modelData
|
|
|
|
screen: modelData
|
|
visible: true
|
|
anchors { top: true; bottom: true; left: true; right: true }
|
|
color: "transparent"
|
|
property bool maskReady: false
|
|
WlrLayershell.namespace: "omarchy-background"
|
|
WlrLayershell.layer: WlrLayer.Background
|
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
exclusionMode: ExclusionMode.Ignore
|
|
|
|
Image {
|
|
id: base
|
|
anchors.fill: parent
|
|
source: root.imageUrl(root.displayedBackground)
|
|
fillMode: Image.PreserveAspectCrop
|
|
asynchronous: true
|
|
cache: false
|
|
onStatusChanged: {
|
|
if (status === Image.Ready && root.finishingTransition) {
|
|
root.incomingBackground = ""
|
|
root.oldBackground = ""
|
|
root.finishingTransition = false
|
|
}
|
|
}
|
|
}
|
|
|
|
Image {
|
|
id: incomingFrame
|
|
anchors.fill: parent
|
|
source: root.imageUrl(root.incomingBackground)
|
|
fillMode: Image.PreserveAspectCrop
|
|
asynchronous: true
|
|
cache: false
|
|
visible: root.incomingBackground !== "" && status === Image.Ready
|
|
opacity: root.revealProgress >= 1 ? 1 : 0.001
|
|
onStatusChanged: if (status === Image.Ready && root.incomingBackground) revealCanvas.prepareImage()
|
|
}
|
|
|
|
Image {
|
|
id: oldFrame
|
|
anchors.fill: parent
|
|
source: root.imageUrl(root.oldBackground)
|
|
fillMode: Image.PreserveAspectCrop
|
|
asynchronous: true
|
|
cache: false
|
|
visible: root.oldBackground !== "" && root.revealProgress < 1
|
|
onStatusChanged: if (status === Image.Ready && root.incomingBackground) revealCanvas.requestPaint()
|
|
}
|
|
|
|
Canvas {
|
|
id: revealCanvas
|
|
anchors.fill: parent
|
|
visible: root.incomingBackground !== "" && root.revealProgress < 1 && incomingFrame.status === Image.Ready && oldFrame.status === Image.Ready
|
|
opacity: panel.maskReady ? 1 : 0
|
|
renderTarget: Canvas.FramebufferObject
|
|
renderStrategy: Canvas.Immediate
|
|
|
|
readonly property real slant: -0.18
|
|
|
|
function prepareImage() {
|
|
var src = root.imageUrl(root.incomingBackground)
|
|
if (!src) return
|
|
if (isImageLoaded(src)) {
|
|
requestPaint()
|
|
} else if (!isImageLoading(src)) {
|
|
loadImage(src)
|
|
}
|
|
}
|
|
|
|
onImageLoaded: function(url) {
|
|
if (url === root.imageUrl(root.incomingBackground)) requestPaint()
|
|
}
|
|
|
|
onPaint: {
|
|
var ctx = getContext("2d")
|
|
ctx.reset()
|
|
ctx.clearRect(0, 0, width, height)
|
|
|
|
var src = root.imageUrl(root.incomingBackground)
|
|
if (!src || incomingFrame.status !== Image.Ready || root.revealProgress >= 1) return
|
|
if (!isImageLoaded(src)) {
|
|
prepareImage()
|
|
return
|
|
}
|
|
|
|
var iw = incomingFrame.sourceSize.width
|
|
var ih = incomingFrame.sourceSize.height
|
|
if (iw <= 0 || ih <= 0 || width <= 0 || height <= 0) return
|
|
|
|
var sx = 0
|
|
var sy = 0
|
|
var sw = iw
|
|
var sh = ih
|
|
if (iw / ih > width / height) {
|
|
sw = ih * width / height
|
|
sx = (iw - sw) / 2
|
|
} else {
|
|
sh = iw * height / width
|
|
sy = (ih - sh) / 2
|
|
}
|
|
|
|
var center = width / 2
|
|
var reach = width / 2 + Math.abs(slant) * height + 4
|
|
var spread = reach * root.revealProgress
|
|
var leftTop = center - spread
|
|
var leftBottom = center - spread + slant * height
|
|
var rightTop = center + spread
|
|
var rightBottom = center + spread + slant * height
|
|
|
|
ctx.save()
|
|
ctx.beginPath()
|
|
ctx.moveTo(center, 0)
|
|
ctx.lineTo(leftTop, 0)
|
|
ctx.lineTo(leftBottom, height)
|
|
ctx.lineTo(center, height)
|
|
ctx.closePath()
|
|
ctx.clip()
|
|
ctx.drawImage(src, sx, sy, sw, sh, 0, 0, width, height)
|
|
ctx.restore()
|
|
|
|
ctx.save()
|
|
ctx.beginPath()
|
|
ctx.moveTo(center, 0)
|
|
ctx.lineTo(rightTop, 0)
|
|
ctx.lineTo(rightBottom, height)
|
|
ctx.lineTo(center, height)
|
|
ctx.closePath()
|
|
ctx.clip()
|
|
ctx.drawImage(src, sx, sy, sw, sh, 0, 0, width, height)
|
|
ctx.restore()
|
|
|
|
if (!panel.maskReady && root.incomingBackground && root.revealProgress === 0) {
|
|
Qt.callLater(function() {
|
|
panel.maskReady = true
|
|
revealAnimation.restart()
|
|
})
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: root
|
|
function onRevealProgressChanged() { revealCanvas.requestPaint() }
|
|
function onIncomingBackgroundChanged() {
|
|
panel.maskReady = false
|
|
revealCanvas.prepareImage()
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
onDoubleClicked: function(mouse) {
|
|
if (mouse.button === Qt.RightButton) root.openThemeSwitcher()
|
|
else root.openSelector()
|
|
mouse.accepted = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|