diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-16 16:45:36 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-16 16:45:36 +0200 |
| commit | 193cd8c2e1cc7e16b1389b95036bf7f4f99f97e0 (patch) | |
| tree | ee6e88c3069baf51462f8f036ae8e69da3bd2902 /desktop/assistant/Card.qml | |
| parent | 607934e74ffabc1dcce72b29fc28e92a669e54ce (diff) | |
| download | quickshell-193cd8c2e1cc7e16b1389b95036bf7f4f99f97e0.tar.gz quickshell-193cd8c2e1cc7e16b1389b95036bf7f4f99f97e0.zip | |
feat(assistant): Ask and Do as captioned tile grids with icons
The actions sat as buttons bunched at the left of the card. Each group is
now a grid of equal tiles across the card's width, icon over label,
about 150 px a column, with a small Ask or Do caption. Icons are Font
Awesome and Octicons codepoints checked in Inconsolata Nerd Font's cmap;
a saved preset without one gets a fallback. The lists referenced from
QML are var, since a library's top-level const is not reliably exposed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/assistant/Card.qml')
| -rw-r--r-- | desktop/assistant/Card.qml | 136 |
1 files changed, 100 insertions, 36 deletions
diff --git a/desktop/assistant/Card.qml b/desktop/assistant/Card.qml index 02ee476..069d3a0 100644 --- a/desktop/assistant/Card.qml +++ b/desktop/assistant/Card.qml @@ -205,7 +205,87 @@ Rectangle { // Ask: canned questions matching the staged kinds (Presets.js). A click // sends one; Shift+click puts it in the input to edit first. - Flow { + // One action as a tile: its icon over its label, the tiles of a group + // sharing the card's width equally. + component Tile: Rectangle { + id: tile + property string icon + property string label + property bool on: true + signal picked(var mouse) + implicitHeight: 62 + radius: 8 + opacity: on ? 1 : 0.4 + color: area.pressed && on ? Qt.alpha(Theme.accent, 0.35) + : area.containsMouse && on ? Qt.alpha(Theme.accent, 0.18) + : Qt.alpha(Theme.surface, 0.8) + border.width: 1 + border.color: Qt.alpha(Theme.text, 0.12) + Glow { anchors.fill: parent; z: -1; radius: parent.radius; on: area.containsMouse && tile.on } + Column { + anchors.centerIn: parent + width: parent.width - 12 + spacing: 4 + Text { + width: parent.width + horizontalAlignment: Text.AlignHCenter + text: tile.icon + color: Theme.accent + font { family: Theme.iconFamily; pixelSize: Theme.fontSize + 2 } + } + Text { + width: parent.width + horizontalAlignment: Text.AlignHCenter + text: tile.label + elide: Text.ElideRight + color: Theme.text + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + } + MouseArea { + id: area + anchors.fill: parent + hoverEnabled: true + cursorShape: tile.on ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: mouse => { if (tile.on) tile.picked(mouse); } + } + } + + // A captioned grid of tiles; columns follow the width, about 150 px each. + component Group: Column { + id: group + property string caption + property var items: [] + property bool on: true + signal picked(var item, var mouse) + spacing: 6 + Text { + text: group.caption + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 5; capitalization: Font.AllUppercase } + } + Grid { + id: grid + width: group.width + spacing: 8 + columns: Math.max(1, Math.floor((width + spacing) / (150 + spacing))) + Repeater { + model: group.items + Tile { + required property var modelData + width: (grid.width - (grid.columns - 1) * grid.spacing) / grid.columns + icon: modelData.icon ?? Presets.FALLBACK + label: modelData.label + on: group.on && (modelData.op !== "md" || card.canvas.connected) + onPicked: mouse => group.picked(modelData, mouse) + } + } + } + } + + // Ask: canned questions matching the staged kinds (Presets.js). A click + // sends one; Shift+click puts it in the input to edit first. + Group { id: asks readonly property var offered: Presets.offered(Presets.list(Prefs.presets), card.canvas.staged) visible: card.mode === "file" && offered.length > 0 && !card.canvas.opRunning @@ -213,57 +293,41 @@ Rectangle { bottom: dos.visible ? dos.top : status.visible ? status.top : actions.top; bottomMargin: 10 left: parent.left; right: parent.right; leftMargin: 14; rightMargin: 14 } - spacing: 8 - - Repeater { - model: asks.offered - Button { - required property var modelData - text: modelData.label - enabled: card.canvas.connected && !card.canvas.blocked - onClicked: mouse => { - if (mouse && (mouse.modifiers & Qt.ShiftModifier)) { - input.text = Presets.prompt(modelData); - input.cursorPosition = input.text.length; - input.forceActiveFocus(); - } else { - card.canvas.ask(Presets.prompt(modelData)); - } - } + caption: "Ask" + items: offered + on: card.canvas.connected && !card.canvas.blocked + onPicked: (p, mouse) => { + if (mouse.modifiers & Qt.ShiftModifier) { + input.text = Presets.prompt(p); + input.cursorPosition = input.text.length; + input.forceActiveFocus(); + } else { + card.canvas.ask(Presets.prompt(p)); } } } // Do: local operations (Ops.js, run by fileops or the daemon's write). - // Compress… swaps the row for its formats. Hidden while one runs. + // Compress… swaps the group for its formats. Hidden while one runs. property bool compressing: false Connections { target: card.canvas; function onStagedChanged() { card.compressing = false; } } - Flow { + Group { id: dos readonly property var offered: card.compressing - ? Ops.COMPRESS.concat([{ label: "Back", op: "back" }]) + ? Ops.COMPRESS.concat([{ label: "Back", op: "back", icon: "" }]) : Ops.offered(card.canvas.staged) visible: card.mode === "file" && offered.length > 0 && !card.canvas.opRunning anchors { bottom: status.visible ? status.top : actions.top; bottomMargin: 10 left: parent.left; right: parent.right; leftMargin: 14; rightMargin: 14 } - spacing: 8 - - Repeater { - model: dos.offered - Button { - required property var modelData - text: modelData.label - // Local operations need no daemon; To .md does. - enabled: modelData.op !== "md" || card.canvas.connected - onClicked: { - if (modelData.op === "compress") card.compressing = true; - else if (modelData.op === "back") card.compressing = false; - else { card.compressing = false; card.canvas.runOp(modelData); } - } - } + caption: card.compressing ? "Compress as" : "Do" + items: offered + onPicked: o => { + if (o.op === "compress") card.compressing = true; + else if (o.op === "back") card.compressing = false; + else { card.compressing = false; card.canvas.runOp(o); } } } |
