Files
DankMaterialShell/quickshell/Modules/Lock/LockScrollSampler.qml
T
2026-10-27 19:20:02 +00:00

142 lines
5.7 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import qs.Services
import "../../Common/WidgetPlacement.js" as Placement
import "../../Common/LockScrollAtlas.js" as Atlas
Item {
id: root
required property var screen
readonly property string screenName: screen.name
readonly property string source: {
const path = SessionData.getMonitorWallpaper(screenName);
return path && !path.startsWith("#") ? "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/') : "";
}
readonly property string backgroundKey: LockPlacementService.backgroundKey(screenName, screen.width, screen.height)
readonly property bool needed: !IdleService.isShellLocked && LockPlacementService.usesScrollAtlas(screenName) && LockPlacementService.neededFor(screen) && !LockPlacementService.hasSample(screenName, backgroundKey)
readonly property real sampleScale: LockPlacementService.sampleSize / Math.max(1, screen.width, screen.height)
readonly property int sampleWidth: Math.max(1, Math.round(screen.width * sampleScale))
readonly property int sampleHeight: Math.max(1, Math.round(screen.height * sampleScale))
readonly property string atlasKey: JSON.stringify([source, screen.width, screen.height, Theme.lockScreenScrimAlpha, Theme.screenOffColor.toString(), SettingsData.effectiveWallpaperBackgroundColor.toString()])
property var atlas: null
readonly property bool busy: builder.active
onNeededChanged: sample()
onBackgroundKeyChanged: sample()
Component.onCompleted: LockPlacementService.setScrollSampler(screenName, true)
Component.onDestruction: LockPlacementService.setScrollSampler(screenName, false)
function sample() {
if (!needed || busy)
return;
if (atlas?.key !== atlasKey) {
builder.active = true;
return;
}
if (atlas.failed)
return;
const scroll = SessionData.getMonitorScrollPosition(screenName);
const view = Atlas.scrollWindow(atlas.imageWidth, atlas.imageHeight, screen.width, screen.height, scroll.scrollX, scroll.scrollY);
LockPlacementService.storeSample(screenName, backgroundKey, Atlas.crop(atlas.luminances, atlas.width, atlas.height, view, sampleWidth, sampleHeight), sampleWidth, sampleHeight);
}
function finish(result) {
atlas = result;
Qt.callLater(() => {
builder.active = false;
sample();
});
}
Loader {
id: builder
active: false
sourceComponent: Item {
property string key
property string url
Component.onCompleted: {
key = root.atlasKey;
url = root.source;
}
Image {
id: probe
visible: false
asynchronous: true
cache: false
source: parent.url
sourceSize: Qt.size(LockPlacementService.sampleSize * 8, LockPlacementService.sampleSize * 8)
onStatusChanged: {
if (status === Image.Error) {
root.finish({
key: parent.key,
failed: true
});
return;
}
if (status !== Image.Ready)
return;
const size = Atlas.atlasSize(implicitWidth, implicitHeight, root.screen.width, root.screen.height, root.sampleWidth, root.sampleHeight);
canvas.width = size.width;
canvas.height = size.height;
canvas.load();
}
}
Canvas {
id: canvas
readonly property string key: parent.key
readonly property string imageUrl: parent.url
opacity: 0
function load() {
if (available && width > 0)
loadImage(imageUrl, Qt.size(width, height));
}
onAvailableChanged: load()
onImageLoaded: requestPaint()
onPaint: {
if (isImageError(imageUrl)) {
root.finish({
key,
failed: true
});
return;
}
if (!isImageLoaded(imageUrl))
return;
const context = getContext("2d");
context.fillStyle = SettingsData.effectiveWallpaperBackgroundColor;
context.fillRect(0, 0, width, height);
context.drawImage(imageUrl, 0, 0, width, height);
const pixels = context.getImageData(0, 0, width, height).data;
unloadImage(imageUrl);
const scrim = Theme.screenOffColor;
const alpha = Theme.lockScreenScrimAlpha;
const blend = (value, over) => value / 255 * (1 - alpha) + over * alpha;
const luminances = new Array(width * height);
for (let i = 0; i < luminances.length; i++)
luminances[i] = Placement.luminance(blend(pixels[i * 4], scrim.r), blend(pixels[i * 4 + 1], scrim.g), blend(pixels[i * 4 + 2], scrim.b));
root.finish({
key,
imageWidth: probe.implicitWidth,
imageHeight: probe.implicitHeight,
width,
height,
luminances
});
}
}
}
}
}