forked from Shinonome/dots-hyprland
wallpaper selector: quick places
This commit is contained in:
@@ -108,6 +108,10 @@ Rectangle {
|
|||||||
iconSize: Appearance.font.pixelSize.larger
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
color: dirEditButton.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer2
|
color: dirEditButton.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledToolTip {
|
||||||
|
content: Translation.tr("Edit directory")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,16 @@ import Quickshell
|
|||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
// XDG Dirs, with "file://"
|
// XDG Dirs, with "file://"
|
||||||
|
readonly property string home: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
||||||
readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0]
|
readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0]
|
||||||
readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0]
|
readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0]
|
||||||
readonly property string cache: StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]
|
readonly property string cache: StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]
|
||||||
readonly property string genericCache: StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]
|
readonly property string genericCache: StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]
|
||||||
readonly property string pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
|
readonly property string documents: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0]
|
||||||
readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0]
|
readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0]
|
||||||
|
readonly property string pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
|
||||||
|
readonly property string videos: StandardPaths.standardLocations(StandardPaths.MoviesLocation)[0]
|
||||||
|
|
||||||
// Other dirs used by the shell, without "file://"
|
// Other dirs used by the shell, without "file://"
|
||||||
property string assetsPath: Quickshell.shellPath("assets")
|
property string assetsPath: Quickshell.shellPath("assets")
|
||||||
property string scriptPath: Quickshell.shellPath("scripts")
|
property string scriptPath: Quickshell.shellPath("scripts")
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ ListView {
|
|||||||
required property var modelData
|
required property var modelData
|
||||||
required property int index
|
required property int index
|
||||||
buttonText: index === 0 ? "/" : modelData
|
buttonText: index === 0 ? "/" : modelData
|
||||||
toggled: index === directory.split("/").length - 1
|
toggled: {
|
||||||
|
if (directory.trim() === "/") return index === 0;
|
||||||
|
return index === directory.split("/").length - 1
|
||||||
|
}
|
||||||
leftmost: index === 0
|
leftmost: index === 0
|
||||||
rightmost: index === breadcrumbDirectory.split("/").length - 1
|
rightmost: index === breadcrumbDirectory.split("/").length - 1
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import qs
|
||||||
|
import qs.services
|
||||||
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
|
import qs.modules.common.functions
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
required property string path
|
||||||
|
property bool isHovered: false
|
||||||
|
|
||||||
|
property alias color: background.color
|
||||||
|
property alias radius: background.radius
|
||||||
|
property alias padding: background.anchors.margins
|
||||||
|
|
||||||
|
signal activated()
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: background
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
margins: 8
|
||||||
|
}
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
|
Behavior on color {
|
||||||
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: wallpaperItemColumnLayout
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
margins: 6
|
||||||
|
}
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: wallpaperItemImageContainer
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
StyledRectangularShadow {
|
||||||
|
target: thumbnailImageLoader
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: thumbnailImageLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.visible
|
||||||
|
sourceComponent: Image {
|
||||||
|
id: thumbnailImage
|
||||||
|
source: {
|
||||||
|
if (root.path.length == 0)
|
||||||
|
return;
|
||||||
|
const resolvedUrl = Qt.resolvedUrl(root.path);
|
||||||
|
const md5Hash = Qt.md5(resolvedUrl);
|
||||||
|
const cacheSize = "normal";
|
||||||
|
const thumbnailPath = `${Directories.genericCache}/thumbnails/${cacheSize}/${md5Hash}.png`;
|
||||||
|
return thumbnailPath;
|
||||||
|
}
|
||||||
|
asynchronous: true
|
||||||
|
cache: false
|
||||||
|
smooth: true
|
||||||
|
mipmap: false
|
||||||
|
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
clip: true
|
||||||
|
sourceSize.width: wallpaperItemColumnLayout.width
|
||||||
|
sourceSize.height: wallpaperItemColumnLayout.height - wallpaperItemColumnLayout.spacing - wallpaperItemName.height
|
||||||
|
|
||||||
|
opacity: status === Image.Ready ? 1 : 0
|
||||||
|
Behavior on opacity {
|
||||||
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: Rectangle {
|
||||||
|
width: wallpaperItemImageContainer.width
|
||||||
|
height: wallpaperItemImageContainer.height
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: wallpaperItemName
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 10
|
||||||
|
Layout.rightMargin: 10
|
||||||
|
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||||
|
color: (index === grid.currentIndex || parent.isHovered) ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer0
|
||||||
|
Behavior on color {
|
||||||
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
text: FileUtils.fileNameForPath(root.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
onEntered: {
|
||||||
|
for (let i = 0; i < grid.count; i++) {
|
||||||
|
const item = grid.itemAtIndex(i);
|
||||||
|
if (item && item !== parent) {
|
||||||
|
item.isHovered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent.isHovered = true;
|
||||||
|
grid.currentIndex = index;
|
||||||
|
}
|
||||||
|
onExited: {
|
||||||
|
parent.isHovered = false;
|
||||||
|
}
|
||||||
|
onClicked: root.activated()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,15 +9,11 @@ import QtQuick.Layouts
|
|||||||
import Qt5Compat.GraphicalEffects
|
import Qt5Compat.GraphicalEffects
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland
|
|
||||||
import Quickshell.Hyprland
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
property int columns: 4
|
property int columns: 4
|
||||||
property real previewCellAspectRatio: 4 / 3
|
property real previewCellAspectRatio: 4 / 3
|
||||||
implicitHeight: columnLayout.implicitHeight
|
|
||||||
implicitWidth: columnLayout.implicitWidth
|
|
||||||
property var wallpapers: Wallpapers.wallpapers
|
property var wallpapers: Wallpapers.wallpapers
|
||||||
property string filterQuery: ""
|
property string filterQuery: ""
|
||||||
property bool useDarkMode: Appearance.m3colors.darkmode
|
property bool useDarkMode: Appearance.m3colors.darkmode
|
||||||
@@ -66,357 +62,315 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
implicitHeight: mainLayout.implicitHeight
|
||||||
id: columnLayout
|
implicitWidth: mainLayout.implicitWidth
|
||||||
anchors.fill: parent
|
|
||||||
spacing: -Appearance.sizes.elevationMargin
|
|
||||||
|
|
||||||
Item { // The grid
|
StyledRectangularShadow {
|
||||||
id: wallpaperGrid
|
target: wallpaperGridBackground
|
||||||
Layout.fillWidth: true
|
}
|
||||||
Layout.fillHeight: true
|
Rectangle {
|
||||||
implicitWidth: wallpaperGridBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
|
id: wallpaperGridBackground
|
||||||
implicitHeight: wallpaperGridBackground.implicitHeight + Appearance.sizes.elevationMargin * 2
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
margins: Appearance.sizes.elevationMargin
|
||||||
|
}
|
||||||
|
focus: true
|
||||||
|
border.width: 1
|
||||||
|
border.color: Appearance.colors.colLayer0Border
|
||||||
|
color: Appearance.colors.colLayer0
|
||||||
|
radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
|
||||||
|
|
||||||
|
property int calculatedRows: Math.ceil(grid.count / grid.columns)
|
||||||
|
|
||||||
|
implicitWidth: gridColumnLayout.implicitWidth
|
||||||
|
implicitHeight: gridColumnLayout.implicitHeight
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: mainLayout
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: -4
|
||||||
|
|
||||||
StyledRectangularShadow {
|
|
||||||
target: wallpaperGridBackground
|
|
||||||
}
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: wallpaperGridBackground
|
Layout.fillHeight: true
|
||||||
anchors {
|
Layout.margins: 4
|
||||||
fill: parent
|
implicitWidth: quickDirColumnLayout.implicitWidth
|
||||||
margins: Appearance.sizes.elevationMargin
|
implicitHeight: quickDirColumnLayout.implicitHeight
|
||||||
}
|
color: Appearance.colors.colLayer1
|
||||||
focus: true
|
radius: wallpaperGridBackground.radius - Layout.margins
|
||||||
border.width: 1
|
|
||||||
border.color: Appearance.colors.colLayer0Border
|
|
||||||
color: Appearance.colors.colLayer0
|
|
||||||
radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
|
|
||||||
|
|
||||||
property int calculatedRows: Math.ceil(grid.count / grid.columns)
|
|
||||||
|
|
||||||
// implicitWidth: gridColumnLayout.implicitWidth
|
|
||||||
// implicitHeight: gridColumnLayout.implicitHeight
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
// The grid
|
id: quickDirColumnLayout
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
AddressBar {
|
|
||||||
id: addressBar
|
StyledText {
|
||||||
Layout.margins: 4
|
Layout.margins: 12
|
||||||
Layout.fillWidth: true
|
font {
|
||||||
Layout.fillHeight: false
|
pixelSize: Appearance.font.pixelSize.normal
|
||||||
directory: Wallpapers.directory
|
weight: Font.Medium
|
||||||
onNavigateToDirectory: path => {
|
}
|
||||||
Wallpapers.setDirectory(path);
|
text: Translation.tr("Pick a wallpaper")
|
||||||
|
}
|
||||||
|
ListView {
|
||||||
|
// Quick dirs
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.margins: 4
|
||||||
|
implicitWidth: 140
|
||||||
|
clip: true
|
||||||
|
model: [
|
||||||
|
{ icon: "home", name: "Home", path: Directories.home },
|
||||||
|
{ icon: "folder", name: "Documents", path: Directories.documents },
|
||||||
|
{ icon: "download", name: "Downloads", path: Directories.downloads },
|
||||||
|
{ icon: "image", name: "Pictures", path: Directories.pictures },
|
||||||
|
{ icon: "movie", name: "Videos", path: Directories.videos },
|
||||||
|
{ icon: "", name: "---", path: "INTENTIONALLY_INVALID_DIR" },
|
||||||
|
{ icon: "wallpaper", name: "Wallpapers", path: `${Directories.pictures}/Wallpapers` },
|
||||||
|
{ icon: "favorite", name: "Homework", path: `${Directories.pictures}/homework` },
|
||||||
|
]
|
||||||
|
delegate: RippleButton {
|
||||||
|
id: quickDirButton
|
||||||
|
required property var modelData
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
onClicked: Wallpapers.setDirectory(quickDirButton.modelData.path)
|
||||||
|
enabled: modelData.icon.length > 0
|
||||||
|
toggled: Wallpapers.directory === FileUtils.trimFileProtocol(modelData.path)
|
||||||
|
colBackgroundToggled: Appearance.colors.colSecondaryContainer
|
||||||
|
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
|
||||||
|
colRippleToggled: Appearance.colors.colSecondaryContainerActive
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
MaterialSymbol {
|
||||||
|
color: quickDirButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
text: quickDirButton.modelData.icon
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
color: quickDirButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1
|
||||||
|
text: quickDirButton.modelData.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: gridColumnLayout
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
AddressBar {
|
||||||
|
id: addressBar
|
||||||
|
Layout.margins: 4
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: false
|
||||||
|
directory: Wallpapers.directory
|
||||||
|
onNavigateToDirectory: path => {
|
||||||
|
Wallpapers.setDirectory(path.length == 0 ? "/" : path);
|
||||||
|
}
|
||||||
|
radius: wallpaperGridBackground.radius - Layout.margins
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: gridDisplayRegion
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: Rectangle {
|
||||||
|
width: gridDisplayRegion.width
|
||||||
|
height: gridDisplayRegion.height
|
||||||
|
radius: wallpaperGridBackground.radius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GridView {
|
||||||
|
id: grid
|
||||||
|
visible: root.wallpapers.length > 0
|
||||||
|
|
||||||
|
readonly property int columns: root.columns
|
||||||
|
readonly property int rows: Math.max(1, Math.ceil(count / columns))
|
||||||
|
property int currentIndex: 0
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
cellWidth: width / root.columns
|
||||||
|
cellHeight: cellWidth / root.previewCellAspectRatio
|
||||||
|
interactive: true
|
||||||
|
clip: true
|
||||||
|
keyNavigationWraps: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
bottomMargin: extraOptions.implicitHeight
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
policy: ScrollBar.AsNeeded
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveSelection(delta) {
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const item = itemAtIndex(i);
|
||||||
|
if (item) {
|
||||||
|
item.isHovered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentIndex = Math.max(0, Math.min(root.wallpapers.length - 1, currentIndex + delta));
|
||||||
|
positionViewAtIndex(currentIndex, GridView.Contain);
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateCurrent() {
|
||||||
|
print("ACTIVATE");
|
||||||
|
const path = grid.model.values[currentIndex];
|
||||||
|
if (!path)
|
||||||
|
return;
|
||||||
|
GlobalStates.wallpaperSelectorOpen = false;
|
||||||
|
filterField.text = "";
|
||||||
|
Wallpapers.apply(path, root.useDarkMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
model: ScriptModel {
|
||||||
|
values: root.wallpapers.filter(w => (w.toLowerCase().includes(root.filterQuery.toLowerCase())))
|
||||||
|
}
|
||||||
|
onModelChanged: currentIndex = 0
|
||||||
|
|
||||||
|
delegate: WallpaperDirectoryItem {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
visible: modelData.length > 0
|
||||||
|
width: grid.cellWidth
|
||||||
|
height: grid.cellHeight
|
||||||
|
path: modelData
|
||||||
|
color: (index === grid?.currentIndex || parent?.isHovered) ? Appearance.colors.colPrimary : ColorUtils.transparentize(Appearance.colors.colPrimary)
|
||||||
|
onActivated: {
|
||||||
|
Wallpapers.apply(path, root.useDarkMode);
|
||||||
|
GlobalStates.wallpaperSelectorOpen = false;
|
||||||
|
filterField.text = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
radius: wallpaperGridBackground.radius - Layout.margins
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: gridDisplayRegion
|
id: extraOptions
|
||||||
Layout.fillWidth: true
|
anchors {
|
||||||
Layout.fillHeight: true
|
bottom: parent.bottom
|
||||||
layer.enabled: true
|
horizontalCenter: parent.horizontalCenter
|
||||||
layer.effect: OpacityMask {
|
}
|
||||||
maskSource: Rectangle {
|
implicitHeight: extraOptionsBackground.implicitHeight + extraOptionsBackground.anchors.margins * 2
|
||||||
width: gridDisplayRegion.width
|
implicitWidth: extraOptionsBackground.implicitWidth + extraOptionsBackground.anchors.margins * 2
|
||||||
height: gridDisplayRegion.height
|
|
||||||
radius: wallpaperGridBackground.radius
|
StyledRectangularShadow {
|
||||||
}
|
target: extraOptionsBackground
|
||||||
}
|
}
|
||||||
|
|
||||||
GridView {
|
Rectangle { // Bottom toolbar
|
||||||
id: grid
|
id: extraOptionsBackground
|
||||||
visible: root.wallpapers.length > 0
|
property real padding: 6
|
||||||
|
|
||||||
readonly property int columns: root.columns
|
|
||||||
readonly property int rows: Math.max(1, Math.ceil(count / columns))
|
|
||||||
property int currentIndex: 0
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
cellWidth: width / root.columns
|
|
||||||
cellHeight: cellWidth / root.previewCellAspectRatio
|
|
||||||
interactive: true
|
|
||||||
clip: true
|
|
||||||
keyNavigationWraps: true
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
bottomMargin: extraOptions.implicitHeight
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
|
||||||
policy: ScrollBar.AsNeeded
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveSelection(delta) {
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
const item = itemAtIndex(i);
|
|
||||||
if (item) {
|
|
||||||
item.isHovered = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentIndex = Math.max(0, Math.min(root.wallpapers.length - 1, currentIndex + delta));
|
|
||||||
positionViewAtIndex(currentIndex, GridView.Contain);
|
|
||||||
}
|
|
||||||
|
|
||||||
function activateCurrent() {
|
|
||||||
print("ACTIVATE");
|
|
||||||
const path = grid.model.values[currentIndex];
|
|
||||||
if (!path)
|
|
||||||
return;
|
|
||||||
GlobalStates.wallpaperSelectorOpen = false;
|
|
||||||
filterField.text = "";
|
|
||||||
Wallpapers.apply(path, root.useDarkMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
model: ScriptModel {
|
|
||||||
values: root.wallpapers.filter(w => (w.toLowerCase().includes(root.filterQuery.toLowerCase())))
|
|
||||||
}
|
|
||||||
onModelChanged: currentIndex = 0
|
|
||||||
|
|
||||||
delegate: Item {
|
|
||||||
id: wallpaperItem
|
|
||||||
required property var modelData
|
|
||||||
required property int index
|
|
||||||
visible: modelData.length > 0
|
|
||||||
width: grid.cellWidth
|
|
||||||
height: grid.cellHeight
|
|
||||||
property bool isHovered: false
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors {
|
|
||||||
fill: parent
|
|
||||||
margins: 8
|
|
||||||
}
|
|
||||||
radius: Appearance.rounding.normal
|
|
||||||
color: (index === grid.currentIndex || parent.isHovered) ? Appearance.colors.colPrimary : ColorUtils.transparentize(Appearance.colors.colPrimary)
|
|
||||||
Behavior on color {
|
|
||||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: wallpaperItemColumnLayout
|
|
||||||
anchors {
|
|
||||||
fill: parent
|
|
||||||
margins: 6
|
|
||||||
}
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: wallpaperItemImageContainer
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
StyledRectangularShadow {
|
|
||||||
target: thumbnailImageLoader
|
|
||||||
radius: Appearance.rounding.small
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: thumbnailImageLoader
|
|
||||||
anchors.fill: parent
|
|
||||||
active: wallpaperItem.visible
|
|
||||||
sourceComponent: Image {
|
|
||||||
id: thumbnailImage
|
|
||||||
source: {
|
|
||||||
if (wallpaperItem.modelData.length == 0)
|
|
||||||
return;
|
|
||||||
const resolvedUrl = Qt.resolvedUrl(wallpaperItem.modelData);
|
|
||||||
const md5Hash = Qt.md5(resolvedUrl);
|
|
||||||
const cacheSize = "normal";
|
|
||||||
const thumbnailPath = `${Directories.genericCache}/thumbnails/${cacheSize}/${md5Hash}.png`;
|
|
||||||
return thumbnailPath;
|
|
||||||
}
|
|
||||||
asynchronous: true
|
|
||||||
cache: false
|
|
||||||
smooth: true
|
|
||||||
mipmap: false
|
|
||||||
|
|
||||||
fillMode: Image.PreserveAspectCrop
|
|
||||||
clip: true
|
|
||||||
sourceSize.width: wallpaperItemColumnLayout.width
|
|
||||||
sourceSize.height: wallpaperItemColumnLayout.height - wallpaperItemColumnLayout.spacing - wallpaperItemName.height
|
|
||||||
|
|
||||||
opacity: status === Image.Ready ? 1 : 0
|
|
||||||
Behavior on opacity {
|
|
||||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: OpacityMask {
|
|
||||||
maskSource: Rectangle {
|
|
||||||
width: wallpaperItemImageContainer.width
|
|
||||||
height: wallpaperItemImageContainer.height
|
|
||||||
radius: Appearance.rounding.small
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: wallpaperItemName
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.leftMargin: 10
|
|
||||||
Layout.rightMargin: 10
|
|
||||||
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
|
||||||
color: (index === grid.currentIndex || parent.isHovered) ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer0
|
|
||||||
Behavior on color {
|
|
||||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
|
||||||
}
|
|
||||||
text: FileUtils.fileNameForPath(wallpaperItem.modelData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onEntered: {
|
|
||||||
for (let i = 0; i < grid.count; i++) {
|
|
||||||
const item = grid.itemAtIndex(i);
|
|
||||||
if (item && item !== parent) {
|
|
||||||
item.isHovered = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parent.isHovered = true;
|
|
||||||
grid.currentIndex = index;
|
|
||||||
}
|
|
||||||
onExited: {
|
|
||||||
parent.isHovered = false;
|
|
||||||
}
|
|
||||||
onClicked: {
|
|
||||||
GlobalStates.wallpaperSelectorOpen = false;
|
|
||||||
filterField.text = "";
|
|
||||||
Wallpapers.apply(wallpaperItem.modelData, root.useDarkMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: extraOptions
|
|
||||||
anchors {
|
anchors {
|
||||||
bottom: parent.bottom
|
fill: parent
|
||||||
horizontalCenter: parent.horizontalCenter
|
margins: 8
|
||||||
}
|
}
|
||||||
implicitHeight: extraOptionsBackground.implicitHeight + extraOptionsBackground.anchors.margins * 2
|
color: Appearance.colors.colLayer2
|
||||||
implicitWidth: extraOptionsBackground.implicitWidth + extraOptionsBackground.anchors.margins * 2
|
implicitHeight: extraOptionsRowLayout.implicitHeight + padding * 2
|
||||||
|
implicitWidth: extraOptionsRowLayout.implicitWidth + padding * 2
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
StyledRectangularShadow {
|
RowLayout {
|
||||||
target: extraOptionsBackground
|
id: extraOptionsRowLayout
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle { // Bottom toolbar
|
|
||||||
id: extraOptionsBackground
|
|
||||||
property real padding: 6
|
|
||||||
anchors {
|
anchors {
|
||||||
fill: parent
|
fill: parent
|
||||||
margins: 8
|
margins: extraOptionsBackground.padding
|
||||||
}
|
}
|
||||||
color: Appearance.colors.colLayer2
|
|
||||||
implicitHeight: extraOptionsRowLayout.implicitHeight + padding * 2
|
|
||||||
implicitWidth: extraOptionsRowLayout.implicitWidth + padding * 2
|
|
||||||
radius: Appearance.rounding.full
|
|
||||||
|
|
||||||
RowLayout {
|
RippleButton {
|
||||||
id: extraOptionsRowLayout
|
Layout.fillHeight: true
|
||||||
anchors {
|
Layout.topMargin: 2
|
||||||
fill: parent
|
Layout.bottomMargin: 2
|
||||||
margins: extraOptionsBackground.padding
|
implicitWidth: height
|
||||||
|
buttonRadius: Appearance.rounding.full
|
||||||
|
onClicked: {
|
||||||
|
Wallpapers.openFallbackPicker();
|
||||||
|
GlobalStates.wallpaperSelectorOpen = false;
|
||||||
|
}
|
||||||
|
contentItem: MaterialSymbol {
|
||||||
|
text: "open_in_new"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
}
|
||||||
|
StyledToolTip {
|
||||||
|
content: Translation.tr("Use the system file picker instead")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.topMargin: 2
|
||||||
|
Layout.bottomMargin: 2
|
||||||
|
implicitWidth: height
|
||||||
|
buttonRadius: Appearance.rounding.full
|
||||||
|
onClicked: root.useDarkMode = !root.useDarkMode
|
||||||
|
contentItem: MaterialSymbol {
|
||||||
|
text: root.useDarkMode ? "dark_mode" : "light_mode"
|
||||||
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
|
}
|
||||||
|
StyledToolTip {
|
||||||
|
content: Translation.tr("Click to toggle light/dark mode (applied when wallpaper is chosen)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: filterField
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.topMargin: 2
|
||||||
|
Layout.bottomMargin: 2
|
||||||
|
implicitWidth: 200
|
||||||
|
padding: 10
|
||||||
|
placeholderText: focus ? Translation.tr("Search wallpapers") : Translation.tr("Hit \"/\" to search")
|
||||||
|
placeholderTextColor: Appearance.colors.colSubtext
|
||||||
|
color: Appearance.colors.colOnLayer0
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
|
||||||
|
selectionColor: Appearance.colors.colSecondaryContainer
|
||||||
|
background: Rectangle {
|
||||||
|
color: Appearance.colors.colLayer1
|
||||||
|
radius: Appearance.rounding.full
|
||||||
}
|
}
|
||||||
|
|
||||||
RippleButton {
|
onTextChanged: {
|
||||||
Layout.fillHeight: true
|
root.filterQuery = text;
|
||||||
Layout.topMargin: 2
|
|
||||||
Layout.bottomMargin: 2
|
|
||||||
implicitWidth: height
|
|
||||||
buttonRadius: Appearance.rounding.full
|
|
||||||
onClicked: {
|
|
||||||
Wallpapers.openFallbackPicker();
|
|
||||||
GlobalStates.wallpaperSelectorOpen = false;
|
|
||||||
}
|
|
||||||
contentItem: MaterialSymbol {
|
|
||||||
text: "files"
|
|
||||||
iconSize: Appearance.font.pixelSize.larger
|
|
||||||
}
|
|
||||||
StyledToolTip {
|
|
||||||
content: Translation.tr("Use the system file picker instead")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RippleButton {
|
Keys.onPressed: event => {
|
||||||
Layout.fillHeight: true
|
if (text.length !== 0) {
|
||||||
Layout.topMargin: 2
|
// No filtering, just navigate grid
|
||||||
Layout.bottomMargin: 2
|
if (event.key === Qt.Key_Down) {
|
||||||
implicitWidth: height
|
grid.moveSelection(grid.columns);
|
||||||
buttonRadius: Appearance.rounding.full
|
wallpaperGrid.forceActiveFocus();
|
||||||
onClicked: root.useDarkMode = !root.useDarkMode
|
event.accepted = true;
|
||||||
contentItem: MaterialSymbol {
|
}
|
||||||
text: root.useDarkMode ? "dark_mode" : "light_mode"
|
if (event.key === Qt.Key_Up) {
|
||||||
iconSize: Appearance.font.pixelSize.larger
|
grid.moveSelection(-grid.columns);
|
||||||
}
|
wallpaperGrid.forceActiveFocus();
|
||||||
StyledToolTip {
|
event.accepted = true;
|
||||||
content: Translation.tr("Click to toggle light/dark mode (applied when wallpaper is chosen)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField {
|
|
||||||
id: filterField
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.topMargin: 2
|
|
||||||
Layout.bottomMargin: 2
|
|
||||||
implicitWidth: 200
|
|
||||||
padding: 10
|
|
||||||
placeholderText: focus ? Translation.tr("Search wallpapers") : Translation.tr("Hit \"/\" to search")
|
|
||||||
placeholderTextColor: Appearance.colors.colSubtext
|
|
||||||
color: Appearance.colors.colOnLayer0
|
|
||||||
font.pixelSize: Appearance.font.pixelSize.small
|
|
||||||
renderType: Text.NativeRendering
|
|
||||||
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
|
|
||||||
selectionColor: Appearance.colors.colSecondaryContainer
|
|
||||||
background: Rectangle {
|
|
||||||
color: Appearance.colors.colLayer1
|
|
||||||
radius: Appearance.rounding.full
|
|
||||||
}
|
|
||||||
|
|
||||||
onTextChanged: {
|
|
||||||
root.filterQuery = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
|
||||||
if (text.length !== 0) {
|
|
||||||
// No filtering, just navigate grid
|
|
||||||
if (event.key === Qt.Key_Down) {
|
|
||||||
grid.moveSelection(grid.columns);
|
|
||||||
wallpaperGrid.forceActiveFocus();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
if (event.key === Qt.Key_Up) {
|
|
||||||
grid.moveSelection(-grid.columns);
|
|
||||||
wallpaperGrid.forceActiveFocus();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
event.accepted = false;
|
|
||||||
}
|
}
|
||||||
|
event.accepted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.topMargin: 2
|
||||||
|
Layout.bottomMargin: 2
|
||||||
|
buttonRadius: Appearance.rounding.full
|
||||||
|
onClicked: {
|
||||||
|
GlobalStates.wallpaperSelectorOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RippleButton {
|
contentItem: StyledText {
|
||||||
Layout.fillHeight: true
|
text: "Cancel"
|
||||||
Layout.topMargin: 2
|
|
||||||
Layout.bottomMargin: 2
|
|
||||||
buttonRadius: Appearance.rounding.full
|
|
||||||
onClicked: {
|
|
||||||
GlobalStates.wallpaperSelectorOpen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: StyledText {
|
|
||||||
text: "Cancel"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ Singleton {
|
|||||||
property string nicePath: ""
|
property string nicePath: ""
|
||||||
function setDirectoryIfValid(path) {
|
function setDirectoryIfValid(path) {
|
||||||
validateDirProc.nicePath = FileUtils.trimFileProtocol(path).replace(/\/+$/, "")
|
validateDirProc.nicePath = FileUtils.trimFileProtocol(path).replace(/\/+$/, "")
|
||||||
|
if (/^\/*$/.test(validateDirProc.nicePath)) validateDirProc.nicePath = "/";
|
||||||
validateDirProc.exec(["test", "-d", nicePath])
|
validateDirProc.exec(["test", "-d", nicePath])
|
||||||
}
|
}
|
||||||
onExited: (exitCode, exitStatus) => {
|
onExited: (exitCode, exitStatus) => {
|
||||||
@@ -61,7 +62,7 @@ Singleton {
|
|||||||
id: files
|
id: files
|
||||||
folder: Qt.resolvedUrl(root.directory)
|
folder: Qt.resolvedUrl(root.directory)
|
||||||
nameFilters: root.extensions.map(ext => `*.${ext}`)
|
nameFilters: root.extensions.map(ext => `*.${ext}`)
|
||||||
showDirs: false
|
showDirs: true
|
||||||
showDotAndDotDot: false
|
showDotAndDotDot: false
|
||||||
showOnlyReadable: true
|
showOnlyReadable: true
|
||||||
sortField: FolderListModel.Time
|
sortField: FolderListModel.Time
|
||||||
|
|||||||
Reference in New Issue
Block a user