mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-24 12:05:21 -04:00
feat(wallpaper): preload thumbnails & switch/sort options
- add sorting and clickable page jump - wrap-around page navigation
This commit is contained in:
@@ -10,7 +10,7 @@ StyledRect {
|
|||||||
signal locationSelected(string path)
|
signal locationSelected(string path)
|
||||||
|
|
||||||
width: 200
|
width: 200
|
||||||
color: Theme.surface
|
color: Theme.nestedSurface
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ StyledRect {
|
|||||||
|
|
||||||
property string sortBy: "name"
|
property string sortBy: "name"
|
||||||
property bool sortAscending: true
|
property bool sortAscending: true
|
||||||
|
property color surfaceColor: Theme.surfaceContainer
|
||||||
|
|
||||||
signal sortBySelected(string value)
|
signal sortBySelected(string value)
|
||||||
signal sortOrderSelected(bool ascending)
|
signal sortOrderSelected(bool ascending)
|
||||||
|
|
||||||
width: 200
|
width: 200
|
||||||
height: sortColumn.height + Theme.spacingM * 2
|
height: sortColumn.height + Theme.spacingM * 2
|
||||||
color: Theme.surfaceContainer
|
color: surfaceColor
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.color: Theme.outlineMedium
|
border.color: Theme.outlineMedium
|
||||||
border.width: 1
|
border.width: 1
|
||||||
|
|||||||
@@ -191,6 +191,10 @@ DankPopout {
|
|||||||
|
|
||||||
Keys.onPressed: function (event) {
|
Keys.onPressed: function (event) {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
if (root.currentTabIndex === 2 && wallpaperLoader.item?.handleKeyEvent && wallpaperLoader.item.handleKeyEvent(event)) {
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
root.dashVisible = false;
|
root.dashVisible = false;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -31,9 +31,46 @@ Item {
|
|||||||
property string selectedFileName: ""
|
property string selectedFileName: ""
|
||||||
property var targetScreen: null
|
property var targetScreen: null
|
||||||
property string targetScreenName: targetScreen ? targetScreen.name : ""
|
property string targetScreenName: targetScreen ? targetScreen.name : ""
|
||||||
|
// Shared with the wallpaper FileBrowser via CacheData.fileBrowserSettings["wallpaper"]
|
||||||
|
property string sortBy: "name"
|
||||||
|
property bool sortAscending: true
|
||||||
|
// Forces the page grid to rebuild when the folder model reorders in place.
|
||||||
|
property int gridRevision: 0
|
||||||
|
|
||||||
signal requestTabChange(int newIndex)
|
signal requestTabChange(int newIndex)
|
||||||
|
|
||||||
|
function refreshAfterSort() {
|
||||||
|
// Defer until FolderListModel finishes reordering.
|
||||||
|
Qt.callLater(() => {
|
||||||
|
gridRevision++;
|
||||||
|
if (visible && active) {
|
||||||
|
setInitialSelection();
|
||||||
|
}
|
||||||
|
updateSelectedFileName();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSortByChanged: refreshAfterSort()
|
||||||
|
onSortAscendingChanged: refreshAfterSort()
|
||||||
|
|
||||||
|
function loadSort() {
|
||||||
|
const s = CacheData.fileBrowserSettings["wallpaper"];
|
||||||
|
if (s) {
|
||||||
|
sortBy = s.sortBy || "name";
|
||||||
|
sortAscending = s.sortAscending !== undefined ? s.sortAscending : true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistSort() {
|
||||||
|
let settings = CacheData.fileBrowserSettings;
|
||||||
|
if (!settings["wallpaper"])
|
||||||
|
settings["wallpaper"] = {};
|
||||||
|
settings["wallpaper"].sortBy = sortBy;
|
||||||
|
settings["wallpaper"].sortAscending = sortAscending;
|
||||||
|
CacheData.fileBrowserSettings = settings;
|
||||||
|
CacheData.saveCache();
|
||||||
|
}
|
||||||
|
|
||||||
function getCurrentWallpaper() {
|
function getCurrentWallpaper() {
|
||||||
if (SessionData.perMonitorWallpaper && targetScreenName) {
|
if (SessionData.perMonitorWallpaper && targetScreenName) {
|
||||||
return SessionData.getMonitorWallpaper(targetScreenName);
|
return SessionData.getMonitorWallpaper(targetScreenName);
|
||||||
@@ -68,16 +105,62 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
loadSort();
|
||||||
loadWallpaperDirectory();
|
loadWallpaperDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: CacheData
|
||||||
|
function onFileBrowserSettingsChanged() {
|
||||||
|
loadSort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (active && visible) {
|
if (active && visible) {
|
||||||
setInitialSelection();
|
setInitialSelection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goToNextCell(visibleCount) {
|
||||||
|
if (gridIndex + 1 < visibleCount) {
|
||||||
|
gridIndex++;
|
||||||
|
} else if (currentPage < totalPages - 1) {
|
||||||
|
gridIndex = 0;
|
||||||
|
currentPage++;
|
||||||
|
} else if (totalPages > 1) {
|
||||||
|
gridIndex = 0;
|
||||||
|
currentPage = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToPrevCell() {
|
||||||
|
if (gridIndex > 0) {
|
||||||
|
gridIndex--;
|
||||||
|
} else if (currentPage > 0) {
|
||||||
|
currentPage--;
|
||||||
|
const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage);
|
||||||
|
gridIndex = prevPageCount - 1;
|
||||||
|
} else if (totalPages > 1) {
|
||||||
|
currentPage = totalPages - 1;
|
||||||
|
const lastPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage);
|
||||||
|
gridIndex = lastPageCount - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeOverlays() {
|
||||||
|
if (sortMenu.visible || pageJumpPopup.visible) {
|
||||||
|
sortMenu.visible = false;
|
||||||
|
pageJumpPopup.visible = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function handleKeyEvent(event) {
|
function handleKeyEvent(event) {
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
return closeOverlays();
|
||||||
|
}
|
||||||
const columns = 4;
|
const columns = 4;
|
||||||
const currentCol = gridIndex % columns;
|
const currentCol = gridIndex % columns;
|
||||||
const visibleCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage);
|
const visibleCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage);
|
||||||
@@ -97,40 +180,18 @@ Item {
|
|||||||
|
|
||||||
if (event.key === Qt.Key_Right || event.key === Qt.Key_L) {
|
if (event.key === Qt.Key_Right || event.key === Qt.Key_L) {
|
||||||
if (I18n.isRtl) {
|
if (I18n.isRtl) {
|
||||||
if (gridIndex > 0) {
|
goToPrevCell();
|
||||||
gridIndex--;
|
|
||||||
} else if (currentPage > 0) {
|
|
||||||
currentPage--;
|
|
||||||
const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage);
|
|
||||||
gridIndex = prevPageCount - 1;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (gridIndex + 1 < visibleCount) {
|
goToNextCell(visibleCount);
|
||||||
gridIndex++;
|
|
||||||
} else if (currentPage < totalPages - 1) {
|
|
||||||
gridIndex = 0;
|
|
||||||
currentPage++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === Qt.Key_Left || event.key === Qt.Key_H) {
|
if (event.key === Qt.Key_Left || event.key === Qt.Key_H) {
|
||||||
if (I18n.isRtl) {
|
if (I18n.isRtl) {
|
||||||
if (gridIndex + 1 < visibleCount) {
|
goToNextCell(visibleCount);
|
||||||
gridIndex++;
|
|
||||||
} else if (currentPage < totalPages - 1) {
|
|
||||||
gridIndex = 0;
|
|
||||||
currentPage++;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (gridIndex > 0) {
|
goToPrevCell();
|
||||||
gridIndex--;
|
|
||||||
} else if (currentPage > 0) {
|
|
||||||
currentPage--;
|
|
||||||
const prevPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage);
|
|
||||||
gridIndex = prevPageCount - 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -141,6 +202,9 @@ Item {
|
|||||||
} else if (currentPage < totalPages - 1) {
|
} else if (currentPage < totalPages - 1) {
|
||||||
gridIndex = currentCol;
|
gridIndex = currentCol;
|
||||||
currentPage++;
|
currentPage++;
|
||||||
|
} else if (totalPages > 1) {
|
||||||
|
gridIndex = currentCol;
|
||||||
|
currentPage = 0;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -154,19 +218,25 @@ Item {
|
|||||||
const prevPageRows = Math.ceil(prevPageCount / columns);
|
const prevPageRows = Math.ceil(prevPageCount / columns);
|
||||||
gridIndex = (prevPageRows - 1) * columns + currentCol;
|
gridIndex = (prevPageRows - 1) * columns + currentCol;
|
||||||
gridIndex = Math.min(gridIndex, prevPageCount - 1);
|
gridIndex = Math.min(gridIndex, prevPageCount - 1);
|
||||||
|
} else if (totalPages > 1) {
|
||||||
|
currentPage = totalPages - 1;
|
||||||
|
const lastPageCount = Math.min(itemsPerPage, wallpaperFolderModel.count - currentPage * itemsPerPage);
|
||||||
|
const lastPageRows = Math.ceil(lastPageCount / columns);
|
||||||
|
gridIndex = (lastPageRows - 1) * columns + currentCol;
|
||||||
|
gridIndex = Math.min(gridIndex, lastPageCount - 1);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === Qt.Key_PageUp && currentPage > 0) {
|
if (event.key === Qt.Key_PageUp && totalPages > 1) {
|
||||||
gridIndex = 0;
|
gridIndex = 0;
|
||||||
currentPage--;
|
currentPage = (currentPage - 1 + totalPages) % totalPages;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === Qt.Key_PageDown && currentPage < totalPages - 1) {
|
if (event.key === Qt.Key_PageDown && totalPages > 1) {
|
||||||
gridIndex = 0;
|
gridIndex = 0;
|
||||||
currentPage++;
|
currentPage = (currentPage + 1) % totalPages;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,6 +350,17 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function collectWallpaperPaths() {
|
||||||
|
const paths = [];
|
||||||
|
for (var i = 0; i < wallpaperFolderModel.count; i++) {
|
||||||
|
const filePath = wallpaperFolderModel.get(i, "filePath");
|
||||||
|
if (filePath) {
|
||||||
|
paths.push(filePath.toString().replace(/^file:\/\//, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: wallpaperFolderModel
|
target: wallpaperFolderModel
|
||||||
function onCountChanged() {
|
function onCountChanged() {
|
||||||
@@ -288,6 +369,7 @@ Item {
|
|||||||
setInitialSelection();
|
setInitialSelection();
|
||||||
}
|
}
|
||||||
updateSelectedFileName();
|
updateSelectedFileName();
|
||||||
|
thumbnailPreloader.paths = collectWallpaperPaths();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onStatusChanged() {
|
function onStatusChanged() {
|
||||||
@@ -296,10 +378,16 @@ Item {
|
|||||||
setInitialSelection();
|
setInitialSelection();
|
||||||
}
|
}
|
||||||
updateSelectedFileName();
|
updateSelectedFileName();
|
||||||
|
thumbnailPreloader.paths = collectWallpaperPaths();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WallpaperThumbnailPreloader {
|
||||||
|
id: thumbnailPreloader
|
||||||
|
cacheSize: 256
|
||||||
|
}
|
||||||
|
|
||||||
FolderListModel {
|
FolderListModel {
|
||||||
id: wallpaperFolderModel
|
id: wallpaperFolderModel
|
||||||
|
|
||||||
@@ -310,7 +398,19 @@ Item {
|
|||||||
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp", "*.jxl", "*.avif", "*.heif", "*.exr"]
|
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp", "*.jxl", "*.avif", "*.heif", "*.exr"]
|
||||||
showFiles: true
|
showFiles: true
|
||||||
showDirs: false
|
showDirs: false
|
||||||
sortField: FolderListModel.Name
|
sortField: {
|
||||||
|
switch (root.sortBy) {
|
||||||
|
case "size":
|
||||||
|
return FolderListModel.Size;
|
||||||
|
case "modified":
|
||||||
|
return FolderListModel.Time;
|
||||||
|
case "type":
|
||||||
|
return FolderListModel.Type;
|
||||||
|
default:
|
||||||
|
return FolderListModel.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortReversed: !root.sortAscending
|
||||||
folder: wallpaperDir ? "file://" + wallpaperDir.split('/').map(s => encodeURIComponent(s)).join('/') : ""
|
folder: wallpaperDir ? "file://" + wallpaperDir.split('/').map(s => encodeURIComponent(s)).join('/') : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,6 +439,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
id: contentColumn
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
@@ -376,6 +477,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model: {
|
model: {
|
||||||
|
root.gridRevision; // re-evaluate when sort order changes in place
|
||||||
const startIndex = currentPage * itemsPerPage;
|
const startIndex = currentPage * itemsPerPage;
|
||||||
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperFolderModel.count);
|
const endIndex = Math.min(startIndex + itemsPerPage, wallpaperFolderModel.count);
|
||||||
const items = [];
|
const items = [];
|
||||||
@@ -513,7 +615,7 @@ Item {
|
|||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: (parent.width - controlsRow.width - browseButton.width - Theme.spacingS) / 2
|
width: (parent.width - controlsRow.width - sortButton.width - browseButton.width - Theme.spacingS * 3) / 2
|
||||||
height: parent.height
|
height: parent.height
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,21 +629,42 @@ Item {
|
|||||||
iconName: "skip_previous"
|
iconName: "skip_previous"
|
||||||
iconSize: 20
|
iconSize: 20
|
||||||
buttonSize: 32
|
buttonSize: 32
|
||||||
enabled: currentPage > 0
|
enabled: totalPages > 1
|
||||||
opacity: enabled ? 1.0 : 0.3
|
opacity: enabled ? 1.0 : 0.3
|
||||||
|
tooltipText: I18n.tr("Previous page")
|
||||||
|
tooltipSide: "top"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (currentPage > 0) {
|
if (totalPages > 1) {
|
||||||
currentPage--;
|
currentPage = (currentPage - 1 + totalPages) % totalPages;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
id: pageIndicator
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: wallpaperFolderModel.count > 0 ? (wallpaperFolderModel.count === 1 ? I18n.tr("%1 wallpaper • %2 / %3").arg(wallpaperFolderModel.count).arg(currentPage + 1).arg(totalPages) : I18n.tr("%1 wallpapers • %2 / %3").arg(wallpaperFolderModel.count).arg(currentPage + 1).arg(totalPages)) : I18n.tr("No wallpapers")
|
text: wallpaperFolderModel.count > 0 ? (wallpaperFolderModel.count === 1 ? I18n.tr("%1 wallpaper • %2 / %3").arg(wallpaperFolderModel.count).arg(currentPage + 1).arg(totalPages) : I18n.tr("%1 wallpapers • %2 / %3").arg(wallpaperFolderModel.count).arg(currentPage + 1).arg(totalPages)) : I18n.tr("No wallpapers")
|
||||||
font.pixelSize: 14
|
font.pixelSize: 14
|
||||||
color: Theme.surfaceText
|
color: pageIndicatorMouseArea.containsMouse && pageIndicatorMouseArea.enabled ? Theme.primary : Theme.surfaceText
|
||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: pageIndicatorMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: totalPages > 1
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
onClicked: {
|
||||||
|
sortMenu.visible = false;
|
||||||
|
pageJumpPopup.visible = !pageJumpPopup.visible;
|
||||||
|
}
|
||||||
|
onEntered: if (enabled) pageJumpTooltip.show(I18n.tr("Jump to page"), pageIndicator, 0, 0, "top")
|
||||||
|
onExited: pageJumpTooltip.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTooltipV2 {
|
||||||
|
id: pageJumpTooltip
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
@@ -549,16 +672,34 @@ Item {
|
|||||||
iconName: "skip_next"
|
iconName: "skip_next"
|
||||||
iconSize: 20
|
iconSize: 20
|
||||||
buttonSize: 32
|
buttonSize: 32
|
||||||
enabled: currentPage < totalPages - 1
|
enabled: totalPages > 1
|
||||||
opacity: enabled ? 1.0 : 0.3
|
opacity: enabled ? 1.0 : 0.3
|
||||||
|
tooltipText: I18n.tr("Next page")
|
||||||
|
tooltipSide: "top"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (currentPage < totalPages - 1) {
|
if (totalPages > 1) {
|
||||||
currentPage++;
|
currentPage = (currentPage + 1) % totalPages;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
id: sortButton
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
iconName: "sort"
|
||||||
|
iconSize: 20
|
||||||
|
buttonSize: 32
|
||||||
|
opacity: 0.7
|
||||||
|
enabled: wallpaperFolderModel.count > 0
|
||||||
|
tooltipText: I18n.tr("Sort wallpapers")
|
||||||
|
tooltipSide: "top"
|
||||||
|
onClicked: {
|
||||||
|
pageJumpPopup.visible = false;
|
||||||
|
sortMenu.visible = !sortMenu.visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
id: browseButton
|
id: browseButton
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -566,6 +707,8 @@ Item {
|
|||||||
iconSize: 20
|
iconSize: 20
|
||||||
buttonSize: 32
|
buttonSize: 32
|
||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
|
tooltipText: I18n.tr("Choose wallpaper folder")
|
||||||
|
tooltipSide: "top"
|
||||||
onClicked: wallpaperBrowser.open()
|
onClicked: wallpaperBrowser.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -583,4 +726,119 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function jumpToPage(value) {
|
||||||
|
const n = parseInt(value);
|
||||||
|
if (!isNaN(n)) {
|
||||||
|
currentPage = Math.max(0, Math.min(totalPages - 1, n - 1));
|
||||||
|
}
|
||||||
|
pageJumpPopup.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click anywhere outside an open overlay to dismiss it.
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
z: 99
|
||||||
|
visible: sortMenu.visible || pageJumpPopup.visible
|
||||||
|
enabled: visible
|
||||||
|
onClicked: closeOverlays()
|
||||||
|
}
|
||||||
|
|
||||||
|
BackdropBlur {
|
||||||
|
visible: sortMenu.visible
|
||||||
|
z: 100
|
||||||
|
width: sortMenu.width
|
||||||
|
height: sortMenu.height
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.bottomMargin: 56
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
sourceItem: contentColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
FileBrowserSortMenu {
|
||||||
|
id: sortMenu
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.bottomMargin: 56
|
||||||
|
z: 101
|
||||||
|
surfaceColor: Theme.readableSurface
|
||||||
|
sortBy: root.sortBy
|
||||||
|
sortAscending: root.sortAscending
|
||||||
|
onSortBySelected: value => {
|
||||||
|
root.sortBy = value;
|
||||||
|
root.persistSort();
|
||||||
|
}
|
||||||
|
onSortOrderSelected: ascending => {
|
||||||
|
root.sortAscending = ascending;
|
||||||
|
root.persistSort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackdropBlur {
|
||||||
|
visible: pageJumpPopup.visible
|
||||||
|
z: 100
|
||||||
|
width: pageJumpPopup.width
|
||||||
|
height: pageJumpPopup.height
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: 56
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
sourceItem: contentColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
id: pageJumpPopup
|
||||||
|
width: 180
|
||||||
|
height: jumpColumn.height + Theme.spacingM * 2
|
||||||
|
color: Theme.readableSurface
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
border.color: Theme.outlineMedium
|
||||||
|
border.width: 1
|
||||||
|
visible: false
|
||||||
|
z: 101
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: 56
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible) {
|
||||||
|
pageJumpField.text = (root.currentPage + 1).toString();
|
||||||
|
pageJumpField.forceActiveFocus();
|
||||||
|
pageJumpField.selectAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: jumpColumn
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Jump to page (1 - %1)").arg(root.totalPages)
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: pageJumpField
|
||||||
|
width: parent.width
|
||||||
|
placeholderText: "1 - " + root.totalPages
|
||||||
|
maximumLength: 6
|
||||||
|
topPadding: Theme.spacingS
|
||||||
|
bottomPadding: Theme.spacingS
|
||||||
|
validator: IntValidator {
|
||||||
|
bottom: 1
|
||||||
|
top: root.totalPages
|
||||||
|
}
|
||||||
|
onAccepted: root.jumpToPage(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
// Frosted-glass backdrop: blurs the region of sourceItem directly behind the item
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property Item sourceItem: null
|
||||||
|
property real radius: Theme.cornerRadius
|
||||||
|
property real blurAmount: 1.0
|
||||||
|
property int blurMax: 96
|
||||||
|
|
||||||
|
readonly property bool blurActive: visible && BlurService.enabled
|
||||||
|
|
||||||
|
ShaderEffectSource {
|
||||||
|
id: snapshot
|
||||||
|
anchors.fill: parent
|
||||||
|
sourceItem: root.sourceItem
|
||||||
|
sourceRect: {
|
||||||
|
if (!root.sourceItem)
|
||||||
|
return Qt.rect(0, 0, 0, 0);
|
||||||
|
const p = root.mapToItem(root.sourceItem, 0, 0);
|
||||||
|
return Qt.rect(p.x, p.y, root.width, root.height);
|
||||||
|
}
|
||||||
|
live: root.blurActive
|
||||||
|
hideSource: false
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
source: snapshot
|
||||||
|
visible: root.blurActive
|
||||||
|
blurEnabled: root.blurActive
|
||||||
|
blurMax: root.blurMax
|
||||||
|
blur: root.blurAmount
|
||||||
|
maskEnabled: true
|
||||||
|
maskSource: maskRect
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: maskRect
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: root.radius
|
||||||
|
visible: false
|
||||||
|
layer.enabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
// Preload the CachingImage disk cache for a folder of wallpapers via ffmpegthumbnailer
|
||||||
|
// so the switcher grid renders instantly. No-op (graceful fallback) if the tool is absent.
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
property var paths: []
|
||||||
|
property int cacheSize: 256
|
||||||
|
property bool autoStart: true
|
||||||
|
property int maxConcurrent: 3
|
||||||
|
|
||||||
|
property int _active: 0
|
||||||
|
property var _queue: []
|
||||||
|
property int _toolState: -1 // -1 unknown, 0 unavailable, 1 available
|
||||||
|
|
||||||
|
onPathsChanged: if (autoStart)
|
||||||
|
preload()
|
||||||
|
|
||||||
|
// Must match djb2Hash + cachePath in Widgets/CachingImage.qml.
|
||||||
|
function _hash(str) {
|
||||||
|
if (!str)
|
||||||
|
return "";
|
||||||
|
let hash = 5381;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
hash = ((hash << 5) + hash) + str.charCodeAt(i);
|
||||||
|
hash = hash & 0x7FFFFFFF;
|
||||||
|
}
|
||||||
|
return hash.toString(16).padStart(8, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
function _cachePathFor(path) {
|
||||||
|
const hash = _hash(path);
|
||||||
|
if (!hash)
|
||||||
|
return "";
|
||||||
|
return `${Paths.stringify(Paths.imagecache)}/${hash}@${cacheSize}x${cacheSize}.png`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isAnimated(path) {
|
||||||
|
const lower = path.toLowerCase();
|
||||||
|
return lower.endsWith(".gif") || lower.endsWith(".webp");
|
||||||
|
}
|
||||||
|
|
||||||
|
function preload() {
|
||||||
|
if (!paths || paths.length === 0 || _toolState === 0)
|
||||||
|
return;
|
||||||
|
if (_toolState === -1) {
|
||||||
|
Proc.runCommand("wallpaperThumbToolCheck", ["sh", "-c", "command -v ffmpegthumbnailer"], function (out, code) {
|
||||||
|
root._toolState = code === 0 ? 1 : 0;
|
||||||
|
if (root._toolState === 1)
|
||||||
|
root._start();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _start() {
|
||||||
|
Paths.mkdir(Paths.imagecache);
|
||||||
|
const q = [];
|
||||||
|
for (let i = 0; i < paths.length; i++) {
|
||||||
|
const p = paths[i];
|
||||||
|
if (!p || p.startsWith("#") || _isAnimated(p))
|
||||||
|
continue;
|
||||||
|
q.push(p);
|
||||||
|
}
|
||||||
|
_queue = q;
|
||||||
|
for (let i = 0; i < maxConcurrent; i++)
|
||||||
|
_pump();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _pump() {
|
||||||
|
if (_queue.length === 0)
|
||||||
|
return;
|
||||||
|
const path = _queue.shift();
|
||||||
|
const cachePath = _cachePathFor(path);
|
||||||
|
if (!cachePath) {
|
||||||
|
_pump();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_active++;
|
||||||
|
// One process per file: skip if already cached, otherwise generate.
|
||||||
|
const script = "test -f \"$1\" || ffmpegthumbnailer -i \"$2\" -o \"$1\" -s " + cacheSize;
|
||||||
|
Proc.runCommand(null, ["sh", "-c", script, "thumb", cachePath, path], function (out, code) {
|
||||||
|
root._active--;
|
||||||
|
root._pump();
|
||||||
|
}, 0, 20000);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user