Files
z-bar-qt/Modules/Launcher/CarouselView.qml
T
zach ec80adf0a5
C++ / fmt (pull_request) Successful in 4s
JS/TS / fmt (pull_request) Successful in 27s
JS/TS / lint (pull_request) Successful in 33s
Python / fmt (pull_request) Successful in 27s
Python / lint (pull_request) Successful in 30s
Python / test (pull_request) Successful in 59s
Python / typecheck (pull_request) Failing after 1m5s
C++ / build (pull_request) Successful in 2m26s
Rust / build (pull_request) Successful in 1m30s
Rust / fmt (pull_request) Successful in 1m12s
Python / buildcheck (pull_request) Successful in 2m19s
Rust / clippy (pull_request) Successful in 1m56s
C++ / clang-tidy (pull_request) Successful in 4m1s
improve image caching for better performance
2026-08-14 19:49:48 +02:00

339 lines
7.5 KiB
QML
Executable File

pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import ZShell.Config
import qs.Modules.Launcher.Items
import qs.Components
import qs.Helpers
Item {
id: root
readonly property var arrangement: {
const W = width;
const F = focalWidth;
const M = minEdgeWidth;
const sp = itemSpacing;
if (W <= 0 || F <= 0)
return {
n: 0,
sizes: [F],
effectiveExtraWidth: 0
};
let bestN = 0;
let bestSizes = [F];
let bestExtraWidth = 0;
for (let n = 1; ; n++) {
const ratio = Math.pow(M / F, 1 / n);
const sizes = [F];
for (let i = 1; i <= n; i++) {
sizes.push(i === n ? M : F * Math.pow(ratio, i));
}
let decreasedWidth = 0;
for (let i = 1; i <= n; i++)
decreasedWidth += pitch - sizes[i];
const effectiveWidth = W + decreasedWidth * 2;
const requiredWidth = (2 * n + 1) * pitch;
if (requiredWidth > effectiveWidth)
break;
bestN = n;
bestSizes = sizes;
bestExtraWidth = decreasedWidth;
}
return {
n: bestN,
sizes: bestSizes,
effectiveExtraWidth: bestExtraWidth
};
}
readonly property int centerBlockIndex: Math.floor(loopMultiplier / 2)
readonly property int centerBlockStart: centerBlockIndex * realCount
readonly property int currentRealIndex: realIndexOf(view.currentIndex)
readonly property real focalWidth: Config.launcher.sizes.wallpaperWidth
readonly property real itemSpacing: Tokens.spacing.small
readonly property var keylines: {
const sp = itemSpacing;
const sizes = arrangement.sizes;
const n = arrangement.n;
const right = [
{
io: 0,
lo: 0,
sz: sizes[0]
}
];
let lo = sizes[0] / 2;
for (let i = 1; i <= n; i++) {
const size = sizes[i];
lo += sp + size / 2;
right.push({
io: i,
lo: lo,
sz: size
});
lo += size / 2;
}
right.push({
io: n + 1,
lo: width / 2 - sp,
sz: 0
});
const k = [];
for (let i = right.length - 1; i >= 1; i--) {
k.push({
io: -right[i].io,
lo: -right[i].lo,
sz: right[i].sz
});
}
for (const item of right)
k.push(item);
for (let i = 0; i < k.length; i++)
k[i].loc = k[i].io * pitch;
return k;
}
readonly property int loopMultiplier: 401
readonly property real minEdgeWidth: 56
required property var panels
readonly property real pitch: focalWidth + itemSpacing
readonly property int realCount: values.length
readonly property var screen: QsWindow.window?.screen
required property SearchBar search
readonly property real shrinkRatio: 0.63
readonly property real tileHeight: focalWidth / 16 * 9
readonly property var values: Wallpapers.query(root.search.text.split(" ").slice(1).join(" "))
readonly property int virtualCount: realCount > 0 ? realCount * loopMultiplier : 0
required property var visibilities
function contentXForIndex(index) {
return index * pitch + pitch / 2 - width / 2;
}
function decrementCurrentIndex() {
if (realCount === 0)
return;
goTo(view.currentIndex - 1);
}
function glideTo(dest) {
if (Math.abs(dest - view.contentX) < 0.5)
return;
view.cancelFlick();
snapAnim.stop();
snapAnim.from = view.contentX;
snapAnim.to = dest;
snapAnim.start();
}
function goTo(virtualIndex) {
glideTo(contentXForIndex(virtualIndex));
}
function incrementCurrentIndex() {
if (realCount === 0)
return;
goTo(view.currentIndex + 1);
}
function indexForContentX(x) {
return Math.round((x + width / 2 - pitch / 2) / pitch);
}
function realIndexOf(virtualIndex) {
if (realCount === 0)
return -1;
return ((virtualIndex % realCount) + realCount) % realCount;
}
function sample(childLoc) {
const k = keylines;
if (childLoc <= k[0].loc)
return {
c: k[0].lo,
s: 0
};
if (childLoc >= k[k.length - 1].loc)
return {
c: k[k.length - 1].lo,
s: 0
};
for (let i = 0; i < k.length - 1; i++) {
const a = k[i];
const b = k[i + 1];
if (childLoc > b.loc)
continue;
const span = b.loc - a.loc;
const t = span > 0 ? (childLoc - a.loc) / span : 0;
return {
c: a.lo + (b.lo - a.lo) * t,
s: a.sz + (b.sz - a.sz) * t
};
}
return {
c: 0,
s: 0
};
}
function selectReal(realIdx) {
if (realCount === 0)
return;
const curBlock = Math.floor(view.currentIndex / realCount);
const current = view.currentIndex;
let target = curBlock * realCount + realIdx;
if (target - current > realCount / 2)
target -= realCount;
else if (current - target > realCount / 2)
target += realCount;
goTo(target);
}
function snapTarget(x) {
return contentXForIndex(indexForContentX(x));
}
function snapToNearest() {
glideTo(snapTarget(view.contentX));
}
implicitHeight: tileHeight + Tokens.spacing.small / 2 + Tokens.padding.large + Tokens.padding.normal
implicitWidth: {
if (!screen)
return 0;
const barMargins = Config.bar.border * 2;
let margins = 0;
if (visibilities.utilities || visibilities.sidebar)
margins = panels.utilities.implicitWidth;
const maxWidth = screen.width - (barMargins + margins) * 2 - Config.bar.rounding * 4;
return maxWidth;
}
width: implicitWidth
Component.onCompleted: {
const idx = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent);
if (idx >= 0)
view.contentX = contentXForIndex(centerBlockStart + idx);
}
Component.onDestruction: Wallpapers.stopPreview()
ListView {
id: view
anchors.fill: parent
boundsBehavior: Flickable.StopAtBounds
cacheBuffer: root.width
clip: false
currentIndex: root.centerBlockStart
displayMarginBeginning: root.arrangement.effectiveExtraWidth
displayMarginEnd: root.arrangement.effectiveExtraWidth
interactive: true
model: root.virtualCount
orientation: ListView.Horizontal
snapMode: ListView.NoSnap
Anim on contentX {
id: snapAnim
running: false
type: Anim.Standard
}
delegate: Item {
id: slot
readonly property real childLoc: index * root.pitch + root.pitch / 2 - view.contentX - view.width / 2
required property int index
readonly property bool isCurrent: index === view.currentIndex
readonly property var keyline: root.sample(childLoc)
readonly property var modelData: realIdx >= 0 ? root.values[realIdx] : null
readonly property int realIdx: root.realIndexOf(index)
readonly property real tileWidth: keyline.s
height: view.height
visible: modelData !== null
width: root.pitch
WallpaperTile {
anchors.verticalCenter: parent.verticalCenter
isCurrent: slot.isCurrent
modelData: slot.modelData
tileHeight: root.tileHeight
tileWidth: slot.tileWidth
visibilities: root.visibilities
x: (slot.keyline.c - slot.childLoc) + (slot.width - width) / 2
onRequestActivate: {
if (slot.isCurrent) {
Wallpapers.setWallpaper(slot.modelData.path);
root.visibilities.launcher = false;
} else {
root.goTo(slot.index);
}
}
}
}
onContentXChanged: {
if (root.realCount === 0)
return;
const blockWidth = root.realCount * root.pitch;
const centerX = root.contentXForIndex(root.centerBlockStart);
const blocksFromCenter = Math.round((contentX - centerX) / blockWidth);
if (Math.abs(blocksFromCenter) >= 2) {
const shift = blocksFromCenter * blockWidth;
contentX -= shift;
if (snapAnim.running)
snapAnim.to -= shift;
}
currentIndex = Math.round((contentX + width / 2 - root.pitch / 2) / root.pitch);
}
onCurrentIndexChanged: {
const realIdx = root.realIndexOf(currentIndex);
if (realIdx >= 0)
Wallpapers.preview(root.values[realIdx].path);
}
onFlickEnded: root.snapToNearest()
onMovementEnded: root.snapToNearest()
}
}