mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-28 22:17:48 -04:00
1 parent
2804183b7f
commit
ee6e941459
8 files changed
+240
-7
No files matched your search
@@ -0,0 +1,35 @@
|
||||
.pragma library
|
||||
|
||||
function scrollWindow(imageWidth, imageHeight, screenWidth, screenHeight, scrollX, scrollY) {
|
||||
const scale = Math.max(screenWidth / imageWidth, screenHeight / imageHeight);
|
||||
const width = screenWidth / (imageWidth * scale);
|
||||
const height = screenHeight / (imageHeight * scale);
|
||||
const imageAspect = imageWidth / imageHeight;
|
||||
const screenAspect = screenWidth / screenHeight;
|
||||
return {
|
||||
x: imageAspect > screenAspect + 0.01 ? (1 - width) * scrollX / 100 : (1 - width) / 2,
|
||||
y: imageAspect < screenAspect - 0.01 ? (1 - height) * scrollY / 100 : (1 - height) / 2,
|
||||
width,
|
||||
height
|
||||
};
|
||||
}
|
||||
|
||||
function atlasSize(imageWidth, imageHeight, screenWidth, screenHeight, sampleWidth, sampleHeight) {
|
||||
const view = scrollWindow(imageWidth, imageHeight, screenWidth, screenHeight, 0, 0);
|
||||
return {
|
||||
width: Math.ceil(sampleWidth / view.width),
|
||||
height: Math.ceil(sampleHeight / view.height)
|
||||
};
|
||||
}
|
||||
|
||||
function crop(atlas, atlasWidth, atlasHeight, view, sampleWidth, sampleHeight) {
|
||||
const result = new Array(sampleWidth * sampleHeight);
|
||||
for (let y = 0; y < sampleHeight; y++) {
|
||||
const row = Math.min(atlasHeight - 1, Math.floor((view.y + (y + 0.5) / sampleHeight * view.height) * atlasHeight));
|
||||
for (let x = 0; x < sampleWidth; x++) {
|
||||
const column = Math.min(atlasWidth - 1, Math.floor((view.x + (x + 0.5) / sampleWidth * view.width) * atlasWidth));
|
||||
result[y * sampleWidth + x] = atlas[row * atlasWidth + column];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import qs.Services
|
||||
import "../../Common/WidgetPlacement.js" as Placement
|
||||
|
||||
Item {
|
||||
@@ -12,7 +13,7 @@ Item {
|
||||
property string capturedKey: ""
|
||||
property var capture: null
|
||||
readonly property size sourceSize: Qt.size(sourceItem.width, sourceItem.height)
|
||||
readonly property real sampleScale: 128 / Math.max(1, sourceItem.width, sourceItem.height)
|
||||
readonly property real sampleScale: LockPlacementService.sampleSize / Math.max(1, sourceItem.width, sourceItem.height)
|
||||
readonly property int sampleWidth: Math.max(1, Math.round(sourceItem.width * sampleScale))
|
||||
readonly property int sampleHeight: Math.max(1, Math.round(sourceItem.height * sampleScale))
|
||||
readonly property bool sourceVisible: sourceItem.visible && (sourceItem.Window.window?.visible ?? false)
|
||||
|
||||
@@ -19,7 +19,7 @@ Variants {
|
||||
|
||||
Loader {
|
||||
id: preparation
|
||||
active: scope.sampling || (!IdleService.isShellLocked && LockPlacementService.neededFor(scope.modelData) && scope.failedKey !== scope.backgroundKey && !LockPlacementService.hasSample(scope.modelData.name, scope.backgroundKey))
|
||||
active: scope.sampling || (!IdleService.isShellLocked && !LockPlacementService.usesScrollAtlas(scope.modelData.name) && LockPlacementService.neededFor(scope.modelData) && scope.failedKey !== scope.backgroundKey && !LockPlacementService.hasSample(scope.modelData.name, scope.backgroundKey))
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
@@ -43,6 +43,7 @@ Variants {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
screenName: scope.modelData.name
|
||||
asynchronous: true
|
||||
}
|
||||
|
||||
LockBackgroundSampler {
|
||||
|
||||
@@ -11,6 +11,7 @@ import qs.DCommon.Session
|
||||
Item {
|
||||
id: root
|
||||
property string screenName: ""
|
||||
property bool asynchronous: false
|
||||
readonly property bool hasCustomWallpaper: SettingsData.lockScreenWallpaperPath !== ""
|
||||
|
||||
function encodeFileUrl(path) {
|
||||
@@ -35,7 +36,7 @@ Item {
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||
return !currentWallpaper || (currentWallpaper && currentWallpaper.startsWith("#"));
|
||||
}
|
||||
asynchronous: false
|
||||
asynchronous: root.asynchronous
|
||||
|
||||
sourceComponent: DBackdrop {
|
||||
screenName: root.screenName
|
||||
@@ -64,7 +65,7 @@ Item {
|
||||
readonly property size decodeSize: Qt.size(Math.round(width * screenScale), Math.round(height * screenScale))
|
||||
|
||||
active: wallpaperSource !== "" && width > 0 && height > 0
|
||||
asynchronous: false
|
||||
asynchronous: root.asynchronous
|
||||
|
||||
sourceComponent: fillModeName === "Scrolling" ? scrollWallpaperComp : plainWallpaperComp
|
||||
|
||||
@@ -95,7 +96,7 @@ Item {
|
||||
fillMode: Theme.getFillMode(wallpaperBackground.fillModeName)
|
||||
smooth: true
|
||||
cache: true
|
||||
asynchronous: false
|
||||
asynchronous: root.asynchronous
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +110,7 @@ Item {
|
||||
visible: false
|
||||
source: wallpaperBackground.wallpaperSource
|
||||
sourceSize: wallpaperBackground.decodeSize
|
||||
asynchronous: false
|
||||
asynchronous: root.asynchronous
|
||||
cache: true
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
import qs.Modules.Lock
|
||||
import "../Common/WorkspaceModel.js" as WorkspaceModel
|
||||
|
||||
Variants {
|
||||
@@ -177,7 +178,7 @@ Variants {
|
||||
readonly property var backingWindow: Window.window
|
||||
readonly property bool showsBackdrop: !source || isColorSource || currentWallpaper.status === Image.Error
|
||||
readonly property bool backdropBusy: showsBackdrop && !(backdropLoader.item?.ready ?? false)
|
||||
readonly property bool renderActive: backdropBusy || effectActive || overviewBlurActive || pendingWallpaper !== "" || _deferredSource !== "" || changePending || _freezeWaitFrames > 0 || frameAnim.running || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading || parallaxImage.status === Image.Loading
|
||||
readonly property bool renderActive: backdropBusy || effectActive || overviewBlurActive || pendingWallpaper !== "" || _deferredSource !== "" || changePending || _freezeWaitFrames > 0 || frameAnim.running || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading || parallaxImage.status === Image.Loading || lockScrollSampler.busy
|
||||
property int _settleFrames: 3
|
||||
|
||||
function invalidate() {
|
||||
@@ -772,6 +773,11 @@ Variants {
|
||||
property int textureWidth: Math.min(Math.round(modelData.width * screenScale), maxTextureSize)
|
||||
property int textureHeight: Math.min(Math.round(modelData.height * screenScale), maxTextureSize)
|
||||
|
||||
LockScrollSampler {
|
||||
id: lockScrollSampler
|
||||
screen: modelData
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: imageMetrics
|
||||
property real nativeWidth: 0
|
||||
|
||||
@@ -7,8 +7,26 @@ import qs.Common
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property int sampleSize: 128
|
||||
property var decodedSamples: ({})
|
||||
property var layouts: ({})
|
||||
property var scrollSamplers: ({})
|
||||
|
||||
function setScrollSampler(screenName, present) {
|
||||
const next = Object.assign({}, scrollSamplers);
|
||||
if (present)
|
||||
next[screenName] = true;
|
||||
else
|
||||
delete next[screenName];
|
||||
scrollSamplers = next;
|
||||
}
|
||||
|
||||
function usesScrollAtlas(screenName) {
|
||||
if (!scrollSamplers[screenName] || SettingsData.lockScreenWallpaperPath !== "")
|
||||
return false;
|
||||
const source = SessionData.getMonitorWallpaper(screenName);
|
||||
return !!source && !source.startsWith("#") && (SettingsData.lockScreenWallpaperFillMode || SessionData.getMonitorWallpaperFillMode(screenName)) === "Scrolling";
|
||||
}
|
||||
|
||||
function neededFor(screen) {
|
||||
return SettingsData.lockScreenWidgetInstances.some(instance => {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import vm from "node:vm";
|
||||
import test from "node:test";
|
||||
|
||||
const atlas = vm.createContext({});
|
||||
vm.runInContext(readFileSync(new URL("../Common/LockScrollAtlas.js", import.meta.url), "utf8").replace(/^\.pragma.*$/m, ""), atlas);
|
||||
|
||||
test("the scroll window follows the axis the lock shader scrolls", () => {
|
||||
const cases = [
|
||||
["tall image scrolls vertically", 1000, 3000, 2000, 1000, 0, 0, { x: 0, y: 0, width: 1, height: 1 / 6 }],
|
||||
["tall image at the last workspace", 1000, 3000, 2000, 1000, 100, 100, { x: 0, y: 5 / 6, width: 1, height: 1 / 6 }],
|
||||
["wide image scrolls horizontally", 6000, 1000, 2000, 1000, 50, 0, { x: 1 / 3, y: 0, width: 1 / 3, height: 1 }],
|
||||
["matching aspect stays centred", 4000, 2000, 2000, 1000, 100, 100, { x: 0, y: 0, width: 1, height: 1 }]
|
||||
];
|
||||
for (const [name, iw, ih, sw, sh, sx, sy, expected] of cases) {
|
||||
const view = atlas.scrollWindow(iw, ih, sw, sh, sx, sy);
|
||||
for (const key of ["x", "y", "width", "height"])
|
||||
assert.ok(Math.abs(view[key] - expected[key]) < 1e-9, `${name}: ${key} ${view[key]} != ${expected[key]}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("crops pick the atlas rows the lock shows at each scroll position", () => {
|
||||
const size = atlas.atlasSize(1000, 3000, 2000, 1000, 4, 2);
|
||||
assert.deepEqual({ ...size }, { width: 4, height: 12 });
|
||||
const rows = Array.from({ length: size.width * size.height }, (_, i) => Math.floor(i / size.width));
|
||||
const sampleAt = scroll => atlas.crop(rows, size.width, size.height, atlas.scrollWindow(1000, 3000, 2000, 1000, 0, scroll), 4, 2);
|
||||
assert.deepEqual([...sampleAt(0)], [0, 0, 0, 0, 1, 1, 1, 1]);
|
||||
assert.deepEqual([...sampleAt(100)], [10, 10, 10, 10, 11, 11, 11, 11]);
|
||||
});
|
||||
Reference in new issue
Block a user