aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/assistant/Picker.qml
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/assistant/Picker.qml')
-rw-r--r--desktop/assistant/Picker.qml143
1 files changed, 143 insertions, 0 deletions
diff --git a/desktop/assistant/Picker.qml b/desktop/assistant/Picker.qml
new file mode 100644
index 0000000..1465d1b
--- /dev/null
+++ b/desktop/assistant/Picker.qml
@@ -0,0 +1,143 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import Quickshell
+import QtQuick
+import QtQuick.Controls
+import Qt.labs.folderlistmodel
+import ".."
+
+// A file picker drawn in the card. A system dialog cannot be used: the card
+// is an overlay layer surface holding the keyboard, so a dialog window would
+// open beneath it, out of reach. Click a file to stage it and close,
+// Ctrl+click to stage and stay; the filter narrows this directory.
+Rectangle {
+ id: picker
+ property var canvas
+
+ readonly property string home: Quickshell.env("HOME") || "/"
+ property string dir: home
+
+ color: Qt.alpha(Theme.base, 0.97)
+ radius: 8
+
+ onVisibleChanged: if (visible) {
+ dir = canvas.pickDir || home;
+ field.text = "";
+ field.forceActiveFocus();
+ }
+
+ MouseArea { anchors.fill: parent } // keep clicks off what is beneath
+
+ function enter(path) { dir = path; field.text = ""; rows.currentIndex = 0; }
+ function up() { if (dir !== "/") enter(dir.slice(0, dir.lastIndexOf("/")) || "/"); }
+ function choose(i, keep) {
+ const isDir = files.get(i, "fileIsDir"), path = files.get(i, "filePath");
+ if (isDir) { enter(path); return; }
+ canvas.pickDir = dir;
+ canvas.stage(path);
+ if (!keep) canvas.pickerOpen = false;
+ }
+
+ FolderListModel {
+ id: files
+ folder: "file://" + picker.dir
+ showDirsFirst: true
+ showDotAndDotDot: false
+ // Hidden entries only on asking for them.
+ showHidden: field.text.startsWith(".")
+ caseSensitive: false
+ nameFilters: field.text ? ["*" + field.text + "*"] : ["*"]
+ sortCaseSensitive: false
+ }
+
+ Row {
+ id: bar
+ anchors { top: parent.top; left: parent.left; right: parent.right }
+ spacing: 6
+ Button { text: "Home"; onClicked: picker.enter(picker.home) }
+ Button { text: "Up"; enabled: picker.dir !== "/"; onClicked: picker.up() }
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ width: bar.width - x
+ text: picker.dir.startsWith(picker.home) ? "~" + picker.dir.slice(picker.home.length) : picker.dir
+ elide: Text.ElideLeft
+ color: Theme.subtext
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ }
+ }
+
+ TextField {
+ id: field
+ anchors { top: bar.bottom; topMargin: 8; left: parent.left; right: parent.right }
+ height: 32
+ placeholderText: "Filter"
+ color: Theme.text
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
+ background: Rectangle {
+ radius: 6
+ color: Qt.alpha(Theme.surface, 0.9)
+ border.width: 1
+ border.color: Qt.alpha(Theme.text, 0.15)
+ }
+ onTextChanged: rows.currentIndex = 0
+ Keys.onUpPressed: rows.decrementCurrentIndex()
+ Keys.onDownPressed: rows.incrementCurrentIndex()
+ Keys.onReturnPressed: event => { if (rows.count) picker.choose(rows.currentIndex, event.modifiers & Qt.ControlModifier); }
+ Keys.onEnterPressed: event => { if (rows.count) picker.choose(rows.currentIndex, event.modifiers & Qt.ControlModifier); }
+ }
+
+ ListView {
+ id: rows
+ anchors { top: field.bottom; topMargin: 8; left: parent.left; right: parent.right; bottom: parent.bottom }
+ clip: true
+ spacing: 2
+ boundsBehavior: Flickable.StopAtBounds
+ model: files
+ highlightMoveDuration: 0
+
+ delegate: Rectangle {
+ id: r
+ required property int index
+ required property string fileName
+ required property bool fileIsDir
+ readonly property bool staged: picker.canvas.staged.some(f => f.path === picker.dir.replace(/\/$/, "") + "/" + r.fileName)
+ width: rows.width
+ height: name.implicitHeight + 10
+ radius: 6
+ color: rows.currentIndex === r.index ? Qt.alpha(Theme.accent, 0.18)
+ : hover.hovered ? Qt.alpha(Theme.surface, 0.9) : "transparent"
+
+ HoverHandler { id: hover }
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: mouse => { rows.currentIndex = r.index; picker.choose(r.index, mouse.modifiers & Qt.ControlModifier); }
+ }
+ Text {
+ id: name
+ anchors { left: parent.left; leftMargin: 8; right: parent.right; rightMargin: 8; verticalCenter: parent.verticalCenter }
+ text: (r.fileIsDir ? r.fileName + "/" : r.fileName) + (r.staged ? " ✓" : "")
+ elide: Text.ElideMiddle
+ color: r.fileIsDir ? Theme.accent : Theme.text
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ }
+ }
+
+ Text {
+ anchors.centerIn: parent
+ visible: rows.count === 0
+ text: files.status === FolderListModel.Loading ? "Loading…" : "Nothing here"
+ color: Theme.overlay
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
+ }
+ }
+}