wallpaper selector: add shadows

This commit is contained in:
end-4
2025-08-22 22:03:12 +07:00
parent 9cc576b98d
commit dac9ed2785
@@ -19,326 +19,344 @@ Item {
property int thumbnailHeight: 108 property int thumbnailHeight: 108
implicitHeight: columnLayout.implicitHeight implicitHeight: columnLayout.implicitHeight
implicitWidth: columnLayout.implicitWidth implicitWidth: columnLayout.implicitWidth
ColumnLayout { ColumnLayout {
id: columnLayout id: columnLayout
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
spacing: 8 spacing: 8
TextField { Item {
id: filterField implicitHeight: filterField.implicitHeight
implicitWidth: filterField.implicitWidth
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
implicitHeight: 40
implicitWidth: Appearance.sizes.searchWidth
padding: 10
placeholderText: "Search wallpapers..."
placeholderTextColor: Appearance.colors.colSubtext
color: Appearance.colors.colPrimary
background: Rectangle {
color: Appearance.colors.colLayer0
border.color: Appearance.colors.colLayer0Border
border.width: 1
radius: Appearance.rounding.small
}
font.family: Appearance.font.family.main
font.pixelSize: Appearance.font.pixelSize.normal
onTextChanged: { StyledRectangularShadow {
let newModel = []; target: filterField
if (text.length > 0) { }
for (let i = 0; i < Wallpapers.wallpapers.length; ++i) {
let wallpaperPath = Wallpapers.wallpapers[i]; TextField {
if (wallpaperPath.toLowerCase().includes(text.toLowerCase())) { id: filterField
newModel.push(wallpaperPath); implicitHeight: 40
implicitWidth: Appearance.sizes.searchWidth
padding: 10
placeholderText: "Search wallpapers..."
placeholderTextColor: Appearance.colors.colSubtext
color: Appearance.colors.colPrimary
background: Rectangle {
color: Appearance.colors.colLayer0
border.color: Appearance.colors.colLayer0Border
border.width: 1
radius: Appearance.rounding.small
}
font.family: Appearance.font.family.main
font.pixelSize: Appearance.font.pixelSize.normal
onTextChanged: {
let newModel = [];
if (text.length > 0) {
for (let i = 0; i < Wallpapers.wallpapers.length; ++i) {
let wallpaperPath = Wallpapers.wallpapers[i];
if (wallpaperPath.toLowerCase().includes(text.toLowerCase())) {
newModel.push(wallpaperPath);
}
}
panelWindow.filteredWallpapers = newModel;
} else {
panelWindow.filteredWallpapers = Wallpapers.wallpapers;
}
}
Keys.onPressed: event => {
if (text.length === 0) {
if (event.key === Qt.Key_Down || event.key === Qt.Key_Left || event.key === Qt.Key_Right) {
bg.forceActiveFocus();
if (event.key === Qt.Key_Down)
grid.moveSelection(grid.columns);
else if (event.key === Qt.Key_Left)
grid.moveSelection(-1);
else if (event.key === Qt.Key_Right)
grid.moveSelection(1);
event.accepted = true;
}
} else {
if (event.key === Qt.Key_Down) {
grid.moveSelection(grid.columns);
event.accepted = true;
bg.forceActiveFocus();
} }
} }
panelWindow.filteredWallpapers = newModel; if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
} else { grid.activateCurrent();
panelWindow.filteredWallpapers = Wallpapers.wallpapers; event.accepted = true;
} } else if (event.key === Qt.Key_Escape) {
} if (filterField.text.length > 0) {
filterField.text = "";
Keys.onPressed: event => { } else {
if (text.length === 0) { GlobalStates.wallpaperSelectorOpen = false;
if (event.key === Qt.Key_Down || event.key === Qt.Key_Left || event.key === Qt.Key_Right) { }
bg.forceActiveFocus();
if (event.key === Qt.Key_Down)
grid.moveSelection(grid.columns);
else if (event.key === Qt.Key_Left)
grid.moveSelection(-1);
else if (event.key === Qt.Key_Right)
grid.moveSelection(1);
event.accepted = true; event.accepted = true;
} }
} else {
if (event.key === Qt.Key_Down) {
grid.moveSelection(grid.columns);
event.accepted = true;
bg.forceActiveFocus();
}
}
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
grid.activateCurrent();
event.accepted = true;
} else if (event.key === Qt.Key_Escape) {
if (filterField.text.length > 0) {
filterField.text = "";
} else {
GlobalStates.wallpaperSelectorOpen = false;
}
event.accepted = true;
} }
} }
} }
Rectangle { Item {
id: bg implicitWidth: wallpaperGridBackground.implicitWidth
focus: true implicitHeight: wallpaperGridBackground.implicitHeight
color: Appearance.colors.colLayer0
border.width: 1 StyledRectangularShadow {
border.color: Appearance.colors.colLayer0Border target: wallpaperGridBackground
radius: Appearance.rounding.screenRounding
// Layout.alignment: Qt.AlignHCenter
property int calculatedRows: Math.ceil(grid.count / grid.columns)
implicitWidth: {
if (panelWindow.filteredWallpapers.length === 0) {
return 300;
} else if (panelWindow.filteredWallpapers.length < grid.columns) {
return panelWindow.filteredWallpapers.length * grid.cellWidth + 16;
} else {
return Math.min(panelWindow.width * 0.7, 900);
}
} }
Rectangle {
id: wallpaperGridBackground
implicitHeight: { Layout.alignment: Qt.AlignHCenter
if (panelWindow.filteredWallpapers.length === 0) { color: Appearance.colors.colLayer0
return 100; radius: Appearance.rounding.screenRounding
} else { border.width: 1
return Math.min(panelWindow.height * 0.6, Math.min(calculatedRows, 3) * grid.cellHeight + 16); border.color: Appearance.colors.colLayer0Border
} focus: true
}
Behavior on implicitWidth { property int calculatedRows: Math.ceil(grid.count / grid.columns)
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on implicitHeight { implicitWidth: {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this) if (panelWindow.filteredWallpapers.length === 0) {
} return 300;
} else if (panelWindow.filteredWallpapers.length < grid.columns) {
Keys.onPressed: event => { return panelWindow.filteredWallpapers.length * grid.cellWidth + 16;
if (event.key === Qt.Key_Escape) {
GlobalStates.wallpaperSelectorOpen = false;
event.accepted = true;
} else if (event.key === Qt.Key_Left) {
grid.moveSelection(-1);
event.accepted = true;
} else if (event.key === Qt.Key_Right) {
grid.moveSelection(1);
event.accepted = true;
} else if (event.key === Qt.Key_Up) {
if (grid.currentIndex < grid.columns) {
filterField.forceActiveFocus();
} else { } else {
grid.moveSelection(-grid.columns); return Math.min(panelWindow.width * 0.7, 900);
} }
event.accepted = true;
} else if (event.key === Qt.Key_Down) {
grid.moveSelection(grid.columns);
event.accepted = true;
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
grid.activateCurrent();
event.accepted = true;
} else if (event.key === Qt.Key_Backspace) {
if (filterField.text.length > 0) {
filterField.text = filterField.text.substring(0, filterField.text.length - 1);
}
filterField.forceActiveFocus();
event.accepted = true;
} else {
filterField.forceActiveFocus();
if (event.text.length > 0) {
filterField.text += event.text;
filterField.cursorPosition = filterField.text.length;
}
event.accepted = true;
}
}
GridView {
id: grid
visible: panelWindow.filteredWallpapers.length > 0
property int currentIndex: 0
readonly property int columns: root.columns
readonly property int rows: Math.max(1, Math.ceil(count / columns))
anchors.fill: parent
cellWidth: root.thumbnailWidth
cellHeight: root.thumbnailHeight
clip: true
interactive: true
keyNavigationWraps: true
boundsBehavior: Flickable.StopAtBounds
cacheBuffer: cellHeight * 2
ScrollBar.horizontal: ScrollBar {
policy: ScrollBar.AsNeeded
}
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
} }
model: panelWindow.filteredWallpapers implicitHeight: {
onModelChanged: currentIndex = 0 if (panelWindow.filteredWallpapers.length === 0) {
return 100;
} else {
return Math.min(panelWindow.height * 0.6, Math.min(calculatedRows, 3) * grid.cellHeight + 16);
}
}
function moveSelection(delta) { Behavior on implicitWidth {
for (let i = 0; i < count; i++) { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
const item = itemAtIndex(i); }
if (item) {
item.isHovered = false; Behavior on implicitHeight {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
GlobalStates.wallpaperSelectorOpen = false;
event.accepted = true;
} else if (event.key === Qt.Key_Left) {
grid.moveSelection(-1);
event.accepted = true;
} else if (event.key === Qt.Key_Right) {
grid.moveSelection(1);
event.accepted = true;
} else if (event.key === Qt.Key_Up) {
if (grid.currentIndex < grid.columns) {
filterField.forceActiveFocus();
} else {
grid.moveSelection(-grid.columns);
} }
event.accepted = true;
} else if (event.key === Qt.Key_Down) {
grid.moveSelection(grid.columns);
event.accepted = true;
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
grid.activateCurrent();
event.accepted = true;
} else if (event.key === Qt.Key_Backspace) {
if (filterField.text.length > 0) {
filterField.text = filterField.text.substring(0, filterField.text.length - 1);
}
filterField.forceActiveFocus();
event.accepted = true;
} else {
filterField.forceActiveFocus();
if (event.text.length > 0) {
filterField.text += event.text;
filterField.cursorPosition = filterField.text.length;
}
event.accepted = true;
} }
currentIndex = Math.max(0, Math.min(count - 1, currentIndex + delta));
positionViewAtIndex(currentIndex, GridView.Contain);
}
function activateCurrent() {
const path = model[currentIndex];
if (!path)
return;
GlobalStates.wallpaperSelectorOpen = false;
filterField.text = "";
Wallpapers.apply(path);
} }
delegate: Item { GridView {
width: grid.cellWidth id: grid
height: grid.cellHeight visible: panelWindow.filteredWallpapers.length > 0
property bool isHovered: false
Rectangle { property int currentIndex: 0
anchors.fill: parent readonly property int columns: root.columns
radius: Appearance.rounding.windowRounding readonly property int rows: Math.max(1, Math.ceil(count / columns))
color: Appearance.colors.colLayer1
border.width: (index === grid.currentIndex || parent.isHovered) ? 3 : 0 anchors.fill: parent
border.color: Appearance.colors.colSecondary cellWidth: root.thumbnailWidth
cellHeight: root.thumbnailHeight
clip: true
interactive: true
keyNavigationWraps: true
boundsBehavior: Flickable.StopAtBounds
cacheBuffer: cellHeight * 2
ScrollBar.horizontal: ScrollBar {
policy: ScrollBar.AsNeeded
}
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
} }
Rectangle { model: panelWindow.filteredWallpapers
anchors.fill: parent onModelChanged: currentIndex = 0
anchors.margins: 8
color: Appearance.colors.colLayer2
radius: Appearance.rounding.small
Image { function moveSelection(delta) {
id: thumbnailImage for (let i = 0; i < count; i++) {
const item = itemAtIndex(i);
if (item) {
item.isHovered = false;
}
}
currentIndex = Math.max(0, Math.min(count - 1, currentIndex + delta));
positionViewAtIndex(currentIndex, GridView.Contain);
}
function activateCurrent() {
const path = model[currentIndex];
if (!path)
return;
GlobalStates.wallpaperSelectorOpen = false;
filterField.text = "";
Wallpapers.apply(path);
}
delegate: Item {
width: grid.cellWidth
height: grid.cellHeight
property bool isHovered: false
Rectangle {
anchors.fill: parent anchors.fill: parent
source: { radius: Appearance.rounding.windowRounding
const resolvedUrl = Qt.resolvedUrl(modelData); color: Appearance.colors.colLayer1
const md5Hash = Qt.md5(resolvedUrl); border.width: (index === grid.currentIndex || parent.isHovered) ? 3 : 0
const cacheSize = "normal" border.color: Appearance.colors.colSecondary
const thumbnailPath = `${Directories.genericCache}/thumbnails/${cacheSize}/${md5Hash}.png`; }
return thumbnailPath
}
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: false
smooth: true
mipmap: false
sourceSize.width: Math.min(128, grid.cellWidth - 16) Rectangle {
sourceSize.height: Math.min(96, grid.cellHeight - 16) anchors.fill: parent
anchors.margins: 8
color: Appearance.colors.colLayer2
radius: Appearance.rounding.small
opacity: status === Image.Ready ? 1 : 0 Image {
Behavior on opacity { id: thumbnailImage
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) anchors.fill: parent
} source: {
const resolvedUrl = Qt.resolvedUrl(modelData);
const md5Hash = Qt.md5(resolvedUrl);
const cacheSize = "normal";
const thumbnailPath = `${Directories.genericCache}/thumbnails/${cacheSize}/${md5Hash}.png`;
return thumbnailPath;
}
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: false
smooth: true
mipmap: false
layer.enabled: true sourceSize.width: Math.min(128, grid.cellWidth - 16)
layer.effect: OpacityMask { sourceSize.height: Math.min(96, grid.cellHeight - 16)
maskSource: Rectangle {
width: thumbnailImage.width opacity: status === Image.Ready ? 1 : 0
height: thumbnailImage.height Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: thumbnailImage.width
height: thumbnailImage.height
radius: Appearance.rounding.small
}
}
Rectangle {
anchors.fill: parent
color: "transparent"
border.width: 1
border.color: Appearance.colors.colOutlineVariant
radius: Appearance.rounding.small radius: Appearance.rounding.small
} }
} }
Rectangle {
anchors.fill: parent
color: "transparent"
border.width: 1
border.color: Appearance.colors.colOutlineVariant
radius: Appearance.rounding.small
}
} }
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onEntered: { onEntered: {
for (let i = 0; i < grid.count; i++) { for (let i = 0; i < grid.count; i++) {
const item = grid.itemAtIndex(i); const item = grid.itemAtIndex(i);
if (item && item !== parent) { if (item && item !== parent) {
item.isHovered = false; item.isHovered = false;
}
} }
parent.isHovered = true;
grid.currentIndex = index;
}
onExited: {
parent.isHovered = false;
}
onClicked: {
GlobalStates.wallpaperSelectorOpen = false;
filterField.text = "";
Wallpapers.apply(modelData);
} }
parent.isHovered = true;
grid.currentIndex = index;
} }
onExited: { }
parent.isHovered = false;
} add: Transition {
onClicked: { from: "*"
GlobalStates.wallpaperSelectorOpen = false; to: "*"
filterField.text = ""; ParallelAnimation {
Wallpapers.apply(modelData); PropertyAnimation {
property: "x"
from: grid.contentX + (grid.width / 2) - width / 2
}
PropertyAnimation {
property: "y"
from: grid.contentY + (grid.height / 2) - height / 2
}
NumberAnimation {
property: "scale"
from: 0.0
to: 1.0
duration: animationCurves.expressiveDefaultSpatialDuration
easing.bezierCurve: animationCurves.expressiveDefaultSpatial
}
NumberAnimation {
property: "opacity"
from: 0.0
to: 1.0
duration: animationCurves.expressiveDefaultSpatialDuration
easing.bezierCurve: animationCurves.expressiveDefaultSpatial
}
} }
} }
} }
add: Transition { Label {
from: "*" id: noWallpapersFoundLabel
to: "*" visible: panelWindow.filteredWallpapers.length === 0
ParallelAnimation { anchors.centerIn: parent
PropertyAnimation { text: "No wallpapers found"
property: "x" font.family: Appearance.font.family.main
from: grid.contentX + (grid.width / 2) - width / 2 font.pixelSize: Appearance.font.pixelSize.normal
} color: Appearance.colors.colSubtext
PropertyAnimation {
property: "y"
from: grid.contentY + (grid.height / 2) - height / 2
}
NumberAnimation {
property: "scale"
from: 0.0
to: 1.0
duration: animationCurves.expressiveDefaultSpatialDuration
easing.bezierCurve: animationCurves.expressiveDefaultSpatial
}
NumberAnimation {
property: "opacity"
from: 0.0
to: 1.0
duration: animationCurves.expressiveDefaultSpatialDuration
easing.bezierCurve: animationCurves.expressiveDefaultSpatial
}
}
} }
} }
Label {
id: noWallpapersFoundLabel
visible: panelWindow.filteredWallpapers.length === 0
anchors.centerIn: parent
text: "No wallpapers found"
font.family: Appearance.font.family.main
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.colors.colSubtext
}
} }
} }
@@ -350,5 +368,4 @@ Item {
} }
} }
} }
}
}