add noctalia and fuzzel
This commit is contained in:
+463
@@ -0,0 +1,463 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property string selectedDirectory: ""
|
||||
property string selectedWallpaper: ""
|
||||
property string previewCachedPath: ""
|
||||
|
||||
signal directoryChanged(string directory)
|
||||
signal wallpaperChanged(string wallpaper)
|
||||
|
||||
onSelectedWallpaperChanged: {
|
||||
if (!selectedWallpaper) {
|
||||
previewCachedPath = "";
|
||||
return;
|
||||
}
|
||||
if (ImageCacheService.initialized) {
|
||||
ImageCacheService.getLarge(selectedWallpaper, Screen.width, Screen.height, function (path, success) {
|
||||
if (root)
|
||||
root.previewCachedPath = success ? path : selectedWallpaper;
|
||||
});
|
||||
} else {
|
||||
previewCachedPath = selectedWallpaper;
|
||||
}
|
||||
}
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
// Beautiful header with icon
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
Rectangle {
|
||||
width: 40
|
||||
height: 40
|
||||
radius: Style.radiusL
|
||||
color: Color.mSurfaceVariant
|
||||
opacity: 0.6
|
||||
|
||||
NIcon {
|
||||
icon: "image"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mPrimary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("setup.wallpaper.header")
|
||||
pointSize: Style.fontSizeXL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("setup.wallpaper.subheader")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Large preview area
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: 180
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusL
|
||||
|
||||
// Image with rounded corners
|
||||
NImageRounded {
|
||||
anchors.fill: parent
|
||||
visible: previewCachedPath !== ""
|
||||
imagePath: previewCachedPath
|
||||
radius: Style.radiusL
|
||||
borderColor: previewCachedPath !== "" ? Color.mPrimary : Color.mOutline
|
||||
borderWidth: previewCachedPath !== "" ? 2 : 1
|
||||
imageFillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginL
|
||||
visible: selectedWallpaper === ""
|
||||
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
width: 64
|
||||
height: 64
|
||||
radius: width / 2
|
||||
color: Color.mPrimary
|
||||
|
||||
NIcon {
|
||||
icon: "sparkles"
|
||||
pointSize: Style.fontSizeXXL
|
||||
color: Color.mOnPrimary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("setup.wallpaper.select-prompt")
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
font.weight: Style.fontWeightMedium
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wallpaper gallery strip
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 92
|
||||
visible: filteredWallpapers.length > 0
|
||||
|
||||
NScrollView {
|
||||
id: galleryScroll
|
||||
anchors.fill: parent
|
||||
horizontalPolicy: ScrollBar.AsNeeded
|
||||
verticalPolicy: ScrollBar.AlwaysOff
|
||||
showGradientMasks: false
|
||||
reserveScrollbarSpace: false
|
||||
|
||||
// Enable vertical mouse wheel to scroll the horizontal strip by moving contentX
|
||||
WheelHandler {
|
||||
target: galleryScroll.contentItem
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
onWheel: event => {
|
||||
const flick = galleryScroll.contentItem;
|
||||
if (!flick)
|
||||
return;
|
||||
const delta = event.pixelDelta.x !== 0 || event.pixelDelta.y !== 0 ? (event.pixelDelta.y !== 0 ? event.pixelDelta.y : event.pixelDelta.x) : (event.angleDelta.y !== 0 ? event.angleDelta.y : event.angleDelta.x);
|
||||
// Move opposite of wheel to scroll content to the right for wheel down
|
||||
const step = -delta;
|
||||
const maxX = Math.max(0, flick.contentWidth - flick.width);
|
||||
let newX = flick.contentX + step;
|
||||
if (newX < 0)
|
||||
newX = 0;
|
||||
if (newX > maxX)
|
||||
newX = maxX;
|
||||
flick.contentX = newX;
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
height: parent.height
|
||||
|
||||
Repeater {
|
||||
model: filteredWallpapers
|
||||
delegate: Item {
|
||||
id: thumbDelegate
|
||||
readonly property int baseWidth: 120
|
||||
readonly property int baseHeight: 80
|
||||
readonly property int thumbRadius: Style.radiusM
|
||||
property string cachedPath: ""
|
||||
|
||||
Layout.preferredWidth: baseWidth
|
||||
Layout.preferredHeight: baseHeight
|
||||
|
||||
Component.onCompleted: {
|
||||
if (ImageCacheService.initialized) {
|
||||
ImageCacheService.getThumbnail(modelData, function (path, success) {
|
||||
if (thumbDelegate)
|
||||
thumbDelegate.cachedPath = success ? path : modelData;
|
||||
});
|
||||
} else {
|
||||
cachedPath = modelData;
|
||||
}
|
||||
}
|
||||
|
||||
// Rounded thumbnail using NImageRounded with caching
|
||||
NImageRounded {
|
||||
id: thumbImage
|
||||
anchors.fill: parent
|
||||
imagePath: thumbDelegate.cachedPath
|
||||
radius: thumbDelegate.thumbRadius
|
||||
borderColor: selectedWallpaper === modelData ? Color.mPrimary : Color.mOutline
|
||||
borderWidth: selectedWallpaper === modelData ? 2 : 1
|
||||
imageFillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
|
||||
// Loading/error state background
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.mSurfaceVariant
|
||||
radius: thumbDelegate.thumbRadius
|
||||
visible: thumbImage.status === Image.Loading || thumbImage.status === Image.Error || thumbDelegate.cachedPath === ""
|
||||
|
||||
NIcon {
|
||||
icon: "image"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mOnSurfaceVariant
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
NBusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
visible: thumbImage.status === Image.Loading || thumbDelegate.cachedPath === ""
|
||||
running: visible
|
||||
size: 18
|
||||
}
|
||||
|
||||
// Hover overlay
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: thumbDelegate.thumbRadius
|
||||
color: Color.mPrimary
|
||||
opacity: hoverHandler.hovered ? 0.1 : 0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Selection badge
|
||||
Rectangle {
|
||||
visible: selectedWallpaper === modelData
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 6
|
||||
width: 24
|
||||
height: 24
|
||||
radius: width / 2
|
||||
color: Color.mPrimary
|
||||
|
||||
NIcon {
|
||||
icon: "check"
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnPrimary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
selectedWallpaper = modelData;
|
||||
wallpaperChanged(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helpful info card
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 80
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusM
|
||||
opacity: 0.4
|
||||
visible: filteredWallpapers.length === 0
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "folder-open"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginXS
|
||||
NText {
|
||||
text: filteredWallpapers.length === 0 && selectedDirectory !== "" ? I18n.tr("setup.wallpaper.none-in-dir") : I18n.tr("setup.wallpaper.no-dir")
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
NText {
|
||||
text: selectedDirectory !== "" ? I18n.tr("setup.wallpaper.no-valid", {
|
||||
"dir": selectedDirectory
|
||||
}) : I18n.tr("setup.wallpaper.choose-dir")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
opacity: 0.8
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Directory selection
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInputButton {
|
||||
id: wallpaperPathInput
|
||||
label: I18n.tr("setup.wallpaper.dir-label")
|
||||
description: I18n.tr("setup.wallpaper.dir-description")
|
||||
text: selectedDirectory
|
||||
buttonIcon: "folder-open"
|
||||
buttonTooltip: I18n.tr("setup.wallpaper.dir-browse")
|
||||
Layout.fillWidth: true
|
||||
onInputTextChanged: text => {
|
||||
selectedDirectory = text;
|
||||
directoryChanged(text);
|
||||
}
|
||||
onButtonClicked: directoryPicker.open()
|
||||
}
|
||||
}
|
||||
|
||||
// Internal properties and functions
|
||||
property list<string> wallpapersList: []
|
||||
property list<string> filteredWallpapers: []
|
||||
|
||||
function updateFilteredWallpapers() {
|
||||
filteredWallpapers = wallpapersList;
|
||||
}
|
||||
|
||||
function refreshWallpapers() {
|
||||
if (!selectedDirectory || selectedDirectory === "") {
|
||||
wallpapersList = [];
|
||||
filteredWallpapers = [];
|
||||
return;
|
||||
}
|
||||
if (typeof WallpaperService !== "undefined" && WallpaperService.getWallpapersList) {
|
||||
var wallpapers = WallpaperService.getWallpapersList(Screen.name);
|
||||
wallpapersList = wallpapers;
|
||||
updateFilteredWallpapers();
|
||||
if (wallpapersList.length > 0 && selectedWallpaper === "") {
|
||||
selectedWallpaper = wallpapersList[0];
|
||||
}
|
||||
} else {
|
||||
readDirectoryImages(selectedDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
function readDirectoryImages(directoryPath) {
|
||||
// Build find command args dynamically from ImageCacheService filters
|
||||
var filters = ImageCacheService.imageFilters;
|
||||
var findArgs = ["find", directoryPath, "-type", "f", "("];
|
||||
for (var i = 0; i < filters.length; i++) {
|
||||
if (i > 0) {
|
||||
findArgs.push("-o");
|
||||
}
|
||||
findArgs.push("-iname");
|
||||
findArgs.push(filters[i]);
|
||||
}
|
||||
findArgs.push(")");
|
||||
directoryScanner.command = findArgs;
|
||||
directoryScanner.running = true;
|
||||
return [];
|
||||
}
|
||||
|
||||
onSelectedDirectoryChanged: {
|
||||
if (typeof Settings !== "undefined" && Settings.data && Settings.data.wallpaper) {
|
||||
Settings.data.wallpaper.directory = selectedDirectory;
|
||||
}
|
||||
if (typeof WallpaperService !== "undefined" && WallpaperService.refreshWallpapersList) {
|
||||
WallpaperService.refreshWallpapersList();
|
||||
}
|
||||
Qt.callLater(refreshWallpapers);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: WallpaperService
|
||||
enabled: typeof WallpaperService !== "undefined"
|
||||
function onWallpaperListChanged(screenName, count) {
|
||||
if (screenName === Screen.name) {
|
||||
Qt.callLater(refreshWallpapers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: initialRefreshTimer
|
||||
interval: 1000
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: refreshWallpapers()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (typeof Settings !== "undefined" && Settings.data && Settings.data.wallpaper && Settings.data.wallpaper.directory) {
|
||||
selectedDirectory = Settings.data.wallpaper.directory;
|
||||
} else {
|
||||
selectedDirectory = Quickshell.env("HOME") + "/Pictures/Wallpapers";
|
||||
}
|
||||
if (typeof WallpaperService !== "undefined" && WallpaperService.currentWallpaper) {
|
||||
selectedWallpaper = WallpaperService.currentWallpaper;
|
||||
}
|
||||
initialRefreshTimer.start();
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: directoryPicker
|
||||
selectionMode: "folders"
|
||||
title: I18n.tr("setup.wallpaper.dir-select-title")
|
||||
initialPath: selectedDirectory || Quickshell.env("HOME") + "/Pictures"
|
||||
onAccepted: paths => {
|
||||
if (paths.length > 0) {
|
||||
selectedDirectory = paths[0];
|
||||
directoryChanged(paths[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: directoryScanner
|
||||
command: []
|
||||
running: false
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
var lines = stdout.text.split('\n');
|
||||
var images = [];
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim();
|
||||
if (line !== '') {
|
||||
images.push(line);
|
||||
}
|
||||
}
|
||||
wallpapersList = images;
|
||||
updateFilteredWallpapers();
|
||||
if (wallpapersList.length > 0 && selectedWallpaper === "") {
|
||||
selectedWallpaper = wallpapersList[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user