diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-16 15:23:41 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-16 15:23:41 +0200 |
| commit | f5da8d8ee2cb4ace1fa6841437b4798e7799c100 (patch) | |
| tree | 9a1d51d8e61782852de2779cc77bfa5d2ca94a65 /desktop/assistant/Picker.qml | |
| parent | 13a10999f53802858f87883db381842d741eab69 (diff) | |
| download | quickshell-f5da8d8ee2cb4ace1fa6841437b4798e7799c100.tar.gz quickshell-f5da8d8ee2cb4ace1fa6841437b4798e7799c100.zip | |
feat(assistant): entry line, file state, staged files and a picker
The card has three states: an entry line, a file state for staged files with Chat about it, and the chat. SUPER+Home resumes a conversation active in the last 10 minutes and otherwise starts a new one. The paperclip opens a picker drawn in the card, since a system dialog would open beneath the overlay that holds the keyboard. Button gains a font alias for the paperclip glyph, and the canvas a socketPath property so a check can point it at a scratch daemon.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/assistant/Picker.qml')
| -rw-r--r-- | desktop/assistant/Picker.qml | 143 |
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 } + } + } +} |
