aurorac1f15d7
aurora: vim keys and /-search in the wallpaper picker
+62 −15
1 file changed
- M .config/quickshell/aurora/WallPicker.qml +62 −15
| @@ -4,8 +4,10 @@ import Quickshell | |||
| 4 | 4 | import Quickshell.Wayland | |
| 5 | 5 | import Quickshell.Hyprland | |
| 6 | 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. | |
| 7 | + | // Wallpaper picker: folder chips, filter, thumbnail grid. Vim-style: the grid has the keyboard | |
| 8 | + | // (hjkl/arrows move, g/G first/last, ctrl+d/u page), `/` starts searching (enter keeps the filter, | |
| 9 | + | // esc clears it). Enter / double-click applies (stays open so you can try a few), Esc / q or click | |
| 10 | + | // outside closes. Tab / Shift+Tab cycles folders. | |
| 9 | 11 | PanelWindow { | |
| 10 | 12 | id: win | |
| 11 | 13 | property bool opened: false | |
| @@ -30,7 +32,19 @@ PanelWindow { | |||
| 30 | 32 | if (!opened) return | |
| 31 | 33 | filter.text = "" | |
| 32 | 34 | Wall.refresh() | |
| 33 | − | filter.forceActiveFocus() | |
| 35 | + | grid.forceActiveFocus() | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | // search mode = the filter box has the keyboard (entered with `/` or by clicking it) | |
| 39 | + | readonly property bool searching: filter.activeFocus | |
| 40 | + | function search() { filter.forceActiveFocus() } | |
| 41 | + | function endSearch(clear) { | |
| 42 | + | if (clear) filter.text = "" | |
| 43 | + | grid.forceActiveFocus() | |
| 44 | + | } | |
| 45 | + | function applyCurrent() { if (grid.currentIndex >= 0 && grid.currentIndex < shown.length) Wall.apply(shown[grid.currentIndex]) } | |
| 46 | + | function page(d) { | |
| 47 | + | grid.currentIndex = Math.max(0, Math.min(shown.length - 1, grid.currentIndex + d * grid.perPage)) | |
| 34 | 48 | } | |
| 35 | 49 | ||
| 36 | 50 | readonly property var shown: { | |
| @@ -107,7 +121,18 @@ PanelWindow { | |||
| 107 | 121 | anchors.left: parent.left | |
| 108 | 122 | anchors.leftMargin: 10 | |
| 109 | 123 | anchors.verticalCenter: parent.verticalCenter | |
| 110 | − | text: Theme.iSearch | |
| 124 | + | text: win.searching ? "/" : Theme.iSearch | |
| 125 | + | color: win.searching ? Theme.accent : Theme.dim | |
| 126 | + | font.family: Theme.font | |
| 127 | + | font.pixelSize: Theme.fontSize | |
| 128 | + | font.bold: win.searching | |
| 129 | + | } | |
| 130 | + | Text { | |
| 131 | + | anchors.left: parent.left | |
| 132 | + | anchors.leftMargin: 32 | |
| 133 | + | anchors.verticalCenter: parent.verticalCenter | |
| 134 | + | visible: filter.text === "" && !win.searching | |
| 135 | + | text: "press / to search" | |
| 111 | 136 | color: Theme.dim | |
| 112 | 137 | font.family: Theme.font | |
| 113 | 138 | font.pixelSize: Theme.fontSize | |
| @@ -125,19 +150,14 @@ PanelWindow { | |||
| 125 | 150 | font.pixelSize: Theme.fontSize | |
| 126 | 151 | clip: true | |
| 127 | 152 | 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 } | |
| 153 | + | // like vim's `/`: enter keeps the filter and goes back to the grid, esc drops it | |
| 154 | + | Keys.onEscapePressed: win.endSearch(true) | |
| 155 | + | Keys.onReturnPressed: win.endSearch(false) | |
| 156 | + | Keys.onEnterPressed: win.endSearch(false) | |
| 133 | 157 | Keys.onUpPressed: grid.moveCurrentIndexUp() | |
| 134 | 158 | Keys.onDownPressed: grid.moveCurrentIndexDown() | |
| 135 | 159 | Keys.onTabPressed: win.cycleFolder(1) | |
| 136 | 160 | 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 | 161 | } | |
| 142 | 162 | } | |
| 143 | 163 | Text { | |
| @@ -165,6 +185,31 @@ PanelWindow { | |||
| 165 | 185 | highlightMoveDuration: 0 | |
| 166 | 186 | cacheBuffer: cellHeight * 2 | |
| 167 | 187 | ||
| 188 | + | // normal mode (arrows are handled by GridView itself) | |
| 189 | + | Keys.onTabPressed: win.cycleFolder(1) | |
| 190 | + | Keys.onBacktabPressed: win.cycleFolder(-1) | |
| 191 | + | Keys.onPressed: event => { | |
| 192 | + | var ctrl = event.modifiers & Qt.ControlModifier | |
| 193 | + | event.accepted = true | |
| 194 | + | if (event.key === Qt.Key_Escape) { | |
| 195 | + | if (filter.text !== "") filter.text = "" // first esc drops an active filter | |
| 196 | + | else win.closeRequested() | |
| 197 | + | } | |
| 198 | + | else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) win.applyCurrent() | |
| 199 | + | else if (event.key === Qt.Key_PageDown || (ctrl && event.key === Qt.Key_D)) win.page(1) | |
| 200 | + | else if (event.key === Qt.Key_PageUp || (ctrl && event.key === Qt.Key_U)) win.page(-1) | |
| 201 | + | else if (ctrl) event.accepted = false | |
| 202 | + | else if (event.text === "/") win.search() | |
| 203 | + | else if (event.text === "h") grid.moveCurrentIndexLeft() | |
| 204 | + | else if (event.text === "j") grid.moveCurrentIndexDown() | |
| 205 | + | else if (event.text === "k") grid.moveCurrentIndexUp() | |
| 206 | + | else if (event.text === "l") grid.moveCurrentIndexRight() | |
| 207 | + | else if (event.text === "g") grid.currentIndex = 0 | |
| 208 | + | else if (event.text === "G") grid.currentIndex = win.shown.length - 1 | |
| 209 | + | else if (event.text === "q") win.closeRequested() | |
| 210 | + | else event.accepted = false | |
| 211 | + | } | |
| 212 | + | ||
| 168 | 213 | delegate: Item { | |
| 169 | 214 | id: cell | |
| 170 | 215 | required property var modelData | |
| @@ -218,7 +263,7 @@ PanelWindow { | |||
| 218 | 263 | MouseArea { | |
| 219 | 264 | anchors.fill: parent | |
| 220 | 265 | cursorShape: Qt.PointingHandCursor | |
| 221 | − | onClicked: grid.currentIndex = cell.index | |
| 266 | + | onClicked: { grid.currentIndex = cell.index; grid.forceActiveFocus() } | |
| 222 | 267 | onDoubleClicked: Wall.apply(cell.modelData) | |
| 223 | 268 | } | |
| 224 | 269 | } | |
| @@ -226,7 +271,9 @@ PanelWindow { | |||
| 226 | 271 | ||
| 227 | 272 | Text { | |
| 228 | 273 | Layout.fillWidth: true | |
| 229 | − | text: "enter apply tab folder arrows move esc close " + Theme.iImage + " " + win.name(Wall.current) | |
| 274 | + | text: (win.searching ? "-- SEARCH -- enter done esc clear ↑↓ move " | |
| 275 | + | : "hjkl move / search enter apply tab folder g/G first/last esc close ") | |
| 276 | + | + Theme.iImage + " " + win.name(Wall.current) | |
| 230 | 277 | elide: Text.ElideRight | |
| 231 | 278 | color: Theme.dim | |
| 232 | 279 | font.family: Theme.font | |