Krolyxon
Commit 229376d

Branches

Refs →

aurora.configquickshellauroraWallPicker.qml

237 lines · 10.1 KB · QML

1import QtQuick
2import QtQuick.Layouts
3import Quickshell
4import Quickshell.Wayland
5import Quickshell.Hyprland
6
7// Wallpaper picker: folder chips, filter, thumbnail grid. Enter / double-click applies (stays open
8// so you can try a few), Esc or click outside closes. Tab / Shift+Tab cycles folders.
9PanelWindow {
10 id: win
11 property bool opened: false
12 signal closeRequested()
13
14 visible: opened
15 implicitWidth: screen ? Math.round(screen.width * 0.7) : 1200
16 implicitHeight: screen ? Math.round(screen.height * 0.7) : 700
17 exclusiveZone: 0
18 color: "transparent"
19 WlrLayershell.layer: WlrLayer.Overlay
20 WlrLayershell.namespace: "aurora-wallpaper"
21 WlrLayershell.keyboardFocus: opened ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
22
23 HyprlandFocusGrab {
24 windows: [win]
25 active: win.opened
26 onCleared: win.closeRequested()
27 }
28
29 onOpenedChanged: {
30 if (!opened) return
31 filter.text = ""
32 Wall.refresh()
33 filter.forceActiveFocus()
34 }
35
36 readonly property var shown: {
37 var q = filter.text.toLowerCase(), out = [], l = Wall.files
38 for (var i = 0; i < l.length; i++)
39 if (q === "" || l[i].substring(l[i].lastIndexOf("/") + 1).toLowerCase().indexOf(q) >= 0) out.push(l[i])
40 return out
41 }
42 readonly property var chips: [""].concat(Wall.folders)
43
44 function cycleFolder(d) {
45 var i = chips.indexOf(Wall.folder)
46 Wall.listFolder(chips[(i + d + chips.length) % chips.length])
47 }
48 function name(p) { return p.substring(p.lastIndexOf("/") + 1) }
49
50 Rectangle {
51 anchors.fill: parent
52 color: Theme.bgA
53 border.width: 2
54 border.color: Theme.accent
55
56 ColumnLayout {
57 anchors.fill: parent
58 anchors.margins: 12
59 spacing: 10
60
61 // folder chips
62 Flow {
63 Layout.fillWidth: true
64 spacing: 4
65 Repeater {
66 model: win.chips
67 delegate: Rectangle {
68 id: chip
69 required property var modelData
70 readonly property bool current: Wall.folder === modelData
71 width: cl.implicitWidth + 18
72 height: 24
73 color: current ? Theme.accent : (cm.containsMouse ? Theme.surfaceHi : "transparent")
74 border.width: 1
75 border.color: current ? Theme.accent : Theme.line
76 Text {
77 id: cl
78 anchors.centerIn: parent
79 text: chip.modelData === "" ? "all" : chip.modelData
80 color: chip.current ? Theme.bg : Theme.fg
81 font.family: Theme.font
82 font.pixelSize: Theme.fontSize - 1
83 font.bold: chip.current
84 }
85 MouseArea {
86 id: cm
87 anchors.fill: parent
88 hoverEnabled: true
89 cursorShape: Qt.PointingHandCursor
90 onClicked: Wall.listFolder(chip.modelData)
91 }
92 }
93 }
94 }
95
96 // filter + actions
97 RowLayout {
98 Layout.fillWidth: true
99 spacing: 6
100 Rectangle {
101 Layout.fillWidth: true
102 implicitHeight: 30
103 color: Theme.surface
104 border.width: 1
105 border.color: filter.activeFocus ? Theme.accent : Theme.line
106 Text {
107 anchors.left: parent.left
108 anchors.leftMargin: 10
109 anchors.verticalCenter: parent.verticalCenter
110 text: Theme.iSearch
111 color: Theme.dim
112 font.family: Theme.font
113 font.pixelSize: Theme.fontSize
114 }
115 TextInput {
116 id: filter
117 anchors.fill: parent
118 anchors.leftMargin: 32
119 anchors.rightMargin: 10
120 verticalAlignment: TextInput.AlignVCenter
121 color: Theme.fg
122 selectionColor: Theme.accent
123 selectedTextColor: Theme.bg
124 font.family: Theme.font
125 font.pixelSize: Theme.fontSize
126 clip: true
127 onTextChanged: grid.currentIndex = 0
128 Keys.onEscapePressed: win.closeRequested()
129 Keys.onReturnPressed: if (grid.currentIndex >= 0) Wall.apply(win.shown[grid.currentIndex])
130 Keys.onEnterPressed: if (grid.currentIndex >= 0) Wall.apply(win.shown[grid.currentIndex])
131 Keys.onLeftPressed: event => { if (text === "") grid.moveCurrentIndexLeft(); else event.accepted = false }
132 Keys.onRightPressed: event => { if (text === "") grid.moveCurrentIndexRight(); else event.accepted = false }
133 Keys.onUpPressed: grid.moveCurrentIndexUp()
134 Keys.onDownPressed: grid.moveCurrentIndexDown()
135 Keys.onTabPressed: win.cycleFolder(1)
136 Keys.onBacktabPressed: win.cycleFolder(-1)
137 Keys.onPressed: event => {
138 if (event.key === Qt.Key_PageDown) { grid.currentIndex = Math.min(win.shown.length - 1, grid.currentIndex + grid.perPage); event.accepted = true }
139 else if (event.key === Qt.Key_PageUp) { grid.currentIndex = Math.max(0, grid.currentIndex - grid.perPage); event.accepted = true }
140 }
141 }
142 }
143 Text {
144 text: Wall.loading ? "..." : win.shown.length + " / " + Wall.files.length
145 color: Theme.dim
146 font.family: Theme.font
147 font.pixelSize: Theme.fontSize - 1
148 }
149 IconBtn { icon: Theme.iShuffle; label: "random"; onClicked: Wall.random() }
150 IconBtn { icon: Theme.iClose; onClicked: win.closeRequested() }
151 }
152
153 GridView {
154 id: grid
155 Layout.fillWidth: true
156 Layout.fillHeight: true
157 clip: true
158 readonly property int cols: Math.max(1, Math.floor(width / 240))
159 readonly property int perPage: cols * Math.max(1, Math.floor(height / cellHeight))
160 cellWidth: Math.floor(width / cols)
161 cellHeight: Math.round(cellWidth * 9 / 16) + 22
162 model: win.shown
163 currentIndex: 0
164 boundsBehavior: Flickable.StopAtBounds
165 highlightMoveDuration: 0
166 cacheBuffer: cellHeight * 2
167
168 delegate: Item {
169 id: cell
170 required property var modelData
171 required property int index
172 readonly property string hash: Qt.md5(modelData)
173 readonly property bool isCurrent: Wall.current === modelData
174 readonly property bool selected: grid.currentIndex === index
175 width: grid.cellWidth
176 height: grid.cellHeight
177
178 Rectangle {
179 anchors.fill: parent
180 anchors.margins: 4
181 color: Theme.surface
182 border.width: cell.selected || cell.isCurrent ? 2 : 1
183 border.color: cell.selected ? Theme.fg : (cell.isCurrent ? Theme.accent : Theme.line)
184
185 Image {
186 id: img
187 anchors.fill: parent
188 anchors.margins: 2
189 anchors.bottomMargin: 20
190 fillMode: Image.PreserveAspectCrop
191 asynchronous: true
192 cache: false
193 sourceSize.width: 320
194 sourceSize.height: 180
195 source: Wall.ready[cell.hash] ? "file://" + Wall.thumbDir + "/" + cell.hash + ".jpg" : ""
196 }
197 Text {
198 anchors.centerIn: img
199 visible: img.status !== Image.Ready
200 text: Theme.iImage
201 color: Theme.dim
202 font.family: Theme.font
203 font.pixelSize: 22
204 }
205 Text {
206 anchors.left: parent.left
207 anchors.right: parent.right
208 anchors.bottom: parent.bottom
209 anchors.margins: 4
210 height: 14
211 text: (cell.isCurrent ? Theme.iCheck + " " : "") + win.name(cell.modelData)
212 elide: Text.ElideMiddle
213 color: cell.isCurrent ? Theme.accent : Theme.dim
214 font.family: Theme.font
215 font.pixelSize: Theme.fontSize - 2
216 }
217 }
218 MouseArea {
219 anchors.fill: parent
220 cursorShape: Qt.PointingHandCursor
221 onClicked: grid.currentIndex = cell.index
222 onDoubleClicked: Wall.apply(cell.modelData)
223 }
224 }
225 }
226
227 Text {
228 Layout.fillWidth: true
229 text: "enter apply tab folder arrows move esc close " + Theme.iImage + " " + win.name(Wall.current)
230 elide: Text.ElideRight
231 color: Theme.dim
232 font.family: Theme.font
233 font.pixelSize: Theme.fontSize - 2
234 }
235 }
236 }
237}