move carouselview to standalone c++ component, fix crash due to memory leak in wallpaper object creation
C++ / fmt (pull_request) Successful in 3s
JS/TS / lint (pull_request) Successful in 18s
JS/TS / fmt (pull_request) Successful in 17s
Python / fmt (pull_request) Successful in 28s
Python / lint (pull_request) Successful in 30s
Python / test (pull_request) Successful in 1m5s
Python / typecheck (pull_request) Successful in 1m24s
Rust / fmt (pull_request) Successful in 31s
C++ / build (pull_request) Successful in 2m21s
Rust / build (pull_request) Successful in 1m47s
Python / buildcheck (pull_request) Successful in 2m18s
Rust / clippy (pull_request) Successful in 1m12s
C++ / clang-tidy (pull_request) Successful in 4m3s
C++ / fmt (pull_request) Successful in 3s
JS/TS / lint (pull_request) Successful in 18s
JS/TS / fmt (pull_request) Successful in 17s
Python / fmt (pull_request) Successful in 28s
Python / lint (pull_request) Successful in 30s
Python / test (pull_request) Successful in 1m5s
Python / typecheck (pull_request) Successful in 1m24s
Rust / fmt (pull_request) Successful in 31s
C++ / build (pull_request) Successful in 2m21s
Rust / build (pull_request) Successful in 1m47s
Python / buildcheck (pull_request) Successful in 2m18s
Rust / clippy (pull_request) Successful in 1m12s
C++ / clang-tidy (pull_request) Successful in 4m3s
This commit is contained in:
@@ -1,338 +1,55 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import ZShell.Components
|
||||
import ZShell.Config
|
||||
import qs.Modules.Launcher.Items
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
|
||||
Item {
|
||||
CarouselView {
|
||||
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));
|
||||
}
|
||||
|
||||
// Forwarded verbatim as a property of the same name onto every delegate
|
||||
// instance (the delegate declares `required property PersistentProperties visibilities`).
|
||||
delegateProperties: ({
|
||||
visibilities: root.visibilities
|
||||
})
|
||||
focalWidth: Config.launcher.sizes.wallpaperWidth
|
||||
glideDuration: 250
|
||||
glideEasingType: Easing.OutCubic
|
||||
implicitHeight: tileHeight + Tokens.spacing.small / 2 + Tokens.padding.large + Tokens.padding.normal
|
||||
implicitWidth: {
|
||||
itemSpacing: Tokens.spacing.small
|
||||
maxWidth: {
|
||||
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;
|
||||
return screen.width - (barMargins + margins) * 2 - Config.bar.rounding * 4;
|
||||
}
|
||||
minEdgeWidth: 56
|
||||
model: values
|
||||
tileHeight: focalWidth / 16 * 9
|
||||
|
||||
delegate: Component {
|
||||
WallpaperTile {
|
||||
} // your existing delegate file, unchanged
|
||||
}
|
||||
width: implicitWidth
|
||||
|
||||
Component.onCompleted: {
|
||||
const idx = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent);
|
||||
|
||||
if (idx >= 0)
|
||||
view.contentX = contentXForIndex(centerBlockStart + idx);
|
||||
jumpToIndex(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()
|
||||
onPreviewIndexChanged: (realIndex, modelData) => {
|
||||
Wallpapers.preview(modelData.path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,6 @@ Item {
|
||||
required property real tileWidth
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
// Emitted on click: caller decides whether that means "activate" (if
|
||||
// already current) or "bring to center" (if not) - see CarouselView.
|
||||
signal requestActivate
|
||||
|
||||
implicitHeight: tileHeight + Tokens.padding.large * 2
|
||||
@@ -31,13 +29,6 @@ Item {
|
||||
radius: Tokens.rounding.small
|
||||
y: Tokens.padding.large
|
||||
|
||||
Behavior on implicitWidth {
|
||||
enabled: !root.visibilities.launcher // avoid fighting drag-driven updates
|
||||
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
color: Colors.tPalette.m3outline
|
||||
|
||||
@@ -5,103 +5,46 @@ import Quickshell.Hyprland
|
||||
import QtQuick
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import ZShell.Config
|
||||
import ZShell.Internal
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool completed
|
||||
property real cropHeight: displayData?.height ?? 1.0
|
||||
property real cropWidth: displayData?.width ?? 1.0
|
||||
property real cropX: displayData?.x ?? 0.0
|
||||
property real cropY: displayData?.y ?? 0.0
|
||||
property WallpaperImage current
|
||||
readonly property var displayData: Wallpapers.getCrop(screen.name)
|
||||
required property ShellScreen screen
|
||||
property size screenResolution: Qt.size(screen.width * screenScale, screen.height * screenScale)
|
||||
property real screenScale: Hyprland.monitorFor(screen).scale
|
||||
property string source: Wallpapers.current
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Component.onCompleted: {
|
||||
Hyprland.refreshMonitors();
|
||||
Component.onCompleted: Hyprland.refreshMonitors()
|
||||
|
||||
if (source)
|
||||
Qt.callLater(() => {
|
||||
current = imgComp.createObject(this, {
|
||||
source
|
||||
});
|
||||
completed = true;
|
||||
});
|
||||
}
|
||||
onSourceChanged: {
|
||||
if (!source)
|
||||
current = null;
|
||||
else
|
||||
current = imgComp.createObject(this, {
|
||||
source: source
|
||||
});
|
||||
}
|
||||
WallpaperImage {
|
||||
id: img
|
||||
|
||||
Component {
|
||||
id: imgComp
|
||||
anchors.fill: parent
|
||||
cropHeight: root.displayData?.height ?? 1.0
|
||||
cropWidth: root.displayData?.width ?? 1.0
|
||||
cropX: root.displayData?.x ?? 0.0
|
||||
cropY: root.displayData?.y ?? 0.0
|
||||
fadeDuration: 150
|
||||
screenResolution: root.screenResolution
|
||||
source: Wallpapers.current
|
||||
|
||||
WallpaperImage {
|
||||
id: img
|
||||
|
||||
anchors.fill: parent
|
||||
cropHeight: root.cropHeight
|
||||
cropWidth: root.cropWidth
|
||||
cropX: root.cropX
|
||||
cropY: root.cropY
|
||||
opacity: 0
|
||||
screenResolution: root.screenResolution
|
||||
source: root.source
|
||||
|
||||
Behavior on cropHeight {
|
||||
Anim {
|
||||
id: heightAnim
|
||||
}
|
||||
Behavior on cropHeight {
|
||||
Anim {
|
||||
}
|
||||
Behavior on cropWidth {
|
||||
Anim {
|
||||
id: widthAnim
|
||||
}
|
||||
}
|
||||
Behavior on cropWidth {
|
||||
Anim {
|
||||
}
|
||||
Behavior on cropX {
|
||||
Anim {
|
||||
id: xAnim
|
||||
}
|
||||
}
|
||||
Behavior on cropX {
|
||||
Anim {
|
||||
}
|
||||
Behavior on cropY {
|
||||
Anim {
|
||||
id: yAnim
|
||||
}
|
||||
}
|
||||
Anim on opacity {
|
||||
id: anim
|
||||
|
||||
from: 0
|
||||
running: false
|
||||
to: 1
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
|
||||
onStatusChanged: {
|
||||
if (status === Image.Ready) {
|
||||
anim.start();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: destroyTimer
|
||||
|
||||
interval: anim.duration * 2
|
||||
running: root.current !== img && root.current?.status === Image.Ready
|
||||
|
||||
onTriggered: Qt.callLater(() => img.destroy())
|
||||
}
|
||||
Behavior on cropY {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user