diff --git a/Config/BackgroundConfig.qml b/Config/BackgroundConfig.qml index 160fa8e..10e7691 100644 --- a/Config/BackgroundConfig.qml +++ b/Config/BackgroundConfig.qml @@ -7,4 +7,8 @@ JsonObject { property real alignX: 0.5 property real alignY: 0.5 property real zoom: 1.0 + property real sourceClipX: 0 + property real sourceClipY: 0 + property real sourceClipW: 0 + property real sourceClipH: 0 } diff --git a/Config/Config.qml b/Config/Config.qml index da951f4..ba4c6f2 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -83,6 +83,10 @@ Singleton { wallFadeDuration: background.wallFadeDuration, enabled: background.enabled, alignX: background.alignX, + sourceClipX: background.sourceClipX, + sourceClipY: background.sourceClipY, + sourceClipW: background.sourceClipW, + sourceClipH: background.sourceClipH, alignY: background.alignY, zoom: background.zoom }; diff --git a/Modules/Settings/Categories/Background.qml b/Modules/Settings/Categories/Background.qml index 8933102..68a4931 100644 --- a/Modules/Settings/Categories/Background.qml +++ b/Modules/Settings/Categories/Background.qml @@ -29,13 +29,13 @@ SettingsPage { step: 50 } - // Separator { - // } - // - // WallpaperCropper { - // Layout.fillWidth: true - // Layout.preferredHeight: 300 - // } + Separator { + } + + WallpaperCropper { + Layout.fillWidth: true + Layout.preferredHeight: 600 + } } SettingsSection { diff --git a/Modules/Settings/Controls/WallpaperCropper.qml b/Modules/Settings/Controls/WallpaperCropper.qml index d0f5faa..7090ff5 100644 --- a/Modules/Settings/Controls/WallpaperCropper.qml +++ b/Modules/Settings/Controls/WallpaperCropper.qml @@ -6,97 +6,108 @@ import qs.Config import qs.Components import qs.Helpers -ColumnLayout { +Item { id: root - spacing: 15 - width: Math.min(parent ? parent.width : 600, 600) + Image { + id: imageView - Rectangle { - id: previewContainer + property real displayH: paintedHeight + property real displayW: paintedWidth + property real displayX: (width - paintedWidth) * 0.5 + property real displayY: (height - paintedHeight) * 0.5 + property real scaleX: sourceW / displayW + property real scaleY: sourceH / displayH + property real sourceH: sourceSize.height + property real sourceW: sourceSize.width + + anchors.fill: parent + fillMode: Image.PreserveAspectFit + smooth: true + source: Wallpapers.current + } + + Item { + id: overlay - Layout.fillHeight: true - Layout.preferredWidth: height * (Quickshell.screens.length > 0 ? (Quickshell.screens[0].height / Math.max(1, Quickshell.screens[0].width)) : 16 / 9) clip: true - color: DynamicColors.palette.m3surfaceContainer - radius: Config.appearance.rounding.scale * 10 + height: imageView.displayH + width: imageView.displayW + x: imageView.displayX + y: imageView.displayY - Image { - id: img + CustomRect { + id: cropRect + + property real aspectRatio: Quickshell.screens[0].width / Quickshell.screens[0].height + readonly property rect sourceRect: Qt.rect(x * imageView.scaleX, y * imageView.scaleY, width * imageView.scaleX, height * imageView.scaleY) + property real zoom: Config.background.zoom + + function clampToBounds() { + x = Math.max(0, Math.min(x, overlay.width - width)); + + y = Math.max(0, Math.min(y, overlay.height - height)); + } + + border.color: DynamicColors.palette.m3primary + border.width: 2 + color: DynamicColors.tPalette.m3primary + height: width / aspectRatio + radius: Appearance.rounding.small + visible: imageView.status === Image.Ready + width: Math.min(overlay.width / zoom, overlay.height * aspectRatio / zoom) + x: Config.background.sourceClipX / imageView.scaleX + y: Config.background.sourceClipY / imageView.scaleY + } + + MouseArea { + function updateCrop(mouseX, mouseY) { + let nx = mouseX - cropRect.width * 0.5; + let ny = mouseY - cropRect.height * 0.5; + + nx = Math.max(0, Math.min(nx, overlay.width - cropRect.width)); + + ny = Math.max(0, Math.min(ny, overlay.height - cropRect.height)); + + cropRect.x = nx; + cropRect.y = ny; + } anchors.fill: parent - asynchronous: true - fillMode: Image.PreserveAspectFit - source: Wallpapers.current + hoverEnabled: true + preventStealing: true - Rectangle { - id: cropRect + onPositionChanged: mouse => { + if (pressed) + updateCrop(mouse.x, mouse.y); + } + onPressed: mouse => { + updateCrop(mouse.x, mouse.y); + } + onReleased: { + Config.background.sourceClipX = cropRect.sourceRect.x; + Config.background.sourceClipY = cropRect.sourceRect.y; + Config.background.sourceClipW = cropRect.sourceRect.width; + Config.background.sourceClipH = cropRect.sourceRect.height; + Config.save(); + } + onWheel: wheel => { + let oldCenterX = cropRect.x + cropRect.width * 0.5; + let oldCenterY = cropRect.y + cropRect.height * 0.5; - property real cropHeight: (imageAspect > screenAspect ? paintedHeight : paintedWidth / screenAspect) / Config.background.zoom - property real cropWidth: (imageAspect > screenAspect ? paintedHeight * screenAspect : paintedWidth) / Config.background.zoom - property real imageAspect: Math.max(1, paintedWidth) / Math.max(1, paintedHeight) - property real paintedHeight: img.paintedHeight > 0 ? img.paintedHeight : img.height - property real paintedWidth: img.paintedWidth > 0 ? img.paintedWidth : img.width - property real paintedX: (img.width - paintedWidth) / 2 - property real paintedY: (img.height - paintedHeight) / 2 - property real screenAspect: Quickshell.screens.length > 0 ? (Quickshell.screens[0].width / Math.max(1, Quickshell.screens[0].height)) : 16 / 9 + if (wheel.angleDelta.y > 0) + cropRect.zoom *= 1.1; + else + cropRect.zoom /= 1.1; - border.color: DynamicColors.palette.m3primary - border.width: 2 - color: Qt.alpha(DynamicColors.palette.m3primaryContainer, 0.3) - height: cropHeight - width: cropWidth - x: paintedX + (paintedWidth - width) * Config.background.alignX - y: paintedY + (paintedHeight - height) * Config.background.alignY + cropRect.zoom = Math.max(1.0, Math.min(cropRect.zoom, 10.0)); + Config.background.zoom = cropRect.zoom; - DragHandler { - target: null + cropRect.x = oldCenterX - cropRect.width * 0.5; + cropRect.y = oldCenterY - cropRect.height * 0.5; - onActiveTranslationChanged: { - if (active) { - let newX = cropRect.x - cropRect.paintedX + translation.x; - let newY = cropRect.y - cropRect.paintedY + translation.y; - - let rangeX = cropRect.paintedWidth - cropRect.width; - let rangeY = cropRect.paintedHeight - cropRect.height; - - if (rangeX > 0) { - let valX = newX / rangeX; - Config.background.alignX = Math.max(0.0, Math.min(1.0, valX)); - Config.save(); - } - - if (rangeY > 0) { - let valY = newY / rangeY; - Config.background.alignY = Math.max(0.0, Math.min(1.0, valY)); - Config.save(); - } - } - } - } - - PinchHandler { - maximumScale: 5.0 - minimumScale: 1.0 - target: null - - onActiveScaleChanged: { - if (active) { - let newZoom = Config.background.zoom * (1 / (1 + (activeScale - 1) * 0.1)); - Config.background.zoom = Math.max(1.0, Math.min(newZoom, 5.0)); - } - } - } + cropRect.clampToBounds(); } } } - - SettingSpinBox { - max: 5.0 - min: 1.0 - name: "Zoom" - object: Config.background - setting: "zoom" - step: 0.1 - } } diff --git a/Modules/Wallpaper/WallBackground.qml b/Modules/Wallpaper/WallBackground.qml index c2c8a35..b8bb649 100644 --- a/Modules/Wallpaper/WallBackground.qml +++ b/Modules/Wallpaper/WallBackground.qml @@ -35,30 +35,24 @@ Item { id: two } - component Img: CachingImage { + component Img: Image { id: img - property real imageRatio: Math.max(1, sourceSize.width) / Math.max(1, sourceSize.height) - property bool isValid: sourceSize.width > 0 && sourceSize.height > 0 && root.width > 0 && root.height > 0 - property real windowRatio: root.width / Math.max(1, root.height) - function update(): void { - if (path === root.source) { + if (source === root.source) { root.current = this; } else { - path = root.source; + source = root.source; } } - anchors.fill: undefined + anchors.fill: parent asynchronous: true fillMode: Image.PreserveAspectCrop - height: isValid ? (imageRatio > windowRatio ? root.height : root.width / imageRatio) * Config.background.zoom : root.height opacity: 0 + retainWhileLoading: true scale: Wallpapers.showPreview ? 1 : 0.8 - width: isValid ? (imageRatio > windowRatio ? root.height * imageRatio : root.width) * Config.background.zoom : root.width - x: isValid ? (root.width - width) * Config.background.alignX : 0 - y: isValid ? (root.height - height) * Config.background.alignY : 0 + sourceClipRect: Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH) states: State { name: "visible"