aurorab7d60e8
aurora: nothing os redesign, phase 4 (overlays)
Power menu as big Doto words with an uptime ring; runner, clipboard and glyph picker as glyph-style cards over a dimmed screen (search pill with live caret, chips, round icons); overview with Doto workspace numbers; polkit/bt prompt with a dot ring and dotted password pill; dock as a pill of round icons; slurp themed for region recording and OCR.
+1038 −801
10 files changed
- M .config/quickshell/aurora/AuthPrompt.qml +121 −86
- M .config/quickshell/aurora/CharPicker.qml +148 −197
- M .config/quickshell/aurora/ClipWindow.qml +300 −162
- M .config/quickshell/aurora/Dock.qml +37 −24
- M .config/quickshell/aurora/Overview.qml +64 −39
- M .config/quickshell/aurora/PowerMenu.qml +126 −117
- M .config/quickshell/aurora/Rec.qml +1 −1
- M .config/quickshell/aurora/Runner.qml +235 −174
- M .config/quickshell/aurora/Shot.qml +1 −1
- M .config/quickshell/aurora/Theme.qml +5 −0
| @@ -7,6 +7,8 @@ import Quickshell.Wayland | |||
| 7 | 7 | // (Agents.bt: confirm a passkey, type a PIN/passkey, or show one to type on the device). Polkit wins | |
| 8 | 8 | // if both are pending. Overlay layer with exclusive keyboard on the focused monitor; Enter accepts, | |
| 9 | 9 | // Esc cancels. Typed text is cleared after every attempt, never logged. | |
| 10 | + | // Looks like glyph's lock surface (github.com/Nebelung-cloud/glyph-rice, GPL-3.0, lock/LockSurface.qml): a lock in a ring | |
| 11 | + | // of dots, the password in a pill as dots with the live caret; a wrong password shakes it. | |
| 10 | 12 | PanelWindow { | |
| 11 | 13 | id: win | |
| 12 | 14 | readonly property var flow: Agents.flow | |
| @@ -21,8 +23,8 @@ PanelWindow { | |||
| 21 | 23 | readonly property bool needsInput: mode === "polkit" || btInput | |
| 22 | 24 | ||
| 23 | 25 | visible: opened | |
| 24 | − | implicitWidth: 420 | |
| 25 | − | implicitHeight: col.implicitHeight + 32 | |
| 26 | + | implicitWidth: 480 | |
| 27 | + | implicitHeight: col.implicitHeight + 60 | |
| 26 | 28 | exclusiveZone: 0 | |
| 27 | 29 | color: "transparent" | |
| 28 | 30 | WlrLayershell.layer: WlrLayer.Overlay | |
| @@ -56,7 +58,7 @@ PanelWindow { | |||
| 56 | 58 | target: win.flow | |
| 57 | 59 | function onIsResponseRequiredChanged() { if (win.flow.isResponseRequired) win.reset() } | |
| 58 | 60 | // polkit asks again right after a failure (isResponseRequired goes true with failed set) | |
| 59 | − | function onAuthenticationFailed() { win.error = "wrong password, try again"; win.reset() } | |
| 61 | + | function onAuthenticationFailed() { win.error = "wrong password, try again"; shake.restart(); win.reset() } | |
| 60 | 62 | } | |
| 61 | 63 | ||
| 62 | 64 | // keyboard for the button-only bluetooth prompts | |
| @@ -67,77 +69,91 @@ PanelWindow { | |||
| 67 | 69 | Keys.onEscapePressed: win.cancel() | |
| 68 | 70 | } | |
| 69 | 71 | ||
| 72 | + | readonly property bool bad: win.error !== "" || (win.mode === "polkit" && win.flow && win.flow.supplementaryIsError) | |
| 73 | + | ||
| 70 | 74 | Rectangle { | |
| 71 | 75 | anchors.fill: parent | |
| 72 | − | color: Theme.bgA | |
| 73 | − | border.width: 2 | |
| 74 | − | border.color: win.error !== "" || (win.mode === "polkit" && win.flow && win.flow.supplementaryIsError) ? Theme.err : Theme.accent | |
| 76 | + | radius: 36 | |
| 77 | + | color: Theme.s1 | |
| 78 | + | border.width: 1 | |
| 79 | + | border.color: win.bad ? Theme.err : Theme.s2 | |
| 75 | 80 | ||
| 76 | 81 | ColumnLayout { | |
| 77 | 82 | id: col | |
| 78 | 83 | anchors.left: parent.left | |
| 79 | 84 | anchors.right: parent.right | |
| 80 | 85 | anchors.top: parent.top | |
| 81 | − | anchors.margins: 16 | |
| 82 | − | spacing: 10 | |
| 86 | + | anchors.margins: 30 | |
| 87 | + | spacing: 14 | |
| 83 | 88 | ||
| 84 | 89 | RowLayout { | |
| 85 | 90 | Layout.fillWidth: true | |
| 86 | − | spacing: 10 | |
| 87 | − | Text { | |
| 88 | − | text: win.mode === "bt" ? Theme.iBt : Theme.iLock | |
| 89 | − | color: Theme.accent | |
| 90 | − | font.family: Theme.font | |
| 91 | − | font.pixelSize: Theme.fontSize + 8 | |
| 91 | + | spacing: 16 | |
| 92 | + | Item { // the lock / bluetooth glyph in a ring of dots | |
| 93 | + | width: 64 | |
| 94 | + | height: 64 | |
| 95 | + | Ring { anchors.fill: parent; anchors.margins: 3; n: 24; dot: 3.5; on: win.bad ? Theme.err : Theme.fill } | |
| 96 | + | Icon { | |
| 97 | + | anchors.centerIn: parent | |
| 98 | + | text: win.mode === "bt" ? Theme.iBt : Theme.iLock | |
| 99 | + | font.pixelSize: 24 | |
| 100 | + | color: win.bad ? Theme.err : Theme.fg | |
| 101 | + | } | |
| 92 | 102 | } | |
| 93 | − | SectionLabel { | |
| 103 | + | Column { | |
| 94 | 104 | Layout.fillWidth: true | |
| 95 | − | text: win.mode === "bt" ? "bluetooth pairing" : "authentication required" | |
| 105 | + | spacing: 4 | |
| 106 | + | Lb { text: win.mode === "bt" ? "bluetooth pairing" : "authentication" } | |
| 107 | + | Text { | |
| 108 | + | width: parent.width | |
| 109 | + | text: win.mode === "bt" ? (win.bt ? win.bt.alias : "") : "Required" | |
| 110 | + | elide: Text.ElideRight | |
| 111 | + | color: Theme.fg | |
| 112 | + | font.family: Theme.ui | |
| 113 | + | font.pixelSize: 20 | |
| 114 | + | font.weight: Font.DemiBold | |
| 115 | + | } | |
| 96 | 116 | } | |
| 97 | 117 | } | |
| 98 | 118 | Text { | |
| 99 | 119 | Layout.fillWidth: true | |
| 100 | − | color: Theme.fg | |
| 120 | + | color: Theme.fgDim | |
| 101 | 121 | wrapMode: Text.Wrap | |
| 102 | − | font.family: Theme.font | |
| 103 | − | font.pixelSize: Theme.fontSize | |
| 122 | + | font.family: Theme.ui | |
| 123 | + | font.pixelSize: 15 | |
| 104 | 124 | text: { | |
| 105 | 125 | if (win.mode === "polkit") return win.flow ? win.flow.message : "" | |
| 106 | 126 | if (!win.bt) return "" | |
| 107 | 127 | var a = win.bt.alias | |
| 108 | 128 | switch (win.btMethod) { | |
| 109 | − | case "RequestConfirmation": return "pair with " + a + "? check that it shows the same code:" | |
| 110 | − | case "RequestPinCode": return "enter the PIN for " + a + " (often 0000 or 1234)" | |
| 111 | − | case "RequestPasskey": return "enter the 6-digit passkey shown on " + a | |
| 129 | + | case "RequestConfirmation": return "Pair with " + a + "? Check that it shows the same code:" | |
| 130 | + | case "RequestPinCode": return "Enter the PIN for " + a + " (often 0000 or 1234)" | |
| 131 | + | case "RequestPasskey": return "Enter the 6-digit passkey shown on " + a | |
| 112 | 132 | case "DisplayPasskey": | |
| 113 | − | case "DisplayPinCode": return "type this code on " + a + ", then press Enter there:" | |
| 114 | − | case "RequestAuthorization": return "allow " + a + " to pair with this computer?" | |
| 115 | − | case "AuthorizeService": return "allow " + a + " to use a service?" | |
| 133 | + | case "DisplayPinCode": return "Type this code on " + a + ", then press Enter there:" | |
| 134 | + | case "RequestAuthorization": return "Allow " + a + " to pair with this computer?" | |
| 135 | + | case "AuthorizeService": return "Allow " + a + " to use a service?" | |
| 116 | 136 | } | |
| 117 | 137 | return a | |
| 118 | 138 | } | |
| 119 | 139 | } | |
| 120 | − | Text { | |
| 140 | + | Lb { | |
| 121 | 141 | Layout.fillWidth: true | |
| 122 | 142 | visible: text !== "" | |
| 143 | + | px: 10 | |
| 144 | + | color: Theme.mute | |
| 145 | + | elide: Text.ElideMiddle | |
| 123 | 146 | text: win.mode === "polkit" ? (win.flow ? win.flow.actionId : "") | |
| 124 | 147 | : (win.btMethod === "AuthorizeService" ? win.bt.uuid : "") | |
| 125 | − | color: Theme.dim | |
| 126 | − | elide: Text.ElideMiddle | |
| 127 | − | font.family: Theme.font | |
| 128 | − | font.pixelSize: Theme.fontSize - 2 | |
| 129 | 148 | } | |
| 130 | 149 | ||
| 131 | 150 | // bluetooth passkey / PIN to compare or type on the device | |
| 132 | − | Text { | |
| 133 | − | Layout.fillWidth: true | |
| 151 | + | Num { | |
| 152 | + | Layout.alignment: Qt.AlignHCenter | |
| 134 | 153 | visible: win.mode === "bt" && win.btCode !== "" && !win.btInput | |
| 135 | − | horizontalAlignment: Text.AlignHCenter | |
| 136 | 154 | text: win.btCode | |
| 137 | − | color: Theme.accent | |
| 138 | − | font.family: Theme.font | |
| 139 | − | font.pixelSize: Theme.fontSize * 3 | |
| 140 | − | font.bold: true | |
| 155 | + | font.pixelSize: 64 | |
| 156 | + | color: Theme.fill | |
| 141 | 157 | } | |
| 142 | 158 | ||
| 143 | 159 | // more than one admin: pick who authenticates | |
| @@ -147,81 +163,100 @@ PanelWindow { | |||
| 147 | 163 | spacing: 4 | |
| 148 | 164 | Repeater { | |
| 149 | 165 | model: win.mode === "polkit" && win.flow ? win.flow.identities : [] | |
| 150 | − | delegate: IconBtn { | |
| 166 | + | delegate: Chip { | |
| 151 | 167 | required property var modelData | |
| 152 | − | implicitHeight: 24 | |
| 153 | − | label: modelData.displayName || String(modelData) | |
| 154 | − | active: win.flow && win.flow.selectedIdentity === modelData | |
| 168 | + | text: modelData.displayName || String(modelData) | |
| 169 | + | selected: win.flow && win.flow.selectedIdentity === modelData | |
| 155 | 170 | onClicked: win.flow.selectedIdentity = modelData | |
| 156 | 171 | } | |
| 157 | 172 | } | |
| 158 | 173 | } | |
| 159 | 174 | ||
| 160 | − | Rectangle { | |
| 175 | + | Item { | |
| 161 | 176 | Layout.fillWidth: true | |
| 162 | 177 | visible: win.needsInput | |
| 163 | − | implicitHeight: 32 | |
| 164 | − | color: Theme.surface | |
| 165 | − | border.width: 1 | |
| 166 | − | border.color: pass.activeFocus ? Theme.accent : Theme.line | |
| 167 | − | Text { | |
| 168 | − | id: promptTxt | |
| 169 | − | anchors.left: parent.left | |
| 170 | − | anchors.leftMargin: 10 | |
| 171 | − | anchors.verticalCenter: parent.verticalCenter | |
| 172 | − | text: win.mode === "bt" ? (win.btMethod === "RequestPinCode" ? "PIN" : "passkey") | |
| 173 | − | : (win.flow && win.flow.inputPrompt !== "" ? win.flow.inputPrompt.replace(/:\s*$/, "") : "password") | |
| 174 | − | color: Theme.dim | |
| 175 | − | font.family: Theme.font | |
| 176 | − | font.pixelSize: Theme.fontSize | |
| 177 | − | } | |
| 178 | − | TextInput { | |
| 179 | − | id: pass | |
| 180 | − | anchors.fill: parent | |
| 181 | − | anchors.leftMargin: promptTxt.implicitWidth + 22 | |
| 182 | − | anchors.rightMargin: 10 | |
| 183 | − | verticalAlignment: TextInput.AlignVCenter | |
| 184 | − | echoMode: win.mode === "bt" || (win.flow && win.flow.responseVisible) ? TextInput.Normal : TextInput.Password | |
| 185 | − | passwordCharacter: "•" | |
| 186 | − | maximumLength: win.btMethod === "RequestPasskey" ? 6 : 256 | |
| 187 | − | validator: RegularExpressionValidator { | |
| 188 | − | regularExpression: win.btMethod === "RequestPasskey" ? /[0-9]*/ : /.*/ | |
| 178 | + | implicitHeight: 56 | |
| 179 | + | Rectangle { | |
| 180 | + | id: field | |
| 181 | + | width: parent.width | |
| 182 | + | height: parent.height | |
| 183 | + | radius: height / 2 | |
| 184 | + | color: Theme.s2 | |
| 185 | + | border.width: 1 | |
| 186 | + | border.color: win.bad ? Theme.err : pass.activeFocus ? Theme.fill : Theme.s3 | |
| 187 | + | SequentialAnimation on x { | |
| 188 | + | id: shake | |
| 189 | + | running: false | |
| 190 | + | NumberAnimation { to: -12; duration: 50 } | |
| 191 | + | NumberAnimation { to: 12; duration: 70 } | |
| 192 | + | NumberAnimation { to: -8; duration: 70 } | |
| 193 | + | NumberAnimation { to: 6; duration: 60 } | |
| 194 | + | NumberAnimation { to: 0; duration: 50 } | |
| 195 | + | } | |
| 196 | + | Lb { | |
| 197 | + | id: promptTxt | |
| 198 | + | x: 22 | |
| 199 | + | anchors.verticalCenter: parent.verticalCenter | |
| 200 | + | text: win.mode === "bt" ? (win.btMethod === "RequestPinCode" ? "pin" : "passkey") | |
| 201 | + | : (win.flow && win.flow.inputPrompt !== "" ? win.flow.inputPrompt.replace(/:\s*$/, "") : "password") | |
| 202 | + | } | |
| 203 | + | TextInput { | |
| 204 | + | id: pass | |
| 205 | + | anchors.left: promptTxt.right | |
| 206 | + | anchors.leftMargin: 16 | |
| 207 | + | anchors.right: parent.right | |
| 208 | + | anchors.rightMargin: 22 | |
| 209 | + | anchors.verticalCenter: parent.verticalCenter | |
| 210 | + | echoMode: win.mode === "bt" || (win.flow && win.flow.responseVisible) ? TextInput.Normal : TextInput.Password | |
| 211 | + | passwordCharacter: "•" | |
| 212 | + | maximumLength: win.btMethod === "RequestPasskey" ? 6 : 256 | |
| 213 | + | validator: RegularExpressionValidator { | |
| 214 | + | regularExpression: win.btMethod === "RequestPasskey" ? /[0-9]*/ : /.*/ | |
| 215 | + | } | |
| 216 | + | enabled: !win.waiting | |
| 217 | + | clip: true | |
| 218 | + | color: Theme.fg | |
| 219 | + | selectionColor: Theme.fill | |
| 220 | + | selectedTextColor: Theme.fillFg | |
| 221 | + | font.family: Theme.mono | |
| 222 | + | font.pixelSize: 16 | |
| 223 | + | font.letterSpacing: 3 | |
| 224 | + | cursorDelegate: Rectangle { | |
| 225 | + | width: 2 | |
| 226 | + | color: Theme.live | |
| 227 | + | visible: pass.activeFocus | |
| 228 | + | Timer { running: pass.activeFocus; interval: 500; repeat: true; onTriggered: parent.opacity = 1 - parent.opacity } | |
| 229 | + | } | |
| 230 | + | Keys.onReturnPressed: win.accept() | |
| 231 | + | Keys.onEnterPressed: win.accept() | |
| 232 | + | Keys.onEscapePressed: win.cancel() | |
| 189 | 233 | } | |
| 190 | − | enabled: !win.waiting | |
| 191 | − | clip: true | |
| 192 | − | color: Theme.fg | |
| 193 | − | selectionColor: Theme.accent | |
| 194 | − | selectedTextColor: Theme.bg | |
| 195 | − | font.family: Theme.font | |
| 196 | − | font.pixelSize: Theme.fontSize | |
| 197 | − | Keys.onReturnPressed: win.accept() | |
| 198 | − | Keys.onEnterPressed: win.accept() | |
| 199 | − | Keys.onEscapePressed: win.cancel() | |
| 200 | 234 | } | |
| 201 | 235 | } | |
| 202 | − | Text { | |
| 236 | + | Lb { | |
| 203 | 237 | Layout.fillWidth: true | |
| 238 | + | Layout.leftMargin: 6 | |
| 204 | 239 | visible: win.mode === "polkit" && text !== "" | |
| 205 | − | text: win.waiting ? "checking…" : (win.error !== "" ? win.error : (win.flow ? win.flow.supplementaryMessage : "")) | |
| 206 | − | color: !win.waiting && (win.error !== "" || (win.flow && win.flow.supplementaryIsError)) ? Theme.err : Theme.dim | |
| 240 | + | px: 10 | |
| 207 | 241 | wrapMode: Text.Wrap | |
| 208 | − | font.family: Theme.font | |
| 209 | − | font.pixelSize: Theme.fontSize - 1 | |
| 242 | + | text: win.waiting ? "checking…" : (win.error !== "" ? win.error : (win.flow ? win.flow.supplementaryMessage : "")) | |
| 243 | + | color: !win.waiting && win.bad ? Theme.err : Theme.grey | |
| 210 | 244 | } | |
| 211 | 245 | ||
| 212 | 246 | RowLayout { | |
| 213 | 247 | Layout.fillWidth: true | |
| 214 | − | spacing: 6 | |
| 248 | + | Layout.topMargin: 4 | |
| 249 | + | spacing: 8 | |
| 215 | 250 | Item { Layout.fillWidth: true } | |
| 216 | 251 | IconBtn { | |
| 217 | 252 | visible: !win.btDisplay | |
| 218 | 253 | label: win.mode === "bt" ? "reject" : "cancel" | |
| 219 | − | implicitHeight: 26 | |
| 254 | + | implicitHeight: 44 | |
| 220 | 255 | onClicked: win.cancel() | |
| 221 | 256 | } | |
| 222 | 257 | IconBtn { | |
| 223 | 258 | label: win.btDisplay ? "close" : (win.mode === "bt" ? (win.btInput ? "pair" : "allow") : "authenticate") | |
| 224 | − | implicitHeight: 26 | |
| 259 | + | implicitHeight: 44 | |
| 225 | 260 | active: true | |
| 226 | 261 | onClicked: win.accept() | |
| 227 | 262 | } | |
| @@ -2,31 +2,28 @@ import QtQuick | |||
| 2 | 2 | import QtQuick.Layouts | |
| 3 | 3 | import Quickshell | |
| 4 | 4 | import Quickshell.Wayland | |
| 5 | − | import Quickshell.Hyprland | |
| 6 | 5 | ||
| 7 | 6 | // Glyph picker (SUPER+grave, `chars` IPC): emoji, nerd font glyphs, unicode symbols, kaomoji, fancy text. | |
| 8 | 7 | // Type to fuzzy search; arrows / Ctrl+HJKL move, Tab section, Ctrl+Left/Right group, Enter copies, Shift+Enter types it | |
| 9 | 8 | // into the focused window, Ctrl+Enter collects into a buffer, Alt+Enter copies the codepoint. | |
| 9 | + | // Looks like glyph's charms (github.com/Nebelung-cloud/glyph-rice, GPL-3.0, charms/Charms.qml): a card over the dimmed | |
| 10 | + | // screen, search pill, section chips, tiles (the selected one white... here primary), the selection large at the bottom. | |
| 10 | 11 | PanelWindow { | |
| 11 | 12 | id: win | |
| 12 | 13 | property bool opened: false | |
| 13 | 14 | signal closeRequested() | |
| 14 | 15 | ||
| 15 | − | visible: opened | |
| 16 | − | implicitWidth: 780 | |
| 17 | − | implicitHeight: 590 | |
| 18 | − | exclusiveZone: 0 | |
| 16 | + | property real t: opened ? 1 : 0 | |
| 17 | + | Behavior on t { NumberAnimation { duration: Theme.t; easing.type: Easing.BezierSpline; easing.bezierCurve: Theme.glide } } | |
| 18 | + | ||
| 19 | + | visible: t > 0 | |
| 20 | + | anchors { top: true; bottom: true; left: true; right: true } | |
| 21 | + | exclusionMode: ExclusionMode.Ignore | |
| 19 | 22 | color: "transparent" | |
| 20 | 23 | WlrLayershell.layer: WlrLayer.Overlay | |
| 21 | 24 | WlrLayershell.namespace: "aurora-chars" | |
| 22 | 25 | WlrLayershell.keyboardFocus: opened ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None | |
| 23 | 26 | ||
| 24 | − | HyprlandFocusGrab { | |
| 25 | − | windows: [win] | |
| 26 | − | active: win.opened | |
| 27 | − | onCleared: win.closeRequested() | |
| 28 | − | } | |
| 29 | − | ||
| 30 | 27 | readonly property var sections: [ | |
| 31 | 28 | { id: "all", label: "all", icon: Theme.iGrid, sets: ["emoji", "nerd", "sym"] }, | |
| 32 | 29 | { id: "recent", label: "recent", icon: Theme.iHistory, sets: ["emoji", "nerd", "sym"] }, | |
| @@ -147,56 +144,76 @@ PanelWindow { | |||
| 147 | 144 | ||
| 148 | 145 | Rectangle { | |
| 149 | 146 | anchors.fill: parent | |
| 150 | − | color: Theme.bgA | |
| 151 | − | border.width: 2 | |
| 152 | − | border.color: Theme.accent | |
| 147 | + | color: Theme.scrim | |
| 148 | + | opacity: win.t | |
| 149 | + | MouseArea { anchors.fill: parent; onClicked: win.closeRequested() } | |
| 150 | + | } | |
| 151 | + | ||
| 152 | + | Rectangle { | |
| 153 | + | id: card | |
| 154 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 155 | + | y: Math.round((parent.height - height) / 2) - 30 + 24 * (1 - win.t) | |
| 156 | + | width: Math.min(parent.width - 80, 960) | |
| 157 | + | height: Math.min(parent.height - 160, 740) | |
| 158 | + | opacity: win.t | |
| 159 | + | radius: 44 | |
| 160 | + | color: Qt.rgba(Theme.s1.r, Theme.s1.g, Theme.s1.b, Theme.panelAlpha) | |
| 161 | + | border.width: 1 | |
| 162 | + | border.color: Theme.s2 | |
| 163 | + | MouseArea { anchors.fill: parent } | |
| 153 | 164 | ||
| 154 | 165 | ColumnLayout { | |
| 155 | 166 | anchors.fill: parent | |
| 156 | − | anchors.margins: 12 | |
| 157 | − | spacing: 8 | |
| 167 | + | anchors.margins: 30 | |
| 168 | + | spacing: 14 | |
| 158 | 169 | ||
| 159 | 170 | // ---- search + tone + close ---- | |
| 160 | 171 | RowLayout { | |
| 161 | 172 | Layout.fillWidth: true | |
| 162 | − | spacing: 6 | |
| 173 | + | spacing: 10 | |
| 163 | 174 | Rectangle { | |
| 164 | 175 | Layout.fillWidth: true | |
| 165 | − | implicitHeight: 30 | |
| 166 | − | color: Theme.surface | |
| 167 | − | border.width: 1 | |
| 168 | − | border.color: filter.activeFocus ? Theme.accent : Theme.line | |
| 169 | − | Text { | |
| 170 | − | anchors.left: parent.left | |
| 171 | − | anchors.leftMargin: 10 | |
| 176 | + | implicitHeight: 60 | |
| 177 | + | radius: 30 | |
| 178 | + | color: Theme.s2 | |
| 179 | + | Icon { | |
| 180 | + | id: lens | |
| 181 | + | x: 22 | |
| 172 | 182 | anchors.verticalCenter: parent.verticalCenter | |
| 173 | 183 | text: Theme.iSearch | |
| 174 | − | color: Theme.dim | |
| 175 | − | font.family: Theme.font | |
| 176 | − | font.pixelSize: Theme.fontSize | |
| 184 | + | color: Theme.grey | |
| 185 | + | font.pixelSize: 20 | |
| 177 | 186 | } | |
| 178 | 187 | Text { | |
| 179 | − | anchors.left: parent.left | |
| 180 | − | anchors.leftMargin: 32 | |
| 188 | + | anchors.left: filter.left | |
| 189 | + | anchors.right: count.left | |
| 181 | 190 | anchors.verticalCenter: parent.verticalCenter | |
| 182 | 191 | visible: filter.text === "" | |
| 183 | − | text: win.section.id === "fancy" ? "type some text" : "search " + win.section.label + " (name, keyword, u+f0001, or paste a glyph)" | |
| 184 | − | color: Theme.dim | |
| 185 | − | font.family: Theme.font | |
| 186 | − | font.pixelSize: Theme.fontSize | |
| 192 | + | elide: Text.ElideRight | |
| 193 | + | text: win.section.id === "fancy" ? "Type some text" : "Search " + win.section.label + " (name, keyword, u+f0001, or paste a glyph)" | |
| 194 | + | color: Theme.mute | |
| 195 | + | font.family: Theme.ui | |
| 196 | + | font.pixelSize: 18 | |
| 187 | 197 | } | |
| 188 | 198 | TextInput { | |
| 189 | 199 | id: filter | |
| 190 | − | anchors.fill: parent | |
| 191 | − | anchors.leftMargin: 32 | |
| 192 | − | anchors.rightMargin: 10 | |
| 193 | − | verticalAlignment: TextInput.AlignVCenter | |
| 200 | + | anchors.left: lens.right | |
| 201 | + | anchors.leftMargin: 14 | |
| 202 | + | anchors.right: count.left | |
| 203 | + | anchors.rightMargin: 12 | |
| 204 | + | anchors.verticalCenter: parent.verticalCenter | |
| 194 | 205 | color: Theme.fg | |
| 195 | − | selectionColor: Theme.accent | |
| 196 | − | selectedTextColor: Theme.bg | |
| 197 | − | font.family: Theme.font | |
| 198 | − | font.pixelSize: Theme.fontSize | |
| 206 | + | selectionColor: Theme.fill | |
| 207 | + | selectedTextColor: Theme.fillFg | |
| 208 | + | font.family: Theme.ui | |
| 209 | + | font.pixelSize: 18 | |
| 199 | 210 | clip: true | |
| 211 | + | cursorDelegate: Rectangle { | |
| 212 | + | width: 2 | |
| 213 | + | color: Theme.live | |
| 214 | + | visible: filter.activeFocus | |
| 215 | + | Timer { running: filter.activeFocus; interval: 500; repeat: true; onTriggered: parent.opacity = 1 - parent.opacity } | |
| 216 | + | } | |
| 200 | 217 | onTextChanged: { | |
| 201 | 218 | // recent is the landing page: typing there searches everything (used ones rank first anyway) | |
| 202 | 219 | if (text !== "" && win.section.id === "recent") win.sec = 0 | |
| @@ -235,32 +252,32 @@ PanelWindow { | |||
| 235 | 252 | event.accepted = true | |
| 236 | 253 | } | |
| 237 | 254 | } | |
| 238 | − | } | |
| 239 | − | Text { | |
| 240 | − | text: win.results.length | |
| 241 | − | color: Theme.dim | |
| 242 | − | font.family: Theme.font | |
| 243 | − | font.pixelSize: Theme.fontSize - 1 | |
| 255 | + | Lb { | |
| 256 | + | id: count | |
| 257 | + | anchors.right: parent.right | |
| 258 | + | anchors.rightMargin: 22 | |
| 259 | + | anchors.verticalCenter: parent.verticalCenter | |
| 260 | + | text: win.results.length | |
| 261 | + | } | |
| 244 | 262 | } | |
| 245 | 263 | // skin tone: the raised hand in each tone, click to choose (persisted) | |
| 246 | 264 | Row { | |
| 247 | 265 | visible: Chars.loaded && (win.section.id === "emoji" || win.section.id === "all" || win.section.id === "recent") | |
| 248 | − | spacing: 0 | |
| 266 | + | spacing: 2 | |
| 249 | 267 | Repeater { | |
| 250 | 268 | model: 6 | |
| 251 | 269 | delegate: Rectangle { | |
| 252 | 270 | required property int index | |
| 253 | 271 | readonly property var hand: Chars.byChar["✋"] | |
| 254 | − | width: 26 | |
| 255 | − | height: 30 | |
| 256 | − | color: Settings.charTone === index ? Theme.surfaceHi : (tm.containsMouse ? Theme.surface : "transparent") | |
| 257 | − | border.width: Settings.charTone === index ? 1 : 0 | |
| 258 | − | border.color: Theme.accent | |
| 272 | + | width: 40 | |
| 273 | + | height: 40 | |
| 274 | + | radius: 20 | |
| 275 | + | color: Settings.charTone === index ? Theme.fill : (tm.containsMouse ? Theme.s3 : "transparent") | |
| 259 | 276 | Text { | |
| 260 | 277 | anchors.centerIn: parent | |
| 261 | 278 | text: parent.hand ? Chars.glyph(parent.hand, parent.index) : "" | |
| 262 | 279 | font.family: "Noto Color Emoji" | |
| 263 | − | font.pixelSize: 15 | |
| 280 | + | font.pixelSize: 17 | |
| 264 | 281 | } | |
| 265 | 282 | MouseArea { | |
| 266 | 283 | id: tm | |
| @@ -272,56 +289,21 @@ PanelWindow { | |||
| 272 | 289 | } | |
| 273 | 290 | } | |
| 274 | 291 | } | |
| 275 | − | IconBtn { icon: Theme.iClose; onClicked: win.closeRequested() } | |
| 292 | + | IconBtn { implicitHeight: 48; icon: Theme.iClose; onClicked: win.closeRequested() } | |
| 276 | 293 | } | |
| 277 | 294 | ||
| 278 | 295 | // ---- sections ---- | |
| 279 | − | RowLayout { | |
| 296 | + | Row { | |
| 280 | 297 | Layout.fillWidth: true | |
| 281 | 298 | spacing: 2 | |
| 282 | 299 | Repeater { | |
| 283 | 300 | model: win.sections | |
| 284 | − | delegate: Rectangle { | |
| 285 | − | id: tab | |
| 301 | + | delegate: Chip { | |
| 286 | 302 | required property var modelData | |
| 287 | 303 | required property int index | |
| 288 | − | readonly property bool active: win.sec === index | |
| 289 | − | Layout.fillWidth: true | |
| 290 | − | implicitHeight: 28 | |
| 291 | − | color: active ? Theme.surfaceHi : (sm.containsMouse ? Theme.surface : "transparent") | |
| 292 | − | Row { | |
| 293 | − | anchors.centerIn: parent | |
| 294 | − | spacing: 6 | |
| 295 | − | Text { | |
| 296 | − | anchors.verticalCenter: parent.verticalCenter | |
| 297 | − | text: tab.modelData.icon | |
| 298 | − | color: tab.active ? Theme.accent : Theme.dim | |
| 299 | − | font.family: Theme.font | |
| 300 | − | font.pixelSize: Theme.fontSize + 2 | |
| 301 | − | } | |
| 302 | − | Text { | |
| 303 | − | anchors.verticalCenter: parent.verticalCenter | |
| 304 | − | text: tab.modelData.label.toUpperCase() | |
| 305 | − | color: tab.active ? Theme.fg : Theme.dim | |
| 306 | − | font.family: Theme.font | |
| 307 | − | font.pixelSize: Theme.fontSize - 2 | |
| 308 | − | font.letterSpacing: 1.2 | |
| 309 | − | font.bold: tab.active | |
| 310 | − | } | |
| 311 | − | } | |
| 312 | − | Rectangle { // waybar-style underline | |
| 313 | − | anchors.bottom: parent.bottom | |
| 314 | − | width: parent.width | |
| 315 | − | height: tab.active ? 2 : 1 | |
| 316 | − | color: tab.active ? Theme.accent : Theme.line | |
| 317 | − | } | |
| 318 | − | MouseArea { | |
| 319 | − | id: sm | |
| 320 | − | anchors.fill: parent | |
| 321 | − | hoverEnabled: true | |
| 322 | − | cursorShape: Qt.PointingHandCursor | |
| 323 | − | onClicked: { win.sec = tab.index; filter.forceActiveFocus() } | |
| 324 | − | } | |
| 304 | + | text: modelData.label | |
| 305 | + | selected: win.sec === index | |
| 306 | + | onClicked: { win.sec = index; filter.forceActiveFocus() } | |
| 325 | 307 | } | |
| 326 | 308 | } | |
| 327 | 309 | } | |
| @@ -330,7 +312,7 @@ PanelWindow { | |||
| 330 | 312 | ListView { | |
| 331 | 313 | id: chips | |
| 332 | 314 | Layout.fillWidth: true | |
| 333 | − | implicitHeight: 22 | |
| 315 | + | implicitHeight: 32 | |
| 334 | 316 | visible: win.groups.length > 0 | |
| 335 | 317 | orientation: ListView.Horizontal | |
| 336 | 318 | spacing: 4 | |
| @@ -339,31 +321,15 @@ PanelWindow { | |||
| 339 | 321 | model: win.groups.length ? ["all"].concat(win.groups) : [] | |
| 340 | 322 | currentIndex: win.grp + 1 | |
| 341 | 323 | onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain) | |
| 342 | − | delegate: Rectangle { | |
| 343 | − | id: chip | |
| 324 | + | delegate: Chip { | |
| 344 | 325 | required property var modelData | |
| 345 | 326 | required property int index | |
| 346 | − | readonly property bool active: win.grp === index - 1 | |
| 347 | − | width: ct.implicitWidth + 16 | |
| 348 | − | height: 22 | |
| 349 | − | color: active ? Theme.accent : (cm.containsMouse ? Theme.surfaceHi : Theme.surface) | |
| 350 | − | border.width: 1 | |
| 351 | − | border.color: active ? Theme.accent : Theme.line | |
| 352 | − | Text { | |
| 353 | − | id: ct | |
| 354 | − | anchors.centerIn: parent | |
| 355 | − | text: chip.modelData.toLowerCase() | |
| 356 | − | color: chip.active ? Theme.bg : Theme.fg | |
| 357 | − | font.family: Theme.font | |
| 358 | − | font.pixelSize: Theme.fontSize - 2 | |
| 359 | − | } | |
| 360 | − | MouseArea { | |
| 361 | − | id: cm | |
| 362 | − | anchors.fill: parent | |
| 363 | − | hoverEnabled: true | |
| 364 | − | cursorShape: Qt.PointingHandCursor | |
| 365 | − | onClicked: { win.grp = chip.index - 1; filter.forceActiveFocus() } | |
| 366 | − | } | |
| 327 | + | height: 32 | |
| 328 | + | px: 10 | |
| 329 | + | filled: true | |
| 330 | + | text: modelData | |
| 331 | + | selected: win.grp === index - 1 | |
| 332 | + | onClicked: { win.grp = index - 1; filter.forceActiveFocus() } | |
| 367 | 333 | } | |
| 368 | 334 | } | |
| 369 | 335 | ||
| @@ -376,9 +342,9 @@ PanelWindow { | |||
| 376 | 342 | id: grid | |
| 377 | 343 | anchors.fill: parent | |
| 378 | 344 | visible: !win.listMode | |
| 379 | − | readonly property int cols: Math.max(1, Math.floor(width / 50)) | |
| 345 | + | readonly property int cols: Math.max(1, Math.floor(width / 58)) | |
| 380 | 346 | cellWidth: Math.floor(width / cols) | |
| 381 | − | cellHeight: 50 | |
| 347 | + | cellHeight: 58 | |
| 382 | 348 | clip: true | |
| 383 | 349 | model: win.listMode ? 0 : win.results.length | |
| 384 | 350 | boundsBehavior: Flickable.StopAtBounds | |
| @@ -390,15 +356,14 @@ PanelWindow { | |||
| 390 | 356 | required property int index | |
| 391 | 357 | readonly property var it: win.results[index] | |
| 392 | 358 | readonly property bool selected: grid.currentIndex === index | |
| 393 | − | width: grid.cellWidth - 2 | |
| 394 | − | height: grid.cellHeight - 2 | |
| 395 | − | color: selected ? Theme.surfaceHi : (gm.containsMouse ? Theme.surface : "transparent") | |
| 396 | − | border.width: selected ? 1 : 0 | |
| 397 | − | border.color: Theme.accent | |
| 359 | + | width: grid.cellWidth - 4 | |
| 360 | + | height: grid.cellHeight - 4 | |
| 361 | + | radius: 18 | |
| 362 | + | color: selected ? Theme.fill : (gm.containsMouse ? Theme.s2 : "transparent") | |
| 398 | 363 | Text { | |
| 399 | 364 | anchors.centerIn: parent | |
| 400 | 365 | text: cell.it ? Chars.glyph(cell.it, win.tone) : "" | |
| 401 | − | color: Theme.fg | |
| 366 | + | color: cell.selected ? Theme.fillFg : Theme.fg | |
| 402 | 367 | font.family: win.fontOf(cell.it) | |
| 403 | 368 | font.pixelSize: 26 | |
| 404 | 369 | } | |
| @@ -432,38 +397,30 @@ PanelWindow { | |||
| 432 | 397 | readonly property var it: win.results[index] | |
| 433 | 398 | readonly property bool selected: list.currentIndex === index | |
| 434 | 399 | width: list.width | |
| 435 | − | height: 34 | |
| 436 | − | color: selected ? Theme.surfaceHi : (lm.containsMouse ? Theme.surface : "transparent") | |
| 437 | − | border.width: selected ? 1 : 0 | |
| 438 | − | border.color: Theme.accent | |
| 439 | − | Rectangle { | |
| 440 | − | visible: row.selected | |
| 441 | − | width: 2 | |
| 442 | − | height: parent.height | |
| 443 | − | color: Theme.accent | |
| 444 | − | } | |
| 400 | + | height: 52 | |
| 401 | + | radius: 26 | |
| 402 | + | color: selected ? Theme.fill : (lm.containsMouse ? Theme.s2 : "transparent") | |
| 445 | 403 | Text { | |
| 446 | 404 | anchors.left: parent.left | |
| 447 | − | anchors.leftMargin: 12 | |
| 405 | + | anchors.leftMargin: 22 | |
| 448 | 406 | anchors.right: tag.left | |
| 449 | 407 | anchors.rightMargin: 12 | |
| 450 | 408 | anchors.verticalCenter: parent.verticalCenter | |
| 451 | 409 | text: row.it ? row.it.c : "" | |
| 452 | 410 | elide: Text.ElideRight | |
| 453 | 411 | maximumLineCount: 1 | |
| 454 | − | color: Theme.fg | |
| 455 | − | font.family: Theme.font | |
| 456 | − | font.pixelSize: Theme.fontSize + 4 | |
| 412 | + | color: row.selected ? Theme.fillFg : Theme.fg | |
| 413 | + | font.family: Theme.ui | |
| 414 | + | font.pixelSize: 18 | |
| 457 | 415 | } | |
| 458 | − | Text { | |
| 416 | + | Lb { | |
| 459 | 417 | id: tag | |
| 460 | 418 | anchors.right: parent.right | |
| 461 | − | anchors.rightMargin: 12 | |
| 419 | + | anchors.rightMargin: 22 | |
| 462 | 420 | anchors.verticalCenter: parent.verticalCenter | |
| 421 | + | px: 10 | |
| 463 | 422 | text: row.it ? (win.section.id === "fancy" ? row.it.n : row.it.k) : "" | |
| 464 | − | color: row.selected ? Theme.accent : Theme.dim | |
| 465 | − | font.family: Theme.font | |
| 466 | − | font.pixelSize: Theme.fontSize - 1 | |
| 423 | + | color: row.selected ? Theme.fillFg : Theme.grey | |
| 467 | 424 | } | |
| 468 | 425 | MouseArea { | |
| 469 | 426 | id: lm | |
| @@ -480,98 +437,90 @@ PanelWindow { | |||
| 480 | 437 | } | |
| 481 | 438 | } | |
| 482 | 439 | ||
| 483 | − | Text { | |
| 440 | + | Lb { | |
| 484 | 441 | anchors.centerIn: parent | |
| 485 | 442 | visible: win.results.length === 0 | |
| 486 | − | text: !Chars.loaded && win.section.id !== "fancy" ? "loading glyphs..." | |
| 443 | + | color: Theme.mute | |
| 444 | + | text: !Chars.loaded && win.section.id !== "fancy" ? "loading glyphs…" | |
| 487 | 445 | : (win.section.id === "recent" && filter.text === "" ? "nothing used yet" : "no matches") | |
| 488 | − | color: Theme.dim | |
| 489 | − | font.family: Theme.font | |
| 490 | − | font.pixelSize: Theme.fontSize | |
| 491 | 446 | } | |
| 492 | 447 | } | |
| 493 | 448 | ||
| 494 | − | // ---- preview of the selected / hovered glyph ---- | |
| 449 | + | // ---- the selected / hovered glyph, large ---- | |
| 495 | 450 | Rectangle { | |
| 496 | 451 | Layout.fillWidth: true | |
| 497 | − | implicitHeight: 62 | |
| 498 | − | color: Theme.surface | |
| 499 | − | border.width: 1 | |
| 500 | − | border.color: Theme.line | |
| 452 | + | implicitHeight: 92 | |
| 453 | + | radius: 30 | |
| 454 | + | color: Theme.s2 | |
| 501 | 455 | ||
| 502 | − | Rectangle { // big glyph | |
| 456 | + | Rectangle { | |
| 503 | 457 | id: big | |
| 504 | 458 | visible: !win.listMode | |
| 505 | − | anchors.left: parent.left | |
| 506 | − | anchors.top: parent.top | |
| 507 | − | anchors.bottom: parent.bottom | |
| 508 | − | width: visible ? height : 0 | |
| 509 | − | color: "transparent" | |
| 510 | − | border.width: 1 | |
| 511 | − | border.color: Theme.line | |
| 459 | + | x: 12 | |
| 460 | + | anchors.verticalCenter: parent.verticalCenter | |
| 461 | + | width: visible ? 68 : 0 | |
| 462 | + | height: 68 | |
| 463 | + | radius: 34 | |
| 464 | + | color: Theme.s3 | |
| 512 | 465 | Text { | |
| 513 | 466 | anchors.centerIn: parent | |
| 514 | 467 | text: win.current ? Chars.glyph(win.current, win.tone) : "" | |
| 515 | − | color: Theme.accent | |
| 468 | + | color: Theme.fill | |
| 516 | 469 | font.family: win.fontOf(win.current) | |
| 517 | − | font.pixelSize: 38 | |
| 470 | + | font.pixelSize: 36 | |
| 518 | 471 | } | |
| 519 | 472 | } | |
| 520 | 473 | Column { | |
| 521 | 474 | anchors.left: big.right | |
| 522 | − | anchors.leftMargin: 12 | |
| 475 | + | anchors.leftMargin: 16 | |
| 523 | 476 | anchors.right: buf.left | |
| 524 | 477 | anchors.rightMargin: 12 | |
| 525 | 478 | anchors.verticalCenter: parent.verticalCenter | |
| 526 | − | spacing: 3 | |
| 479 | + | spacing: 4 | |
| 527 | 480 | Text { | |
| 528 | 481 | width: parent.width | |
| 529 | 482 | text: win.current ? win.current.n : "" | |
| 530 | 483 | elide: Text.ElideRight | |
| 531 | 484 | color: Theme.fg | |
| 532 | − | font.family: Theme.font | |
| 533 | − | font.pixelSize: Theme.fontSize + 1 | |
| 534 | − | font.bold: true | |
| 485 | + | font.family: Theme.ui | |
| 486 | + | font.pixelSize: 16 | |
| 487 | + | font.weight: Font.DemiBold | |
| 535 | 488 | } | |
| 536 | − | Text { | |
| 489 | + | Lb { | |
| 537 | 490 | width: parent.width | |
| 538 | 491 | visible: win.current !== null && win.section.id !== "fancy" | |
| 492 | + | px: 10 | |
| 493 | + | color: Theme.fill | |
| 539 | 494 | text: { | |
| 540 | 495 | var c = win.current | |
| 541 | 496 | if (!c || c.s === undefined || !Chars.sets[c.s]) return "" | |
| 542 | 497 | var st = Chars.sets[c.s] | |
| 543 | − | return (st.id === "kao" ? "" : Chars.hex(Chars.glyph(c, win.tone)) + " ") | |
| 544 | − | + st.label.toLowerCase() + " > " + st.groups[c.g].toLowerCase() | |
| 498 | + | return (st.id === "kao" ? "" : Chars.hex(Chars.glyph(c, win.tone)) + " · ") | |
| 499 | + | + st.label + " · " + st.groups[c.g] | |
| 545 | 500 | } | |
| 546 | 501 | elide: Text.ElideRight | |
| 547 | − | color: Theme.accent | |
| 548 | − | font.family: Theme.font | |
| 549 | − | font.pixelSize: Theme.fontSize - 1 | |
| 550 | 502 | } | |
| 551 | − | Text { | |
| 503 | + | Lb { | |
| 552 | 504 | width: parent.width | |
| 553 | 505 | visible: text !== "" | |
| 506 | + | px: 10 | |
| 554 | 507 | text: win.current && win.current.k && win.section.id !== "kao" ? win.current.k : "" | |
| 555 | 508 | elide: Text.ElideRight | |
| 556 | − | color: Theme.dim | |
| 557 | − | font.family: Theme.font | |
| 558 | − | font.pixelSize: Theme.fontSize - 2 | |
| 559 | 509 | } | |
| 560 | 510 | } | |
| 561 | 511 | Row { // collected with ctrl+enter / right click | |
| 562 | 512 | id: buf | |
| 563 | 513 | anchors.right: parent.right | |
| 564 | − | anchors.rightMargin: 12 | |
| 514 | + | anchors.rightMargin: 22 | |
| 565 | 515 | anchors.verticalCenter: parent.verticalCenter | |
| 566 | − | spacing: 8 | |
| 516 | + | spacing: 10 | |
| 567 | 517 | visible: win.buffer !== "" | |
| 568 | 518 | width: visible ? implicitWidth : 0 | |
| 569 | − | Text { | |
| 519 | + | Icon { | |
| 570 | 520 | anchors.verticalCenter: parent.verticalCenter | |
| 571 | 521 | text: Theme.iBuffer | |
| 572 | − | color: Theme.accent | |
| 573 | − | font.family: Theme.font | |
| 574 | − | font.pixelSize: Theme.fontSize + 2 | |
| 522 | + | color: Theme.live | |
| 523 | + | font.pixelSize: 18 | |
| 575 | 524 | } | |
| 576 | 525 | Text { | |
| 577 | 526 | anchors.verticalCenter: parent.verticalCenter | |
| @@ -580,20 +529,22 @@ PanelWindow { | |||
| 580 | 529 | elide: Text.ElideLeft | |
| 581 | 530 | color: Theme.fg | |
| 582 | 531 | font.family: Theme.font | |
| 583 | − | font.pixelSize: Theme.fontSize + 4 | |
| 532 | + | font.pixelSize: 20 | |
| 584 | 533 | } | |
| 585 | 534 | } | |
| 586 | 535 | } | |
| 536 | + | } | |
| 537 | + | } | |
| 587 | 538 | ||
| 588 | − | Text { | |
| 589 | − | Layout.fillWidth: true | |
| 590 | − | text: "enter copy " + (Chars.hasWtype ? "shift+enter type " : "") + "ctrl+enter collect alt+enter code " | |
| 591 | − | + "ctrl+hjkl move tab section ctrl+arrows group esc close" | |
| 592 | − | elide: Text.ElideRight | |
| 593 | − | color: Theme.dim | |
| 594 | − | font.family: Theme.font | |
| 595 | − | font.pixelSize: Theme.fontSize - 2 | |
| 596 | − | } | |
| 539 | + | Row { | |
| 540 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 541 | + | anchors.top: card.bottom | |
| 542 | + | anchors.topMargin: 26 | |
| 543 | + | opacity: win.t | |
| 544 | + | spacing: 30 | |
| 545 | + | Repeater { | |
| 546 | + | model: ["↵ copy"].concat(Chars.hasWtype ? ["shift ↵ type"] : []).concat(["ctrl ↵ collect", "alt ↵ code", "ctrl hjkl move", "tab section", "ctrl ←→ group", "esc close"]) | |
| 547 | + | Lb { required property string modelData; text: modelData } | |
| 597 | 548 | } | |
| 598 | 549 | } | |
| 599 | 550 | } | |
| @@ -1,33 +1,34 @@ | |||
| 1 | 1 | import QtQuick | |
| 2 | 2 | import QtQuick.Layouts | |
| 3 | + | import QtQuick.Effects | |
| 3 | 4 | import Quickshell | |
| 4 | 5 | import Quickshell.Wayland | |
| 5 | − | import Quickshell.Hyprland | |
| 6 | 6 | ||
| 7 | − | // Clipboard history: type to filter, Up/Down, Enter copies + closes, Delete removes, Esc closes. | |
| 7 | + | // Clipboard history (cliphist): a card over the dimmed screen. Search pill with the live caret, filter chips | |
| 8 | + | // (Tab cycles: all / text / links / images), the history as rows (type in a circle: text, link, code, the colour | |
| 9 | + | // itself, the image as dots; a Doto number) and the selected entry in full on the right. | |
| 10 | + | // Enter copies + closes, Delete removes, Esc or a click outside closes. | |
| 11 | + | // After glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), clip/Clip.qml. | |
| 8 | 12 | PanelWindow { | |
| 9 | 13 | id: win | |
| 10 | 14 | property bool opened: false | |
| 11 | 15 | signal closeRequested() | |
| 12 | 16 | ||
| 13 | − | visible: opened | |
| 14 | − | implicitWidth: 620 | |
| 15 | − | implicitHeight: 560 | |
| 16 | − | exclusiveZone: 0 | |
| 17 | + | property real t: opened ? 1 : 0 | |
| 18 | + | Behavior on t { NumberAnimation { duration: Theme.t; easing.type: Easing.BezierSpline; easing.bezierCurve: Theme.glide } } | |
| 19 | + | ||
| 20 | + | visible: t > 0 | |
| 21 | + | anchors { top: true; bottom: true; left: true; right: true } | |
| 22 | + | exclusionMode: ExclusionMode.Ignore | |
| 17 | 23 | color: "transparent" | |
| 18 | 24 | WlrLayershell.layer: WlrLayer.Overlay | |
| 19 | 25 | WlrLayershell.namespace: "aurora-clipboard" | |
| 20 | 26 | WlrLayershell.keyboardFocus: opened ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None | |
| 21 | 27 | ||
| 22 | − | HyprlandFocusGrab { | |
| 23 | − | windows: [win] | |
| 24 | − | active: win.opened | |
| 25 | − | onCleared: win.closeRequested() | |
| 26 | − | } | |
| 27 | − | ||
| 28 | 28 | onOpenedChanged: { | |
| 29 | 29 | if (!opened) return | |
| 30 | 30 | filter.text = "" | |
| 31 | + | kind = 0 | |
| 31 | 32 | armed = false | |
| 32 | 33 | Clip.refresh() | |
| 33 | 34 | list.currentIndex = 0 | |
| @@ -37,186 +38,323 @@ PanelWindow { | |||
| 37 | 38 | property bool armed: false | |
| 38 | 39 | Timer { id: disarm; interval: 3000; onTriggered: win.armed = false } | |
| 39 | 40 | ||
| 41 | + | readonly property var kinds: ["all", "text", "links", "images"] | |
| 42 | + | property int kind: 0 | |
| 43 | + | function typeOf(e) { | |
| 44 | + | if (e.image) return "image" | |
| 45 | + | var t = e.text.trim() | |
| 46 | + | if (/^(https?|ftp|file):\/\/\S+$/i.test(t) || /^www\.\S+$/i.test(t)) return "link" | |
| 47 | + | if (/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(t) || /^rgba?\(/i.test(t)) return "colour" | |
| 48 | + | if (/[{};]|=>|^\s*(sudo|git|cd|ls|cat|echo|hyprctl|qs|pacman|paru|systemctl|python3?)\s/.test(t)) return "code" | |
| 49 | + | return "text" | |
| 50 | + | } | |
| 40 | 51 | readonly property var shown: { | |
| 41 | − | var q = filter.text.toLowerCase(), out = [], l = Clip.entries | |
| 42 | − | for (var i = 0; i < l.length; i++) | |
| 52 | + | var q = filter.text.toLowerCase(), out = [], l = Clip.entries, k = kinds[kind] | |
| 53 | + | for (var i = 0; i < l.length; i++) { | |
| 54 | + | var ty = typeOf(l[i]) | |
| 55 | + | if (k === "text" && (ty === "image" || ty === "link")) continue | |
| 56 | + | if (k === "links" && ty !== "link") continue | |
| 57 | + | if (k === "images" && ty !== "image") continue | |
| 43 | 58 | if (q === "" || l[i].text.toLowerCase().indexOf(q) >= 0) out.push(l[i]) | |
| 59 | + | } | |
| 44 | 60 | return out | |
| 45 | 61 | } | |
| 62 | + | readonly property var cur: list.currentIndex >= 0 && list.currentIndex < shown.length ? shown[list.currentIndex] : null | |
| 46 | 63 | function pick(e) { | |
| 47 | 64 | if (!e) return | |
| 48 | 65 | Clip.copy(e.id) | |
| 49 | 66 | closeRequested() | |
| 50 | 67 | } | |
| 68 | + | function icon(ty) { return ty === "link" ? Theme.iLink : ty === "code" ? Theme.iCode : ty === "image" ? Theme.iImage : Theme.iText } | |
| 51 | 69 | ||
| 52 | 70 | Rectangle { | |
| 53 | 71 | anchors.fill: parent | |
| 54 | − | color: Theme.bgA | |
| 55 | − | border.width: 2 | |
| 56 | − | border.color: Theme.accent | |
| 72 | + | color: Theme.scrim | |
| 73 | + | opacity: win.t | |
| 74 | + | MouseArea { anchors.fill: parent; onClicked: win.closeRequested() } | |
| 75 | + | } | |
| 57 | 76 | ||
| 58 | − | ColumnLayout { | |
| 59 | − | anchors.fill: parent | |
| 60 | − | anchors.margins: 12 | |
| 61 | − | spacing: 10 | |
| 77 | + | Rectangle { | |
| 78 | + | id: card | |
| 79 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 80 | + | y: Math.round((parent.height - height) / 2) - 30 + 24 * (1 - win.t) | |
| 81 | + | width: Math.min(parent.width - 80, 1160) | |
| 82 | + | height: Math.min(parent.height - 160, 780) | |
| 83 | + | opacity: win.t | |
| 84 | + | radius: 44 | |
| 85 | + | color: Qt.rgba(Theme.s1.r, Theme.s1.g, Theme.s1.b, Theme.panelAlpha) | |
| 86 | + | border.width: 1 | |
| 87 | + | border.color: Theme.s2 | |
| 88 | + | MouseArea { anchors.fill: parent } | |
| 62 | 89 | ||
| 63 | − | RowLayout { | |
| 64 | − | Layout.fillWidth: true | |
| 65 | − | spacing: 6 | |
| 66 | − | Rectangle { | |
| 67 | − | Layout.fillWidth: true | |
| 68 | − | implicitHeight: 30 | |
| 69 | − | color: Theme.surface | |
| 70 | − | border.width: 1 | |
| 71 | − | border.color: filter.activeFocus ? Theme.accent : Theme.line | |
| 72 | − | Text { | |
| 73 | − | anchors.left: parent.left | |
| 74 | − | anchors.leftMargin: 10 | |
| 75 | − | anchors.verticalCenter: parent.verticalCenter | |
| 76 | − | text: Theme.iClipboard | |
| 77 | − | color: Theme.dim | |
| 78 | − | font.family: Theme.font | |
| 79 | − | font.pixelSize: Theme.fontSize | |
| 80 | − | } | |
| 81 | − | TextInput { | |
| 82 | − | id: filter | |
| 83 | − | anchors.fill: parent | |
| 84 | − | anchors.leftMargin: 32 | |
| 85 | − | anchors.rightMargin: 10 | |
| 86 | − | verticalAlignment: TextInput.AlignVCenter | |
| 87 | − | color: Theme.fg | |
| 88 | − | selectionColor: Theme.accent | |
| 89 | − | selectedTextColor: Theme.bg | |
| 90 | − | font.family: Theme.font | |
| 91 | − | font.pixelSize: Theme.fontSize | |
| 92 | − | clip: true | |
| 93 | − | onTextChanged: list.currentIndex = 0 | |
| 94 | − | Keys.onEscapePressed: win.closeRequested() | |
| 95 | − | Keys.onReturnPressed: win.pick(win.shown[list.currentIndex]) | |
| 96 | − | Keys.onEnterPressed: win.pick(win.shown[list.currentIndex]) | |
| 97 | − | Keys.onUpPressed: list.decrementCurrentIndex() | |
| 98 | − | Keys.onDownPressed: list.incrementCurrentIndex() | |
| 99 | − | Keys.onPressed: event => { | |
| 100 | − | if (event.key === Qt.Key_Delete && list.currentIndex >= 0 && win.shown.length) { | |
| 101 | − | Clip.remove(win.shown[list.currentIndex].id) | |
| 102 | − | event.accepted = true | |
| 103 | − | } else if (event.key === Qt.Key_PageDown) { | |
| 104 | − | list.currentIndex = Math.min(win.shown.length - 1, list.currentIndex + 10); event.accepted = true | |
| 105 | − | } else if (event.key === Qt.Key_PageUp) { | |
| 106 | − | list.currentIndex = Math.max(0, list.currentIndex - 10); event.accepted = true | |
| 107 | − | } | |
| 108 | − | } | |
| 109 | − | } | |
| 90 | + | // ---- search ---- | |
| 91 | + | Rectangle { | |
| 92 | + | id: search | |
| 93 | + | x: 34 | |
| 94 | + | y: 30 | |
| 95 | + | width: parent.width - 68 | |
| 96 | + | height: 66 | |
| 97 | + | radius: 33 | |
| 98 | + | color: Theme.s2 | |
| 99 | + | Icon { id: lens; x: 24; anchors.verticalCenter: parent.verticalCenter; text: Theme.iSearch; color: Theme.grey; font.pixelSize: 22 } | |
| 100 | + | TextInput { | |
| 101 | + | id: filter | |
| 102 | + | anchors.left: lens.right | |
| 103 | + | anchors.leftMargin: 16 | |
| 104 | + | anchors.right: chips.left | |
| 105 | + | anchors.rightMargin: 12 | |
| 106 | + | anchors.verticalCenter: parent.verticalCenter | |
| 107 | + | font.family: Theme.ui | |
| 108 | + | font.pixelSize: 22 | |
| 109 | + | color: Theme.fg | |
| 110 | + | selectionColor: Theme.fill | |
| 111 | + | selectedTextColor: Theme.fillFg | |
| 112 | + | clip: true | |
| 113 | + | cursorDelegate: Rectangle { | |
| 114 | + | width: 2 | |
| 115 | + | color: Theme.live | |
| 116 | + | visible: filter.activeFocus | |
| 117 | + | Timer { running: filter.activeFocus; interval: 500; repeat: true; onTriggered: parent.opacity = 1 - parent.opacity } | |
| 110 | 118 | } | |
| 111 | 119 | Text { | |
| 112 | − | text: win.shown.length | |
| 113 | − | color: Theme.dim | |
| 114 | − | font.family: Theme.font | |
| 115 | − | font.pixelSize: Theme.fontSize - 1 | |
| 120 | + | visible: !filter.text | |
| 121 | + | text: "Search clipboard" | |
| 122 | + | font: filter.font | |
| 123 | + | color: Theme.mute | |
| 116 | 124 | } | |
| 117 | − | IconBtn { | |
| 118 | − | icon: Theme.iTrash | |
| 119 | − | label: win.armed ? "sure?" : "wipe" | |
| 120 | − | active: win.armed | |
| 121 | − | onClicked: { | |
| 122 | − | if (!win.armed) { win.armed = true; disarm.restart(); return } | |
| 123 | − | win.armed = false | |
| 124 | − | Clip.wipe() | |
| 125 | + | onTextChanged: list.currentIndex = 0 | |
| 126 | + | Keys.onEscapePressed: win.closeRequested() | |
| 127 | + | Keys.onReturnPressed: win.pick(win.cur) | |
| 128 | + | Keys.onEnterPressed: win.pick(win.cur) | |
| 129 | + | Keys.onUpPressed: list.decrementCurrentIndex() | |
| 130 | + | Keys.onDownPressed: list.incrementCurrentIndex() | |
| 131 | + | Keys.onTabPressed: { win.kind = (win.kind + 1) % win.kinds.length; list.currentIndex = 0 } | |
| 132 | + | Keys.onBacktabPressed: { win.kind = (win.kind + win.kinds.length - 1) % win.kinds.length; list.currentIndex = 0 } | |
| 133 | + | Keys.onPressed: event => { | |
| 134 | + | if (event.key === Qt.Key_Delete && win.cur) { | |
| 135 | + | Clip.remove(win.cur.id) | |
| 136 | + | event.accepted = true | |
| 137 | + | } else if (event.key === Qt.Key_PageDown) { | |
| 138 | + | list.currentIndex = Math.min(win.shown.length - 1, list.currentIndex + 10); event.accepted = true | |
| 139 | + | } else if (event.key === Qt.Key_PageUp) { | |
| 140 | + | list.currentIndex = Math.max(0, list.currentIndex - 10); event.accepted = true | |
| 125 | 141 | } | |
| 126 | 142 | } | |
| 127 | − | IconBtn { icon: Theme.iClose; onClicked: win.closeRequested() } | |
| 128 | 143 | } | |
| 129 | − | ||
| 130 | − | ListView { | |
| 131 | − | id: list | |
| 132 | − | Layout.fillWidth: true | |
| 133 | − | Layout.fillHeight: true | |
| 134 | − | clip: true | |
| 135 | − | model: win.shown | |
| 136 | − | spacing: 2 | |
| 137 | − | boundsBehavior: Flickable.StopAtBounds | |
| 138 | − | highlightMoveDuration: 0 | |
| 139 | − | ||
| 140 | − | delegate: Rectangle { | |
| 141 | − | id: row | |
| 142 | − | required property var modelData | |
| 143 | − | required property int index | |
| 144 | − | readonly property bool selected: list.currentIndex === index | |
| 145 | − | readonly property bool hasImg: modelData.image && Clip.ready[modelData.id] === true | |
| 146 | − | width: list.width | |
| 147 | − | height: hasImg ? 96 : 28 | |
| 148 | − | color: selected ? Theme.surfaceHi : (rm.containsMouse ? Theme.surface : "transparent") | |
| 149 | − | border.width: selected ? 1 : 0 | |
| 150 | − | border.color: Theme.accent | |
| 151 | − | ||
| 152 | − | Rectangle { // selection marker, like the bar's active workspace | |
| 153 | − | visible: row.selected | |
| 154 | − | width: 2 | |
| 155 | − | height: parent.height | |
| 156 | − | color: Theme.accent | |
| 144 | + | Row { | |
| 145 | + | id: chips | |
| 146 | + | anchors.right: parent.right | |
| 147 | + | anchors.rightMargin: 12 | |
| 148 | + | anchors.verticalCenter: parent.verticalCenter | |
| 149 | + | spacing: 6 | |
| 150 | + | Repeater { | |
| 151 | + | model: win.kinds | |
| 152 | + | Chip { | |
| 153 | + | required property string modelData | |
| 154 | + | required property int index | |
| 155 | + | text: modelData | |
| 156 | + | selected: win.kind === index | |
| 157 | + | onClicked: { win.kind = index; list.currentIndex = 0; filter.forceActiveFocus() } | |
| 157 | 158 | } | |
| 158 | − | Row { | |
| 159 | − | anchors.fill: parent | |
| 160 | − | anchors.leftMargin: 10 | |
| 161 | − | anchors.rightMargin: 34 | |
| 162 | − | spacing: 10 | |
| 163 | − | Image { | |
| 164 | − | visible: row.hasImg | |
| 165 | − | anchors.verticalCenter: parent.verticalCenter | |
| 166 | − | width: visible ? 150 : 0 | |
| 167 | − | height: 84 | |
| 168 | − | fillMode: Image.PreserveAspectFit | |
| 169 | − | asynchronous: true | |
| 170 | − | sourceSize.width: 300 | |
| 171 | − | sourceSize.height: 168 | |
| 172 | − | source: row.hasImg ? "file://" + Clip.imageOf(row.modelData) : "" | |
| 173 | − | } | |
| 174 | − | Text { | |
| 175 | − | anchors.verticalCenter: parent.verticalCenter | |
| 176 | − | width: parent.width - (row.hasImg ? 160 : 0) | |
| 177 | − | text: (row.modelData.image ? Theme.iImage + " " : "") + row.modelData.text | |
| 178 | − | elide: Text.ElideRight | |
| 179 | − | maximumLineCount: 1 | |
| 180 | − | color: row.selected ? Theme.fg : (row.modelData.image ? Theme.accent : Theme.fg) | |
| 181 | − | font.family: Theme.font | |
| 182 | − | font.pixelSize: Theme.fontSize | |
| 183 | − | } | |
| 159 | + | } | |
| 160 | + | } | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | // ---- the history ---- | |
| 164 | + | Row { | |
| 165 | + | id: head | |
| 166 | + | x: 40 | |
| 167 | + | anchors.top: search.bottom | |
| 168 | + | anchors.topMargin: 22 | |
| 169 | + | spacing: 14 | |
| 170 | + | Num { anchors.baseline: hl.baseline; text: win.shown.length; font.pixelSize: 34 } | |
| 171 | + | Lb { id: hl; text: win.shown.length === 1 ? "entry" : "entries" } | |
| 172 | + | } | |
| 173 | + | Row { | |
| 174 | + | anchors.right: list.right | |
| 175 | + | anchors.verticalCenter: head.verticalCenter | |
| 176 | + | spacing: 8 | |
| 177 | + | IconBtn { | |
| 178 | + | visible: Clip.entries.length > 0 | |
| 179 | + | icon: Theme.iTrash | |
| 180 | + | label: win.armed ? "sure?" : "wipe all" | |
| 181 | + | active: win.armed | |
| 182 | + | onClicked: { | |
| 183 | + | if (!win.armed) { win.armed = true; disarm.restart(); return } | |
| 184 | + | win.armed = false | |
| 185 | + | Clip.wipe() | |
| 186 | + | } | |
| 187 | + | } | |
| 188 | + | } | |
| 189 | + | ||
| 190 | + | ListView { | |
| 191 | + | id: list | |
| 192 | + | x: 34 | |
| 193 | + | anchors.top: head.bottom | |
| 194 | + | anchors.topMargin: 18 | |
| 195 | + | anchors.bottom: parent.bottom | |
| 196 | + | anchors.bottomMargin: 34 | |
| 197 | + | width: Math.round((parent.width - 68) * 0.48) | |
| 198 | + | clip: true | |
| 199 | + | model: win.shown | |
| 200 | + | spacing: 4 | |
| 201 | + | boundsBehavior: Flickable.StopAtBounds | |
| 202 | + | highlightMoveDuration: 0 | |
| 203 | + | ||
| 204 | + | delegate: Rectangle { | |
| 205 | + | id: row | |
| 206 | + | required property var modelData | |
| 207 | + | required property int index | |
| 208 | + | readonly property bool selected: list.currentIndex === index | |
| 209 | + | readonly property string ty: win.typeOf(modelData) | |
| 210 | + | readonly property bool hasImg: modelData.image && Clip.ready[modelData.id] === true | |
| 211 | + | width: list.width | |
| 212 | + | height: 66 | |
| 213 | + | radius: 33 | |
| 214 | + | color: selected ? Theme.fill : rm.containsMouse ? Theme.s2 : "transparent" | |
| 215 | + | ||
| 216 | + | MouseArea { | |
| 217 | + | id: rm | |
| 218 | + | anchors.fill: parent | |
| 219 | + | hoverEnabled: true | |
| 220 | + | cursorShape: Qt.PointingHandCursor | |
| 221 | + | onEntered: list.currentIndex = row.index | |
| 222 | + | onClicked: win.pick(row.modelData) | |
| 223 | + | } | |
| 224 | + | Rectangle { | |
| 225 | + | id: ic | |
| 226 | + | x: 8 | |
| 227 | + | anchors.verticalCenter: parent.verticalCenter | |
| 228 | + | width: 50 | |
| 229 | + | height: 50 | |
| 230 | + | radius: 25 | |
| 231 | + | color: row.ty === "colour" ? row.modelData.text.trim() : row.selected ? Theme.fillFg : Theme.s2 | |
| 232 | + | Icon { | |
| 233 | + | anchors.centerIn: parent | |
| 234 | + | visible: row.ty !== "colour" && !thumb.visible | |
| 235 | + | text: win.icon(row.ty) | |
| 236 | + | font.pixelSize: 20 | |
| 237 | + | color: row.selected ? Theme.fill : Theme.fg | |
| 184 | 238 | } | |
| 185 | − | MouseArea { | |
| 186 | − | id: rm | |
| 239 | + | Image { | |
| 240 | + | id: thumb | |
| 187 | 241 | anchors.fill: parent | |
| 188 | − | hoverEnabled: true | |
| 189 | − | cursorShape: Qt.PointingHandCursor | |
| 190 | − | onClicked: win.pick(row.modelData) | |
| 242 | + | visible: row.hasImg && status === Image.Ready | |
| 243 | + | source: row.hasImg ? "file://" + Clip.imageOf(row.modelData) : "" | |
| 244 | + | fillMode: Image.PreserveAspectCrop | |
| 245 | + | sourceSize: Qt.size(100, 100) | |
| 246 | + | asynchronous: true | |
| 247 | + | layer.enabled: true | |
| 248 | + | layer.effect: DotMask { step: 3; circle: true } | |
| 191 | 249 | } | |
| 192 | − | Text { // delete | |
| 193 | − | anchors.right: parent.right | |
| 194 | − | anchors.rightMargin: 10 | |
| 195 | − | anchors.verticalCenter: parent.verticalCenter | |
| 196 | − | visible: row.selected | |
| 197 | − | text: Theme.iClose | |
| 198 | − | color: dm.containsMouse ? Theme.err : Theme.dim | |
| 199 | − | font.family: Theme.font | |
| 200 | − | font.pixelSize: Theme.fontSize | |
| 201 | − | MouseArea { | |
| 202 | − | id: dm | |
| 203 | − | anchors.fill: parent | |
| 204 | − | anchors.margins: -4 | |
| 205 | − | hoverEnabled: true | |
| 206 | − | cursorShape: Qt.PointingHandCursor | |
| 207 | − | onClicked: Clip.remove(row.modelData.id) | |
| 208 | − | } | |
| 250 | + | } | |
| 251 | + | Column { | |
| 252 | + | anchors.left: ic.right | |
| 253 | + | anchors.leftMargin: 14 | |
| 254 | + | anchors.right: num.left | |
| 255 | + | anchors.rightMargin: 12 | |
| 256 | + | anchors.verticalCenter: parent.verticalCenter | |
| 257 | + | Text { | |
| 258 | + | width: parent.width | |
| 259 | + | elide: Text.ElideRight | |
| 260 | + | maximumLineCount: 1 | |
| 261 | + | text: row.modelData.text.replace(/\s+/g, " ") | |
| 262 | + | font.family: Theme.ui | |
| 263 | + | font.pixelSize: 15 | |
| 264 | + | color: row.selected ? Theme.fillFg : Theme.fg | |
| 209 | 265 | } | |
| 266 | + | Lb { text: row.ty; px: 10; color: row.selected ? Theme.fillFg : Theme.grey } | |
| 210 | 267 | } | |
| 268 | + | Num { | |
| 269 | + | id: num | |
| 270 | + | anchors.right: parent.right | |
| 271 | + | anchors.rightMargin: 22 | |
| 272 | + | anchors.verticalCenter: parent.verticalCenter | |
| 273 | + | text: ("0" + (row.index + 1)).slice(-2) | |
| 274 | + | font.pixelSize: 28 | |
| 275 | + | color: row.selected ? Theme.fillFg : Theme.mute | |
| 276 | + | } | |
| 277 | + | } | |
| 278 | + | Lb { | |
| 279 | + | visible: win.shown.length === 0 | |
| 280 | + | anchors.centerIn: parent | |
| 281 | + | color: Theme.mute | |
| 282 | + | text: Clip.available ? (Clip.entries.length ? "no match" : "clipboard is empty") : "cliphist is not installed" | |
| 211 | 283 | } | |
| 284 | + | } | |
| 212 | 285 | ||
| 286 | + | // ---- the selected entry in full ---- | |
| 287 | + | Rectangle { | |
| 288 | + | id: preview | |
| 289 | + | anchors.left: list.right | |
| 290 | + | anchors.leftMargin: 24 | |
| 291 | + | anchors.right: parent.right | |
| 292 | + | anchors.rightMargin: 34 | |
| 293 | + | anchors.top: search.bottom | |
| 294 | + | anchors.topMargin: 30 | |
| 295 | + | anchors.bottom: parent.bottom | |
| 296 | + | anchors.bottomMargin: 34 | |
| 297 | + | radius: 30 | |
| 298 | + | color: Theme.s2 | |
| 299 | + | readonly property var e: win.cur | |
| 300 | + | readonly property string ty: e ? win.typeOf(e) : "" | |
| 301 | + | ||
| 302 | + | Lb { | |
| 303 | + | id: pl | |
| 304 | + | x: 30 | |
| 305 | + | y: 28 | |
| 306 | + | text: preview.e ? preview.ty + " · " + (list.currentIndex + 1) + " of " + win.shown.length : "" | |
| 307 | + | } | |
| 213 | 308 | Text { | |
| 214 | − | Layout.fillWidth: true | |
| 215 | − | text: Clip.available ? "enter copy del remove esc close" : "cliphist not installed" | |
| 216 | − | color: Clip.available ? Theme.dim : Theme.err | |
| 217 | − | font.family: Theme.font | |
| 218 | − | font.pixelSize: Theme.fontSize - 2 | |
| 309 | + | visible: preview.e !== null && preview.ty !== "image" | |
| 310 | + | anchors.left: parent.left | |
| 311 | + | anchors.right: parent.right | |
| 312 | + | anchors.top: pl.bottom | |
| 313 | + | anchors.bottom: parent.bottom | |
| 314 | + | anchors.margins: 30 | |
| 315 | + | wrapMode: Text.WrapAnywhere | |
| 316 | + | elide: Text.ElideRight | |
| 317 | + | textFormat: Text.PlainText | |
| 318 | + | text: preview.e ? preview.e.text : "" | |
| 319 | + | font.family: preview.ty === "code" ? Theme.mono : Theme.ui | |
| 320 | + | font.pixelSize: preview.ty === "code" ? 14 : 17 | |
| 321 | + | color: preview.ty === "colour" ? preview.e.text.trim() : Theme.fg | |
| 219 | 322 | } | |
| 323 | + | Image { | |
| 324 | + | id: big | |
| 325 | + | visible: preview.ty === "image" | |
| 326 | + | anchors.left: parent.left | |
| 327 | + | anchors.right: parent.right | |
| 328 | + | anchors.top: pl.bottom | |
| 329 | + | anchors.bottom: dims.top | |
| 330 | + | anchors.margins: 30 | |
| 331 | + | fillMode: Image.PreserveAspectFit | |
| 332 | + | asynchronous: true | |
| 333 | + | sourceSize.width: 900 | |
| 334 | + | source: preview.ty === "image" && Clip.ready[preview.e.id] ? "file://" + Clip.imageOf(preview.e) : "" | |
| 335 | + | } | |
| 336 | + | Row { | |
| 337 | + | id: dims | |
| 338 | + | visible: preview.ty === "image" | |
| 339 | + | x: 30 | |
| 340 | + | anchors.bottom: parent.bottom | |
| 341 | + | anchors.bottomMargin: 28 | |
| 342 | + | spacing: 12 | |
| 343 | + | Num { anchors.baseline: df.baseline; text: preview.e && preview.ty === "image" ? preview.e.text.split(" ")[1] || "" : ""; font.pixelSize: 40 } | |
| 344 | + | Lb { id: df; text: preview.e && preview.ty === "image" ? preview.e.fmt : "" } | |
| 345 | + | } | |
| 346 | + | } | |
| 347 | + | } | |
| 348 | + | ||
| 349 | + | Row { | |
| 350 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 351 | + | anchors.top: card.bottom | |
| 352 | + | anchors.topMargin: 28 | |
| 353 | + | opacity: win.t | |
| 354 | + | spacing: 36 | |
| 355 | + | Repeater { | |
| 356 | + | model: ["↑↓ select", "↵ copy", "tab filter", "del delete", "esc close"] | |
| 357 | + | Lb { required property string modelData; text: modelData } | |
| 220 | 358 | } | |
| 221 | 359 | } | |
| 222 | 360 | } | |
| @@ -1,4 +1,5 @@ | |||
| 1 | 1 | import QtQuick | |
| 2 | + | import QtQuick.Effects | |
| 2 | 3 | import Quickshell | |
| 3 | 4 | import Quickshell.Wayland | |
| 4 | 5 | import Quickshell.Hyprland | |
| @@ -13,12 +14,12 @@ PanelWindow { | |||
| 13 | 14 | visible: Settings.dock | |
| 14 | 15 | ||
| 15 | 16 | readonly property var hmon: Hyprland.monitorFor(modelData) | |
| 16 | − | readonly property int size: 40 | |
| 17 | + | readonly property int size: 48 | |
| 17 | 18 | property bool shown: false | |
| 18 | 19 | ||
| 19 | 20 | anchors.bottom: true | |
| 20 | − | implicitWidth: Math.max(160, row.implicitWidth + 16) | |
| 21 | − | implicitHeight: size + 16 | |
| 21 | + | implicitWidth: Math.max(160, row.implicitWidth + 28) | |
| 22 | + | implicitHeight: size + 30 | |
| 22 | 23 | exclusiveZone: 0 | |
| 23 | 24 | color: "transparent" | |
| 24 | 25 | // hidden: only a 2px strip at the very bottom catches the mouse; shown: the whole dock | |
| @@ -86,20 +87,22 @@ PanelWindow { | |||
| 86 | 87 | onEntered: { hide.stop(); win.shown = true } | |
| 87 | 88 | onExited: hide.restart() | |
| 88 | 89 | ||
| 90 | + | // a pill of round icons; running apps get dots under them (one per window, up to 3) | |
| 89 | 91 | Rectangle { | |
| 90 | 92 | id: panel | |
| 91 | 93 | width: parent.width | |
| 92 | − | height: parent.height - 6 | |
| 94 | + | height: parent.height - 8 | |
| 93 | 95 | y: win.shown ? 0 : parent.height | |
| 94 | − | Behavior on y { NumberAnimation { duration: 140; easing.type: Easing.OutCubic } } | |
| 95 | − | color: Theme.bgA | |
| 96 | + | Behavior on y { enabled: Theme.motion; NumberAnimation { duration: Theme.t; easing.type: Easing.BezierSpline; easing.bezierCurve: Theme.glide } } | |
| 97 | + | radius: height / 2 | |
| 98 | + | color: Qt.rgba(Theme.s1.r, Theme.s1.g, Theme.s1.b, Theme.panelAlpha) | |
| 96 | 99 | border.width: 1 | |
| 97 | − | border.color: Theme.accent | |
| 100 | + | border.color: Theme.s2 | |
| 98 | 101 | ||
| 99 | 102 | Row { | |
| 100 | 103 | id: row | |
| 101 | 104 | anchors.centerIn: parent | |
| 102 | − | spacing: 4 | |
| 105 | + | spacing: 8 | |
| 103 | 106 | ||
| 104 | 107 | Repeater { | |
| 105 | 108 | model: win.apps | |
| @@ -119,43 +122,53 @@ PanelWindow { | |||
| 119 | 122 | ||
| 120 | 123 | Rectangle { | |
| 121 | 124 | anchors.fill: parent | |
| 122 | − | color: cell.focused ? Theme.accentDim : (cm.containsMouse ? Theme.surfaceHi : "transparent") | |
| 125 | + | anchors.margins: 2 | |
| 126 | + | radius: width / 2 | |
| 127 | + | color: cell.focused ? Theme.fill : (cm.containsMouse ? Theme.s3 : Theme.s2) | |
| 123 | 128 | } | |
| 124 | 129 | Image { | |
| 125 | 130 | id: ico | |
| 126 | 131 | anchors.centerIn: parent | |
| 127 | − | width: win.size - 12 | |
| 132 | + | width: win.size - 20 | |
| 128 | 133 | height: width | |
| 129 | 134 | sourceSize.width: 64 | |
| 130 | 135 | sourceSize.height: 64 | |
| 131 | 136 | asynchronous: true | |
| 132 | 137 | source: Quickshell.iconPath(cell.modelData.entry ? cell.modelData.entry.icon : cell.modelData.id.toLowerCase(), true) | |
| 138 | + | layer.enabled: Settings.appIcons !== "colour" | |
| 139 | + | layer.effect: Settings.appIcons === "mono" ? monoFx : dotFx | |
| 140 | + | Component { id: dotFx; DotMask { step: 2.5 } } | |
| 141 | + | Component { id: monoFx; MultiEffect { saturation: -1 } } | |
| 133 | 142 | } | |
| 134 | 143 | Text { // icon not in the theme: first letter of the app name | |
| 135 | 144 | anchors.centerIn: parent | |
| 136 | 145 | visible: ico.status !== Image.Ready | |
| 137 | 146 | text: (cell.modelData.entry ? cell.modelData.entry.name : cell.modelData.id).charAt(0).toUpperCase() | |
| 138 | − | color: Theme.accent | |
| 139 | − | font.family: Theme.font | |
| 140 | − | font.pixelSize: 18 | |
| 141 | − | font.bold: true | |
| 147 | + | color: cell.focused ? Theme.fillFg : Theme.fg | |
| 148 | + | font.family: Theme.dot | |
| 149 | + | font.pixelSize: 26 | |
| 150 | + | font.weight: Font.Black | |
| 142 | 151 | } | |
| 143 | − | Rectangle { // running marker: accent underline, wider with more windows | |
| 152 | + | Row { // running: a dot per window (up to 3) under the icon | |
| 144 | 153 | visible: cell.running | |
| 145 | − | anchors.bottom: parent.bottom | |
| 154 | + | anchors.top: parent.bottom | |
| 155 | + | anchors.topMargin: 3 | |
| 146 | 156 | anchors.horizontalCenter: parent.horizontalCenter | |
| 147 | − | width: Math.min(parent.width - 8, 8 + (cell.modelData.wins.length - 1) * 8) | |
| 148 | − | height: 2 | |
| 149 | − | color: Theme.accent | |
| 157 | + | spacing: 3 | |
| 158 | + | Repeater { | |
| 159 | + | model: Math.min(3, cell.modelData.wins.length) | |
| 160 | + | Rectangle { width: 4; height: 4; radius: 2; color: cell.focused ? Theme.fill : Theme.grey } | |
| 161 | + | } | |
| 150 | 162 | } | |
| 151 | 163 | Rectangle { // pinned marker: small dot top-right | |
| 152 | − | visible: cell.modelData.pinned | |
| 164 | + | visible: cell.modelData.pinned && !cell.running | |
| 153 | 165 | anchors.top: parent.top | |
| 154 | 166 | anchors.right: parent.right | |
| 155 | − | anchors.margins: 3 | |
| 156 | − | width: 3 | |
| 157 | − | height: 3 | |
| 158 | − | color: Theme.dim | |
| 167 | + | anchors.margins: 2 | |
| 168 | + | width: 5 | |
| 169 | + | height: 5 | |
| 170 | + | radius: 2.5 | |
| 171 | + | color: Theme.mute | |
| 159 | 172 | } | |
| 160 | 173 | MouseArea { | |
| 161 | 174 | id: cm | |
| @@ -73,7 +73,7 @@ PanelWindow { | |||
| 73 | 73 | ||
| 74 | 74 | Rectangle { | |
| 75 | 75 | anchors.fill: parent | |
| 76 | − | color: Theme.bgA | |
| 76 | + | color: Theme.scrim | |
| 77 | 77 | MouseArea { anchors.fill: parent; onClicked: win.closeRequested() } | |
| 78 | 78 | } | |
| 79 | 79 | ||
| @@ -87,42 +87,57 @@ PanelWindow { | |||
| 87 | 87 | Layout.alignment: Qt.AlignHCenter | |
| 88 | 88 | spacing: 8 | |
| 89 | 89 | Rectangle { | |
| 90 | − | implicitWidth: 420 | |
| 91 | − | implicitHeight: 32 | |
| 92 | − | color: Theme.surface | |
| 93 | − | border.width: 1 | |
| 94 | − | border.color: Theme.accent | |
| 95 | − | Text { | |
| 96 | − | anchors.left: parent.left | |
| 97 | − | anchors.leftMargin: 10 | |
| 90 | + | implicitWidth: 520 | |
| 91 | + | implicitHeight: 56 | |
| 92 | + | radius: 28 | |
| 93 | + | color: Theme.s2 | |
| 94 | + | Icon { | |
| 95 | + | id: lens | |
| 96 | + | x: 20 | |
| 98 | 97 | anchors.verticalCenter: parent.verticalCenter | |
| 99 | 98 | text: Theme.iSearch | |
| 100 | − | color: Theme.dim | |
| 101 | − | font.family: Theme.font | |
| 102 | − | font.pixelSize: Theme.fontSize | |
| 99 | + | color: Theme.grey | |
| 100 | + | font.pixelSize: 19 | |
| 101 | + | } | |
| 102 | + | Text { | |
| 103 | + | anchors.left: filter.left | |
| 104 | + | anchors.verticalCenter: parent.verticalCenter | |
| 105 | + | visible: filter.text === "" | |
| 106 | + | text: "Filter windows" | |
| 107 | + | color: Theme.mute | |
| 108 | + | font: filter.font | |
| 103 | 109 | } | |
| 104 | 110 | TextInput { | |
| 105 | 111 | id: filter | |
| 106 | − | anchors.fill: parent | |
| 107 | − | anchors.leftMargin: 32 | |
| 108 | − | anchors.rightMargin: 10 | |
| 109 | − | verticalAlignment: TextInput.AlignVCenter | |
| 112 | + | anchors.left: lens.right | |
| 113 | + | anchors.leftMargin: 14 | |
| 114 | + | anchors.right: parent.right | |
| 115 | + | anchors.rightMargin: 20 | |
| 116 | + | anchors.verticalCenter: parent.verticalCenter | |
| 110 | 117 | color: Theme.fg | |
| 111 | − | selectionColor: Theme.accent | |
| 112 | − | selectedTextColor: Theme.bg | |
| 113 | − | font.family: Theme.font | |
| 114 | − | font.pixelSize: Theme.fontSize | |
| 118 | + | selectionColor: Theme.fill | |
| 119 | + | selectedTextColor: Theme.fillFg | |
| 120 | + | font.family: Theme.ui | |
| 121 | + | font.pixelSize: 18 | |
| 115 | 122 | clip: true | |
| 123 | + | cursorDelegate: Rectangle { | |
| 124 | + | width: 2 | |
| 125 | + | color: Theme.live | |
| 126 | + | visible: filter.activeFocus | |
| 127 | + | Timer { running: filter.activeFocus; interval: 500; repeat: true; onTriggered: parent.opacity = 1 - parent.opacity } | |
| 128 | + | } | |
| 116 | 129 | Keys.onEscapePressed: win.closeRequested() | |
| 117 | 130 | Keys.onReturnPressed: { var t = win.firstMatch(); if (t) win.focus(t) } | |
| 118 | 131 | Keys.onEnterPressed: { var t = win.firstMatch(); if (t) win.focus(t) } | |
| 119 | 132 | } | |
| 120 | 133 | } | |
| 121 | − | Text { | |
| 122 | − | text: "click focus drag move enter first match esc close" | |
| 123 | − | color: Theme.dim | |
| 124 | − | font.family: Theme.font | |
| 125 | − | font.pixelSize: Theme.fontSize - 2 | |
| 134 | + | Row { | |
| 135 | + | Layout.leftMargin: 18 | |
| 136 | + | spacing: 28 | |
| 137 | + | Repeater { | |
| 138 | + | model: ["click focus", "drag move", "↵ first match", "esc close"] | |
| 139 | + | Lb { required property string modelData; text: modelData } | |
| 140 | + | } | |
| 126 | 141 | } | |
| 127 | 142 | } | |
| 128 | 143 | ||
| @@ -135,7 +150,7 @@ PanelWindow { | |||
| 135 | 150 | readonly property int gap: 16 | |
| 136 | 151 | // largest box with the monitor's aspect ratio that fits 5 x 2 | |
| 137 | 152 | readonly property real boxW: Math.min((width - gap * (cols - 1)) / cols, | |
| 138 | − | ((height - gap - 2 * 22) / 2) * win.monW / win.monH) | |
| 153 | + | ((height - gap - 2 * 40) / 2) * win.monW / win.monH) | |
| 139 | 154 | readonly property real boxH: boxW * win.monH / win.monW | |
| 140 | 155 | readonly property real sc: boxW / win.monW | |
| 141 | 156 | ||
| @@ -149,27 +164,36 @@ PanelWindow { | |||
| 149 | 164 | readonly property bool current: win.activeWs !== null && win.activeWs.id === wsId | |
| 150 | 165 | readonly property real originX: (grid.width - (grid.boxW * grid.cols + grid.gap * (grid.cols - 1))) / 2 | |
| 151 | 166 | x: originX + (index % grid.cols) * (grid.boxW + grid.gap) | |
| 152 | − | y: Math.floor(index / grid.cols) * (grid.boxH + grid.gap + 22) | |
| 167 | + | y: Math.floor(index / grid.cols) * (grid.boxH + grid.gap + 40) | |
| 153 | 168 | width: grid.boxW | |
| 154 | − | height: grid.boxH + 22 | |
| 169 | + | height: grid.boxH + 40 | |
| 155 | 170 | z: win.dragWs === wsId ? 10 : 0 | |
| 156 | 171 | ||
| 157 | − | Text { | |
| 158 | − | text: Theme.wsIcon(box.wsId) + " " + box.wsId | |
| 159 | − | color: box.current ? Theme.accent : Theme.dim | |
| 160 | − | font.family: Theme.font | |
| 161 | − | font.pixelSize: Theme.fontSize | |
| 162 | − | font.bold: box.current | |
| 172 | + | Row { | |
| 173 | + | x: 6 | |
| 174 | + | spacing: 10 | |
| 175 | + | Num { | |
| 176 | + | text: box.index + 1 | |
| 177 | + | font.pixelSize: 28 | |
| 178 | + | color: box.current ? Theme.fill : Theme.fg | |
| 179 | + | } | |
| 180 | + | Lb { | |
| 181 | + | anchors.verticalCenter: parent.verticalCenter | |
| 182 | + | text: win.windowsOn(box.wsId).length ? win.windowsOn(box.wsId).length + " open" : "empty" | |
| 183 | + | color: box.current ? Theme.fill : Theme.grey | |
| 184 | + | } | |
| 163 | 185 | } | |
| 164 | 186 | ||
| 165 | 187 | Rectangle { | |
| 166 | 188 | id: frame | |
| 167 | − | y: 20 | |
| 189 | + | y: 34 | |
| 168 | 190 | width: grid.boxW | |
| 169 | 191 | height: grid.boxH | |
| 170 | − | color: Theme.surface | |
| 192 | + | radius: 20 | |
| 193 | + | clip: true | |
| 194 | + | color: Theme.s1 | |
| 171 | 195 | border.width: box.current ? 2 : 1 | |
| 172 | − | border.color: box.current ? Theme.accent : (wm.containsMouse ? Theme.accentDim : Theme.line) | |
| 196 | + | border.color: box.current ? Theme.fill : (wm.containsMouse ? Theme.grey : Theme.s2) | |
| 173 | 197 | ||
| 174 | 198 | MouseArea { | |
| 175 | 199 | id: wm | |
| @@ -194,9 +218,10 @@ PanelWindow { | |||
| 194 | 218 | width: Math.max(12, size[0] * grid.sc) | |
| 195 | 219 | height: Math.max(12, size[1] * grid.sc) | |
| 196 | 220 | z: dm.drag.active ? 10 : (ipc.floating ? 2 : 1) | |
| 197 | − | color: Theme.surfaceHi | |
| 221 | + | color: Theme.s3 | |
| 222 | + | radius: 6 | |
| 198 | 223 | border.width: dm.containsMouse || dm.drag.active ? 2 : 1 | |
| 199 | − | border.color: dm.containsMouse || dm.drag.active ? Theme.accent : Theme.line | |
| 224 | + | border.color: dm.containsMouse || dm.drag.active ? Theme.fill : Theme.s2 | |
| 200 | 225 | opacity: hit ? 1 : 0.25 | |
| 201 | 226 | ||
| 202 | 227 | ScreencopyView { | |
| @@ -1,33 +1,33 @@ | |||
| 1 | 1 | import QtQuick | |
| 2 | − | import QtQuick.Layouts | |
| 3 | 2 | import Quickshell | |
| 4 | 3 | import Quickshell.Wayland | |
| 5 | 4 | import Quickshell.Hyprland | |
| 6 | 5 | ||
| 7 | − | // Session / power actions (was the fuzzel `sysact` script). Type to fuzzy filter, Up/Down/Tab pick, | |
| 8 | − | // Enter runs, Esc closes. Destructive ones need a second Enter/click ("sure?"). | |
| 6 | + | // Session / power actions (SUPER+Escape, the panel's power button). The screen goes nearly black, the actions are a | |
| 7 | + | // column of Doto words: the selected one large with the live dot, the rest dim; uptime in a ring of dots on the right. | |
| 8 | + | // Up/Down/Tab or hover pick, Enter/click runs, typing filters, Esc or a click on the background closes. | |
| 9 | + | // Destructive ones need a second Enter/click within 3s (the word turns the error colour). | |
| 10 | + | // After glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), power/Power.qml. | |
| 9 | 11 | PanelWindow { | |
| 10 | 12 | id: win | |
| 11 | 13 | property bool opened: false | |
| 12 | 14 | signal closeRequested() | |
| 13 | 15 | ||
| 14 | − | visible: opened | |
| 15 | − | implicitWidth: 300 | |
| 16 | − | // fixed to the full list so the centred window doesn't jump while filtering | |
| 17 | − | implicitHeight: 24 + 36 + actions.length * 32 + 22 | |
| 18 | − | exclusiveZone: 0 | |
| 16 | + | property real t: opened ? 1 : 0 | |
| 17 | + | Behavior on t { NumberAnimation { duration: Theme.t; easing.type: Easing.BezierSpline; easing.bezierCurve: Theme.glide } } | |
| 18 | + | ||
| 19 | + | visible: t > 0 | |
| 20 | + | anchors { top: true; bottom: true; left: true; right: true } | |
| 21 | + | exclusionMode: ExclusionMode.Ignore | |
| 19 | 22 | color: "transparent" | |
| 20 | 23 | WlrLayershell.layer: WlrLayer.Overlay | |
| 21 | 24 | WlrLayershell.namespace: "aurora-power" | |
| 22 | 25 | WlrLayershell.keyboardFocus: opened ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None | |
| 23 | 26 | ||
| 24 | − | HyprlandFocusGrab { | |
| 25 | − | windows: [win] | |
| 26 | − | active: win.opened | |
| 27 | − | onCleared: win.closeRequested() | |
| 28 | − | } | |
| 29 | − | ||
| 30 | 27 | readonly property var actions: Session.actions | |
| 28 | + | // short words for the big type (Session's labels stay for the runner) | |
| 29 | + | readonly property var words: ({ leave: "log out", renew: "reload", display: "screen off", shutdown: "shut down" }) | |
| 30 | + | function word(a) { return words[a.id] || a.label } | |
| 31 | 31 | ||
| 32 | 32 | property int current: 0 | |
| 33 | 33 | property string armed: "" | |
| @@ -36,7 +36,7 @@ PanelWindow { | |||
| 36 | 36 | onOpenedChanged: { | |
| 37 | 37 | if (!opened) return | |
| 38 | 38 | filter.text = "" | |
| 39 | − | current = 0 | |
| 39 | + | current = Math.max(0, shown.indexOf(shown.filter(a => a.id === "shutdown")[0])) | |
| 40 | 40 | armed = "" | |
| 41 | 41 | filter.forceActiveFocus() | |
| 42 | 42 | } | |
| @@ -44,10 +44,10 @@ PanelWindow { | |||
| 44 | 44 | readonly property var shown: { | |
| 45 | 45 | var q = filter.text.toLowerCase().trim(), out = [] | |
| 46 | 46 | for (var i = 0; i < actions.length; i++) { | |
| 47 | − | var sc = Fuzzy.score(actions[i].label, q) | |
| 47 | + | var sc = Math.max(Fuzzy.score(actions[i].label, q), Fuzzy.score(word(actions[i]), q)) | |
| 48 | 48 | if (sc > 0) out.push({ a: actions[i], sc: sc, i: i }) | |
| 49 | 49 | } | |
| 50 | − | out.sort(function (x, y) { return y.sc - x.sc || x.i - y.i }) | |
| 50 | + | if (q !== "") out.sort(function (x, y) { return y.sc - x.sc || x.i - y.i }) | |
| 51 | 51 | var res = [] | |
| 52 | 52 | for (var j = 0; j < out.length; j++) res.push(out[j].a) | |
| 53 | 53 | return res | |
| @@ -63,123 +63,132 @@ PanelWindow { | |||
| 63 | 63 | Session.run(a.id) | |
| 64 | 64 | } | |
| 65 | 65 | ||
| 66 | + | SystemClock { id: clock; precision: SystemClock.Minutes } | |
| 67 | + | ||
| 66 | 68 | Rectangle { | |
| 67 | 69 | anchors.fill: parent | |
| 68 | − | color: Theme.bgA | |
| 69 | − | border.width: 2 | |
| 70 | − | border.color: Theme.accent | |
| 71 | − | ||
| 72 | − | ColumnLayout { | |
| 73 | − | id: col | |
| 74 | − | anchors.fill: parent | |
| 75 | − | anchors.margins: 12 | |
| 76 | − | spacing: 2 | |
| 77 | − | ||
| 78 | − | Rectangle { | |
| 79 | − | Layout.fillWidth: true | |
| 80 | − | Layout.bottomMargin: 6 | |
| 81 | − | implicitHeight: 30 | |
| 82 | − | color: Theme.surface | |
| 83 | − | border.width: 1 | |
| 84 | − | border.color: filter.activeFocus ? Theme.accent : Theme.line | |
| 85 | − | Text { | |
| 86 | − | anchors.left: parent.left | |
| 87 | − | anchors.leftMargin: 10 | |
| 88 | − | anchors.verticalCenter: parent.verticalCenter | |
| 89 | − | text: Theme.iPower | |
| 90 | − | color: Theme.dim | |
| 91 | − | font.family: Theme.font | |
| 92 | − | font.pixelSize: Theme.fontSize | |
| 93 | − | } | |
| 94 | − | TextInput { | |
| 95 | − | id: filter | |
| 96 | − | anchors.fill: parent | |
| 97 | − | anchors.leftMargin: 32 | |
| 98 | − | anchors.rightMargin: 10 | |
| 99 | − | verticalAlignment: TextInput.AlignVCenter | |
| 100 | − | color: Theme.fg | |
| 101 | − | selectionColor: Theme.accent | |
| 102 | − | selectedTextColor: Theme.bg | |
| 103 | − | font.family: Theme.font | |
| 104 | − | font.pixelSize: Theme.fontSize | |
| 105 | − | clip: true | |
| 106 | − | onTextChanged: { win.current = 0; win.armed = "" } | |
| 107 | − | Keys.onEscapePressed: win.closeRequested() | |
| 108 | − | Keys.onReturnPressed: win.trigger(win.current) | |
| 109 | − | Keys.onEnterPressed: win.trigger(win.current) | |
| 110 | − | Keys.onPressed: event => { | |
| 111 | − | var n = win.shown.length | |
| 112 | − | if (!n) return | |
| 113 | − | if (event.key === Qt.Key_Down || event.key === Qt.Key_Tab) { | |
| 114 | − | win.current = (win.current + 1) % n; win.armed = ""; event.accepted = true | |
| 115 | − | } else if (event.key === Qt.Key_Up || event.key === Qt.Key_Backtab) { | |
| 116 | − | win.current = (win.current + n - 1) % n; win.armed = ""; event.accepted = true | |
| 117 | − | } | |
| 118 | − | } | |
| 119 | − | } | |
| 70 | + | color: Theme.fade(Theme.bg, 0.96) | |
| 71 | + | opacity: win.t | |
| 72 | + | MouseArea { anchors.fill: parent; onClicked: win.closeRequested() } | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | // typing filters; the field itself stays invisible, the filter shows as a label | |
| 76 | + | TextInput { | |
| 77 | + | id: filter | |
| 78 | + | width: 0 | |
| 79 | + | height: 0 | |
| 80 | + | opacity: 0 | |
| 81 | + | onTextChanged: { win.current = 0; win.armed = "" } | |
| 82 | + | Keys.onEscapePressed: win.closeRequested() | |
| 83 | + | Keys.onReturnPressed: win.trigger(win.current) | |
| 84 | + | Keys.onEnterPressed: win.trigger(win.current) | |
| 85 | + | Keys.onPressed: event => { | |
| 86 | + | var n = win.shown.length | |
| 87 | + | if (!n) return | |
| 88 | + | if (event.key === Qt.Key_Down || event.key === Qt.Key_Tab) { | |
| 89 | + | win.current = (win.current + 1) % n; win.armed = ""; event.accepted = true | |
| 90 | + | } else if (event.key === Qt.Key_Up || event.key === Qt.Key_Backtab) { | |
| 91 | + | win.current = (win.current + n - 1) % n; win.armed = ""; event.accepted = true | |
| 120 | 92 | } | |
| 93 | + | } | |
| 94 | + | } | |
| 95 | + | ||
| 96 | + | // the 1920 x 1080 canvas, scaled to the screen | |
| 97 | + | Item { | |
| 98 | + | id: stage | |
| 99 | + | readonly property real s: Math.min(win.width / 1920, win.height / 1080) | |
| 100 | + | width: win.width / s | |
| 101 | + | height: win.height / s | |
| 102 | + | scale: s | |
| 103 | + | transformOrigin: Item.TopLeft | |
| 104 | + | opacity: win.t | |
| 121 | 105 | ||
| 106 | + | Lb { | |
| 107 | + | x: 240 | |
| 108 | + | y: 90 | |
| 109 | + | visible: filter.text !== "" | |
| 110 | + | px: 14 | |
| 111 | + | color: Theme.fg | |
| 112 | + | text: "filter · " + filter.text + (win.shown.length ? "" : " · no match") | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | Column { | |
| 116 | + | x: 240 - 30 * (1 - win.t) | |
| 117 | + | anchors.verticalCenter: parent.verticalCenter | |
| 118 | + | spacing: 20 | |
| 122 | 119 | Repeater { | |
| 123 | 120 | model: win.shown | |
| 124 | − | delegate: Rectangle { | |
| 125 | − | id: row | |
| 121 | + | Row { | |
| 122 | + | id: it | |
| 126 | 123 | required property var modelData | |
| 127 | 124 | required property int index | |
| 128 | − | readonly property bool selected: win.current === index | |
| 125 | + | readonly property bool on: win.current === index | |
| 129 | 126 | readonly property bool isArmed: win.armed === modelData.id | |
| 130 | − | Layout.fillWidth: true | |
| 131 | − | implicitHeight: 30 | |
| 132 | − | color: isArmed ? Theme.err : (selected ? Theme.surfaceHi : (rm.containsMouse ? Theme.surface : "transparent")) | |
| 133 | − | border.width: selected ? 1 : 0 | |
| 134 | − | border.color: isArmed ? Theme.err : Theme.accent | |
| 135 | − | ||
| 136 | − | Rectangle { // selection marker, like ClipWindow | |
| 137 | − | visible: row.selected | |
| 138 | − | width: 2 | |
| 139 | − | height: parent.height | |
| 140 | − | color: row.isArmed ? Theme.bg : Theme.accent | |
| 141 | − | } | |
| 142 | − | Text { | |
| 143 | − | id: ico | |
| 144 | − | anchors.left: parent.left | |
| 145 | − | anchors.leftMargin: 12 | |
| 127 | + | spacing: 30 | |
| 128 | + | Rectangle { | |
| 146 | 129 | anchors.verticalCenter: parent.verticalCenter | |
| 147 | − | width: 18 | |
| 148 | − | text: row.modelData.icon | |
| 149 | − | color: row.isArmed ? Theme.bg : Theme.accent | |
| 150 | − | font.family: Theme.font | |
| 151 | − | font.pixelSize: Theme.fontSize + 2 | |
| 130 | + | visible: it.on | |
| 131 | + | width: 30 | |
| 132 | + | height: 30 | |
| 133 | + | radius: 15 | |
| 134 | + | color: it.isArmed ? Theme.err : Theme.live | |
| 152 | 135 | } | |
| 153 | − | Text { | |
| 154 | − | anchors.left: ico.right | |
| 155 | − | anchors.leftMargin: 10 | |
| 156 | − | anchors.verticalCenter: parent.verticalCenter | |
| 157 | − | text: row.isArmed ? row.modelData.label + " sure?" : row.modelData.label | |
| 158 | − | color: row.isArmed ? Theme.bg : Theme.fg | |
| 159 | − | font.family: Theme.font | |
| 160 | − | font.pixelSize: Theme.fontSize | |
| 136 | + | Num { | |
| 137 | + | text: win.word(it.modelData).toUpperCase() | |
| 138 | + | font.pixelSize: it.on ? 128 : 56 | |
| 139 | + | color: it.isArmed ? Theme.err : it.on ? Theme.fg : Theme.mute | |
| 140 | + | Behavior on font.pixelSize { | |
| 141 | + | enabled: Theme.motion | |
| 142 | + | NumberAnimation { duration: Theme.t; easing.type: Easing.BezierSpline; easing.bezierCurve: Theme.glide } | |
| 143 | + | } | |
| 144 | + | Behavior on color { enabled: Theme.motion; ColorAnimation { duration: Theme.t } } | |
| 161 | 145 | } | |
| 162 | − | MouseArea { | |
| 163 | − | id: rm | |
| 164 | − | anchors.fill: parent | |
| 165 | − | hoverEnabled: true | |
| 166 | − | cursorShape: Qt.PointingHandCursor | |
| 167 | − | onEntered: if (win.current !== row.index) { win.current = row.index; win.armed = "" } | |
| 168 | − | onClicked: win.trigger(row.index) | |
| 146 | + | Lb { | |
| 147 | + | visible: it.isArmed | |
| 148 | + | anchors.verticalCenter: parent.verticalCenter | |
| 149 | + | px: 14 | |
| 150 | + | color: Theme.err | |
| 151 | + | text: "↵ again to " + win.word(it.modelData) | |
| 169 | 152 | } | |
| 153 | + | HoverHandler { onHoveredChanged: if (hovered && win.current !== it.index) { win.current = it.index; win.armed = "" } } | |
| 154 | + | TapHandler { onTapped: win.trigger(it.index) } | |
| 170 | 155 | } | |
| 171 | 156 | } | |
| 157 | + | } | |
| 172 | 158 | ||
| 173 | − | Item { Layout.fillHeight: true } | |
| 174 | − | ||
| 175 | − | Text { | |
| 176 | − | Layout.fillWidth: true | |
| 177 | − | Layout.topMargin: 6 | |
| 178 | − | text: win.shown.length ? "type filter enter run esc close" : "no match" | |
| 179 | − | color: win.shown.length ? Theme.dim : Theme.err | |
| 180 | − | font.family: Theme.font | |
| 181 | − | font.pixelSize: Theme.fontSize - 2 | |
| 159 | + | Item { | |
| 160 | + | x: stage.width - 200 - 440 | |
| 161 | + | anchors.verticalCenter: parent.verticalCenter | |
| 162 | + | width: 440 | |
| 163 | + | height: 440 | |
| 164 | + | Ring { anchors.fill: parent; n: 60; dot: 10; f: SysStats.uptime / (6 * 3600) } | |
| 165 | + | Column { | |
| 166 | + | anchors.centerIn: parent | |
| 167 | + | Lb { anchors.horizontalCenter: parent.horizontalCenter; text: "uptime" } | |
| 168 | + | Num { | |
| 169 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 170 | + | topPadding: lead + 10 | |
| 171 | + | bottomPadding: lead + 10 | |
| 172 | + | text: Math.floor(SysStats.uptime / 3600) + ":" + ("0" + Math.floor(SysStats.uptime / 60) % 60).slice(-2) | |
| 173 | + | font.pixelSize: 104 | |
| 174 | + | } | |
| 175 | + | Lb { | |
| 176 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 177 | + | readonly property int n: Hyprland.toplevels.values.length | |
| 178 | + | text: n + (n === 1 ? " window open" : " windows open") | |
| 179 | + | } | |
| 182 | 180 | } | |
| 183 | 181 | } | |
| 182 | + | ||
| 183 | + | Row { | |
| 184 | + | x: 240 | |
| 185 | + | anchors.bottom: parent.bottom | |
| 186 | + | anchors.bottomMargin: 70 | |
| 187 | + | spacing: 34 | |
| 188 | + | Lb { text: "↑↓ select" } | |
| 189 | + | Lb { text: "↵ confirm" } | |
| 190 | + | Lb { text: "type to filter" } | |
| 191 | + | Lb { text: "esc cancel" } | |
| 192 | + | } | |
| 184 | 193 | } | |
| 185 | 194 | } | |
| @@ -56,7 +56,7 @@ Singleton { | |||
| 56 | 56 | } | |
| 57 | 57 | Process { | |
| 58 | 58 | id: selectProc | |
| 59 | − | command: ["slurp"] | |
| 59 | + | command: ["slurp"].concat(Theme.slurpArgs) | |
| 60 | 60 | // slurp prints nothing when cancelled (Esc, or stop() killing it) | |
| 61 | 61 | stdout: StdioCollector { | |
| 62 | 62 | onStreamFinished: { | |
| @@ -1,5 +1,6 @@ | |||
| 1 | 1 | import QtQuick | |
| 2 | 2 | import QtQuick.Layouts | |
| 3 | + | import QtQuick.Effects | |
| 3 | 4 | import Quickshell | |
| 4 | 5 | import Quickshell.Io | |
| 5 | 6 | import Quickshell.Wayland | |
| @@ -9,6 +10,8 @@ import Quickshell.Hyprland | |||
| 9 | 10 | // aurora commands and every executable on $PATH, ranked by match and launch count. | |
| 10 | 11 | // `=expr` calculator (Enter copies), `>cmd` runs a shell command. Ctrl+Enter runs in the terminal. | |
| 11 | 12 | // dmenu mode (`aurora-run -d`, `dmenu` IPC): plain list from the script's stdin, the choice goes back over a fifo. | |
| 13 | + | // Looks like glyph's launcher (github.com/Nebelung-cloud/glyph-rice, GPL-3.0, launcher/Launcher.qml): a card over the | |
| 14 | + | // dimmed screen, a search pill with the live caret, mode chips (all / run = `>` / calc = `=`), round icons in rows. | |
| 12 | 15 | PanelWindow { | |
| 13 | 16 | id: win | |
| 14 | 17 | property bool opened: false | |
| @@ -16,7 +19,7 @@ PanelWindow { | |||
| 16 | 19 | signal closeRequested() | |
| 17 | 20 | signal openRequested() // a dmenu request is loaded and wants the window | |
| 18 | 21 | ||
| 19 | − | readonly property int rowH: 36 | |
| 22 | + | readonly property int rowH: 60 | |
| 20 | 23 | readonly property int rows: 10 | |
| 21 | 24 | readonly property int maxShown: 60 | |
| 22 | 25 | readonly property string home: Quickshell.env("HOME") | |
| @@ -92,21 +95,17 @@ PanelWindow { | |||
| 92 | 95 | filter.text = "" | |
| 93 | 96 | } | |
| 94 | 97 | ||
| 95 | − | visible: opened | |
| 96 | − | implicitWidth: dmenu ? 640 : 560 | |
| 97 | − | implicitHeight: 24 + 36 + rowsShown * rowH + 22 | |
| 98 | − | exclusiveZone: 0 | |
| 98 | + | property real t: opened ? 1 : 0 | |
| 99 | + | Behavior on t { NumberAnimation { duration: Theme.t; easing.type: Easing.BezierSpline; easing.bezierCurve: Theme.glide } } | |
| 100 | + | ||
| 101 | + | visible: t > 0 | |
| 102 | + | anchors { top: true; bottom: true; left: true; right: true } | |
| 103 | + | exclusionMode: ExclusionMode.Ignore | |
| 99 | 104 | color: "transparent" | |
| 100 | 105 | WlrLayershell.layer: WlrLayer.Overlay | |
| 101 | 106 | WlrLayershell.namespace: "aurora-runner" | |
| 102 | 107 | WlrLayershell.keyboardFocus: opened ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None | |
| 103 | 108 | ||
| 104 | − | HyprlandFocusGrab { | |
| 105 | − | windows: [win] | |
| 106 | − | active: win.opened | |
| 107 | − | onCleared: win.closeRequested() | |
| 108 | − | } | |
| 109 | − | ||
| 110 | 109 | property int current: 0 | |
| 111 | 110 | property string armed: "" | |
| 112 | 111 | Timer { id: disarm; interval: 3000; onTriggered: win.armed = "" } | |
| @@ -372,193 +371,255 @@ PanelWindow { | |||
| 372 | 371 | } | |
| 373 | 372 | ||
| 374 | 373 | // ---- UI ---- | |
| 374 | + | readonly property string modeNow: filter.text.charAt(0) === ">" ? "run" : filter.text.charAt(0) === "=" ? "calc" : "all" | |
| 375 | + | function setMode(m) { | |
| 376 | + | var t = filter.text.replace(/^[>=]\s*/, "") | |
| 377 | + | filter.text = m === "run" ? ">" + t : m === "calc" ? "=" + t : t | |
| 378 | + | filter.forceActiveFocus() | |
| 379 | + | } | |
| 380 | + | ||
| 375 | 381 | Rectangle { | |
| 376 | 382 | anchors.fill: parent | |
| 377 | − | color: Theme.bgA | |
| 378 | − | border.width: 2 | |
| 379 | − | border.color: Theme.accent | |
| 383 | + | color: Theme.scrim | |
| 384 | + | opacity: win.t | |
| 385 | + | MouseArea { anchors.fill: parent; onClicked: win.closeRequested() } | |
| 386 | + | } | |
| 380 | 387 | ||
| 381 | − | ColumnLayout { | |
| 382 | − | anchors.fill: parent | |
| 383 | − | anchors.margins: 12 | |
| 384 | − | spacing: 2 | |
| 388 | + | Rectangle { | |
| 389 | + | id: card | |
| 390 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 391 | + | y: Math.round(parent.height * 0.13) + 24 * (1 - win.t) | |
| 392 | + | width: Math.min(parent.width - 80, win.dmenu ? 760 : 860) | |
| 393 | + | height: 30 + 66 + (win.rowsShown > 0 ? 20 + win.rowsShown * win.rowH : 0) + 30 | |
| 394 | + | opacity: win.t | |
| 395 | + | radius: 44 | |
| 396 | + | color: Qt.rgba(Theme.s1.r, Theme.s1.g, Theme.s1.b, Theme.panelAlpha) | |
| 397 | + | border.width: 1 | |
| 398 | + | border.color: Theme.s2 | |
| 399 | + | MouseArea { anchors.fill: parent } | |
| 385 | 400 | ||
| 386 | − | Rectangle { | |
| 387 | − | Layout.fillWidth: true | |
| 388 | − | Layout.bottomMargin: 6 | |
| 389 | − | implicitHeight: 30 | |
| 390 | − | color: Theme.surface | |
| 391 | − | border.width: 1 | |
| 392 | − | border.color: filter.activeFocus ? Theme.accent : Theme.line | |
| 393 | − | Text { | |
| 394 | − | id: promptText | |
| 395 | − | anchors.left: parent.left | |
| 396 | − | anchors.leftMargin: 10 | |
| 397 | − | anchors.verticalCenter: parent.verticalCenter | |
| 398 | − | text: win.dmenu && win.dmPrompt !== "" ? win.dmPrompt : (win.dmPassword ? Theme.iLock : Theme.iSearch) | |
| 399 | − | color: win.dmenu && win.dmPrompt !== "" ? Theme.accent : Theme.dim | |
| 400 | − | font.family: Theme.font | |
| 401 | − | font.pixelSize: Theme.fontSize | |
| 401 | + | // ---- search pill ---- | |
| 402 | + | Rectangle { | |
| 403 | + | id: search | |
| 404 | + | x: 30 | |
| 405 | + | y: 30 | |
| 406 | + | width: parent.width - 60 | |
| 407 | + | height: 66 | |
| 408 | + | radius: 33 | |
| 409 | + | color: Theme.s2 | |
| 410 | + | ||
| 411 | + | Rectangle { // the dmenu prompt as a chip, else the lens / lock | |
| 412 | + | id: promptBox | |
| 413 | + | x: 12 | |
| 414 | + | anchors.verticalCenter: parent.verticalCenter | |
| 415 | + | visible: win.dmenu && win.dmPrompt !== "" | |
| 416 | + | width: visible ? promptText.implicitWidth + 28 : 0 | |
| 417 | + | height: 42 | |
| 418 | + | radius: 21 | |
| 419 | + | color: Theme.fill | |
| 420 | + | Lb { id: promptText; anchors.centerIn: parent; text: win.dmPrompt.replace(/[:>]\s*$/, ""); color: Theme.fillFg } | |
| 421 | + | } | |
| 422 | + | Icon { | |
| 423 | + | id: lens | |
| 424 | + | x: 24 | |
| 425 | + | anchors.verticalCenter: parent.verticalCenter | |
| 426 | + | visible: !promptBox.visible | |
| 427 | + | text: win.dmPassword ? Theme.iLock : Theme.iSearch | |
| 428 | + | color: Theme.grey | |
| 429 | + | font.pixelSize: 22 | |
| 430 | + | } | |
| 431 | + | TextInput { | |
| 432 | + | id: filter | |
| 433 | + | anchors.left: promptBox.visible ? promptBox.right : lens.right | |
| 434 | + | anchors.leftMargin: 16 | |
| 435 | + | anchors.right: chips.visible ? chips.left : parent.right | |
| 436 | + | anchors.rightMargin: 16 | |
| 437 | + | anchors.verticalCenter: parent.verticalCenter | |
| 438 | + | echoMode: win.dmPassword ? TextInput.Password : TextInput.Normal | |
| 439 | + | passwordCharacter: "•" | |
| 440 | + | color: Theme.fg | |
| 441 | + | selectionColor: Theme.fill | |
| 442 | + | selectedTextColor: Theme.fillFg | |
| 443 | + | font.family: win.dmPassword ? Theme.mono : Theme.ui | |
| 444 | + | font.pixelSize: 22 | |
| 445 | + | font.letterSpacing: win.dmPassword ? 3 : 0 | |
| 446 | + | clip: true | |
| 447 | + | cursorDelegate: Rectangle { | |
| 448 | + | width: 2 | |
| 449 | + | color: Theme.live | |
| 450 | + | visible: filter.activeFocus | |
| 451 | + | Timer { running: filter.activeFocus; interval: 500; repeat: true; onTriggered: parent.opacity = 1 - parent.opacity } | |
| 402 | 452 | } | |
| 403 | 453 | Text { | |
| 404 | − | anchors.left: filter.left | |
| 405 | − | anchors.verticalCenter: parent.verticalCenter | |
| 406 | − | visible: filter.text === "" && win.dmenu && win.dmPlaceholder !== "" | |
| 407 | − | text: win.dmPlaceholder | |
| 408 | − | color: Theme.dim | |
| 409 | − | font.family: Theme.font | |
| 410 | − | font.pixelSize: Theme.fontSize | |
| 454 | + | visible: filter.text === "" | |
| 455 | + | text: win.dmenu ? (win.dmPlaceholder !== "" ? win.dmPlaceholder : (win.dmPassword ? "Password" : "Type to filter")) | |
| 456 | + | : "Search apps, commands, =calc, >run" | |
| 457 | + | font.family: Theme.ui | |
| 458 | + | font.pixelSize: 22 | |
| 459 | + | color: Theme.mute | |
| 411 | 460 | } | |
| 412 | − | TextInput { | |
| 413 | − | id: filter | |
| 414 | − | anchors.fill: parent | |
| 415 | − | anchors.leftMargin: Math.max(32, promptText.implicitWidth + 20) | |
| 416 | − | anchors.rightMargin: 10 | |
| 417 | − | verticalAlignment: TextInput.AlignVCenter | |
| 418 | − | echoMode: win.dmPassword ? TextInput.Password : TextInput.Normal | |
| 419 | − | passwordCharacter: "*" | |
| 420 | − | color: Theme.fg | |
| 421 | − | selectionColor: Theme.accent | |
| 422 | − | selectedTextColor: Theme.bg | |
| 423 | − | font.family: Theme.font | |
| 424 | − | font.pixelSize: Theme.fontSize | |
| 425 | − | clip: true | |
| 426 | − | onTextChanged: { win.current = 0; win.armed = "" } | |
| 427 | − | Keys.onEscapePressed: win.closeRequested() | |
| 428 | − | Keys.onReturnPressed: event => win.trigger(win.current, event.modifiers & Qt.ControlModifier) | |
| 429 | − | Keys.onEnterPressed: event => win.trigger(win.current, event.modifiers & Qt.ControlModifier) | |
| 430 | − | Keys.onPressed: event => { | |
| 431 | − | var n = win.shown.length | |
| 432 | − | if (!n) return | |
| 433 | − | var step = 0 | |
| 434 | − | if (event.key === Qt.Key_Down || event.key === Qt.Key_Tab) step = 1 | |
| 435 | − | else if (event.key === Qt.Key_Up || event.key === Qt.Key_Backtab) step = -1 | |
| 436 | − | else if (event.key === Qt.Key_PageDown) step = Math.max(2, win.rowsShown) | |
| 437 | − | else if (event.key === Qt.Key_PageUp) step = -Math.max(2, win.rowsShown) | |
| 438 | − | else if ((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_N || event.key === Qt.Key_J)) step = 1 | |
| 439 | − | else if ((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_P || event.key === Qt.Key_K)) step = -1 | |
| 440 | − | if (step === 0) return | |
| 441 | − | if (Math.abs(step) === 1) win.current = (win.current + step + n) % n | |
| 442 | − | else win.current = Math.max(0, Math.min(n - 1, win.current + step)) | |
| 443 | − | win.armed = "" | |
| 444 | − | event.accepted = true | |
| 461 | + | onTextChanged: { win.current = 0; win.armed = "" } | |
| 462 | + | Keys.onEscapePressed: win.closeRequested() | |
| 463 | + | Keys.onReturnPressed: event => win.trigger(win.current, event.modifiers & Qt.ControlModifier) | |
| 464 | + | Keys.onEnterPressed: event => win.trigger(win.current, event.modifiers & Qt.ControlModifier) | |
| 465 | + | Keys.onPressed: event => { | |
| 466 | + | var n = win.shown.length | |
| 467 | + | if (!n) return | |
| 468 | + | var step = 0 | |
| 469 | + | if (event.key === Qt.Key_Down || event.key === Qt.Key_Tab) step = 1 | |
| 470 | + | else if (event.key === Qt.Key_Up || event.key === Qt.Key_Backtab) step = -1 | |
| 471 | + | else if (event.key === Qt.Key_PageDown) step = Math.max(2, win.rowsShown) | |
| 472 | + | else if (event.key === Qt.Key_PageUp) step = -Math.max(2, win.rowsShown) | |
| 473 | + | else if ((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_N || event.key === Qt.Key_J)) step = 1 | |
| 474 | + | else if ((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_P || event.key === Qt.Key_K)) step = -1 | |
| 475 | + | if (step === 0) return | |
| 476 | + | if (Math.abs(step) === 1) win.current = (win.current + step + n) % n | |
| 477 | + | else win.current = Math.max(0, Math.min(n - 1, win.current + step)) | |
| 478 | + | win.armed = "" | |
| 479 | + | event.accepted = true | |
| 480 | + | } | |
| 481 | + | } | |
| 482 | + | Row { | |
| 483 | + | id: chips | |
| 484 | + | visible: !win.dmenu | |
| 485 | + | anchors.right: parent.right | |
| 486 | + | anchors.rightMargin: 12 | |
| 487 | + | anchors.verticalCenter: parent.verticalCenter | |
| 488 | + | spacing: 6 | |
| 489 | + | Repeater { | |
| 490 | + | model: ["all", "run", "calc"] | |
| 491 | + | Chip { | |
| 492 | + | required property string modelData | |
| 493 | + | text: modelData | |
| 494 | + | selected: win.modeNow === modelData | |
| 495 | + | onClicked: win.setMode(modelData) | |
| 445 | 496 | } | |
| 446 | 497 | } | |
| 447 | 498 | } | |
| 499 | + | } | |
| 448 | 500 | ||
| 449 | − | ListView { | |
| 450 | − | id: list | |
| 451 | − | Layout.fillWidth: true | |
| 452 | − | Layout.fillHeight: true | |
| 453 | − | visible: win.rowsShown > 0 | |
| 454 | − | clip: true | |
| 455 | − | spacing: 0 | |
| 456 | − | boundsBehavior: Flickable.StopAtBounds | |
| 457 | − | model: win.shown | |
| 458 | − | currentIndex: win.current | |
| 459 | − | highlightFollowsCurrentItem: false | |
| 460 | − | onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain) | |
| 461 | − | delegate: Rectangle { | |
| 462 | − | id: row | |
| 463 | − | required property var modelData | |
| 464 | − | required property int index | |
| 465 | − | readonly property bool selected: win.current === index | |
| 466 | − | readonly property bool isArmed: win.armed !== "" && win.armed === modelData.key | |
| 467 | − | width: list.width | |
| 468 | − | height: win.rowH | |
| 469 | − | color: isArmed ? Theme.err : (selected ? Theme.surfaceHi : (rm.containsMouse ? Theme.surface : "transparent")) | |
| 470 | − | border.width: selected ? 1 : 0 | |
| 471 | − | border.color: isArmed ? Theme.err : Theme.accent | |
| 501 | + | ListView { | |
| 502 | + | id: list | |
| 503 | + | x: 30 | |
| 504 | + | anchors.top: search.bottom | |
| 505 | + | anchors.topMargin: 20 | |
| 506 | + | width: parent.width - 60 | |
| 507 | + | height: win.rowsShown * win.rowH | |
| 508 | + | visible: win.rowsShown > 0 | |
| 509 | + | clip: true | |
| 510 | + | spacing: 0 | |
| 511 | + | boundsBehavior: Flickable.StopAtBounds | |
| 512 | + | model: win.shown | |
| 513 | + | currentIndex: win.current | |
| 514 | + | highlightFollowsCurrentItem: false | |
| 515 | + | onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain) | |
| 516 | + | delegate: Item { | |
| 517 | + | id: row | |
| 518 | + | required property var modelData | |
| 519 | + | required property int index | |
| 520 | + | readonly property bool selected: win.current === index | |
| 521 | + | readonly property bool isArmed: win.armed !== "" && win.armed === modelData.key | |
| 522 | + | readonly property bool isDm: modelData.kind === "dm" | |
| 523 | + | readonly property color ink: isArmed ? Theme.errFg : selected ? Theme.fillFg : Theme.fg | |
| 524 | + | width: list.width | |
| 525 | + | height: win.rowH | |
| 472 | 526 | ||
| 473 | − | Rectangle { // selection marker, like ClipWindow | |
| 474 | − | visible: row.selected | |
| 475 | − | width: 2 | |
| 476 | − | height: parent.height | |
| 477 | − | color: row.isArmed ? Theme.bg : Theme.accent | |
| 478 | − | } | |
| 479 | − | Item { | |
| 480 | − | id: icoBox | |
| 481 | − | anchors.left: parent.left | |
| 482 | − | anchors.leftMargin: row.modelData.kind === "dm" ? 2 : 10 | |
| 483 | − | anchors.verticalCenter: parent.verticalCenter | |
| 484 | − | visible: row.modelData.kind !== "dm" | |
| 485 | − | width: visible ? 24 : 0 | |
| 486 | − | height: 24 | |
| 487 | − | Image { | |
| 488 | − | id: img | |
| 489 | − | anchors.fill: parent | |
| 490 | − | visible: row.modelData.icon !== "" && status === Image.Ready | |
| 491 | − | source: row.modelData.icon | |
| 492 | − | sourceSize.width: 48 | |
| 493 | − | sourceSize.height: 48 | |
| 494 | − | asynchronous: true | |
| 495 | − | smooth: true | |
| 496 | − | } | |
| 497 | − | Text { | |
| 498 | − | anchors.centerIn: parent | |
| 499 | − | visible: !img.visible | |
| 500 | − | text: row.modelData.glyph | |
| 501 | − | color: row.isArmed ? Theme.bg : Theme.accent | |
| 502 | − | font.family: Theme.font | |
| 503 | − | font.pixelSize: Theme.fontSize + 4 | |
| 504 | − | } | |
| 527 | + | Rectangle { | |
| 528 | + | anchors.fill: parent | |
| 529 | + | anchors.topMargin: 2 | |
| 530 | + | anchors.bottomMargin: 2 | |
| 531 | + | radius: height / 2 | |
| 532 | + | color: row.isArmed ? Theme.err : row.selected ? Theme.fill : rm.containsMouse ? Theme.s2 : "transparent" | |
| 533 | + | } | |
| 534 | + | Rectangle { | |
| 535 | + | id: icoBox | |
| 536 | + | x: 7 | |
| 537 | + | anchors.verticalCenter: parent.verticalCenter | |
| 538 | + | visible: !row.isDm | |
| 539 | + | width: visible ? 44 : 0 | |
| 540 | + | height: 44 | |
| 541 | + | radius: 22 | |
| 542 | + | color: row.selected || row.isArmed ? Theme.fade(row.ink, 0.14) : Theme.s2 | |
| 543 | + | Image { | |
| 544 | + | id: img | |
| 545 | + | anchors.centerIn: parent | |
| 546 | + | width: 26 | |
| 547 | + | height: 26 | |
| 548 | + | visible: row.modelData.icon !== "" && status === Image.Ready | |
| 549 | + | source: row.modelData.icon | |
| 550 | + | sourceSize: Qt.size(52, 52) | |
| 551 | + | asynchronous: true | |
| 552 | + | smooth: true | |
| 553 | + | layer.enabled: Settings.appIcons !== "colour" | |
| 554 | + | layer.effect: Settings.appIcons === "mono" ? monoFx : dotFx | |
| 555 | + | Component { id: dotFx; DotMask { step: 2 } } | |
| 556 | + | Component { id: monoFx; MultiEffect { saturation: -1 } } | |
| 505 | 557 | } | |
| 506 | − | Text { | |
| 507 | − | id: tag | |
| 508 | − | anchors.right: parent.right | |
| 509 | − | anchors.rightMargin: 10 | |
| 510 | − | anchors.verticalCenter: parent.verticalCenter | |
| 511 | − | text: row.isArmed ? "sure?" : row.modelData.tag | |
| 512 | − | color: row.isArmed ? Theme.bg : Theme.dim | |
| 513 | − | font.family: Theme.font | |
| 514 | − | font.pixelSize: Theme.fontSize - 2 | |
| 558 | + | Icon { | |
| 559 | + | anchors.centerIn: parent | |
| 560 | + | visible: !img.visible | |
| 561 | + | text: row.modelData.glyph | |
| 562 | + | color: row.ink | |
| 563 | + | font.pixelSize: 19 | |
| 515 | 564 | } | |
| 565 | + | } | |
| 566 | + | Lb { | |
| 567 | + | id: tag | |
| 568 | + | anchors.right: parent.right | |
| 569 | + | anchors.rightMargin: 22 | |
| 570 | + | anchors.verticalCenter: parent.verticalCenter | |
| 571 | + | px: 10 | |
| 572 | + | text: row.isArmed ? "↵ again · sure?" : row.modelData.tag | |
| 573 | + | color: row.isArmed || row.selected ? row.ink : Theme.grey | |
| 574 | + | } | |
| 575 | + | Column { | |
| 576 | + | anchors.left: icoBox.right | |
| 577 | + | anchors.leftMargin: row.isDm ? 22 : 14 | |
| 578 | + | anchors.right: tag.left | |
| 579 | + | anchors.rightMargin: 14 | |
| 580 | + | anchors.verticalCenter: parent.verticalCenter | |
| 516 | 581 | Text { | |
| 517 | − | id: nameText | |
| 518 | − | anchors.left: icoBox.right | |
| 519 | − | anchors.leftMargin: 10 | |
| 520 | − | anchors.verticalCenter: parent.verticalCenter | |
| 521 | − | width: Math.min(implicitWidth, tag.x - x - 12) | |
| 582 | + | width: parent.width | |
| 522 | 583 | elide: Text.ElideRight | |
| 523 | 584 | text: row.modelData.name | |
| 524 | − | color: row.isArmed ? Theme.bg : Theme.fg | |
| 525 | − | font.family: Theme.font | |
| 526 | − | font.pixelSize: Theme.fontSize | |
| 527 | − | font.bold: row.modelData.kind === "calc" | |
| 585 | + | color: row.ink | |
| 586 | + | font.family: row.modelData.kind === "calc" ? Theme.mono : Theme.ui | |
| 587 | + | font.pixelSize: row.modelData.kind === "calc" ? 20 : 15 | |
| 588 | + | font.weight: row.modelData.kind === "calc" ? Font.Bold : Font.DemiBold | |
| 528 | 589 | } | |
| 529 | − | Text { | |
| 530 | − | anchors.left: nameText.right | |
| 531 | − | anchors.leftMargin: 10 | |
| 532 | − | anchors.right: tag.left | |
| 533 | − | anchors.rightMargin: 12 | |
| 534 | − | anchors.verticalCenter: parent.verticalCenter | |
| 590 | + | Lb { | |
| 591 | + | visible: row.modelData.sub !== "" | |
| 592 | + | width: parent.width | |
| 535 | 593 | elide: Text.ElideRight | |
| 594 | + | px: 10 | |
| 536 | 595 | text: row.modelData.sub | |
| 537 | − | color: row.isArmed ? Theme.bg : Theme.dim | |
| 538 | − | font.family: Theme.font | |
| 539 | − | font.pixelSize: Theme.fontSize - 2 | |
| 540 | − | } | |
| 541 | − | MouseArea { | |
| 542 | − | id: rm | |
| 543 | − | anchors.fill: parent | |
| 544 | − | hoverEnabled: true | |
| 545 | − | cursorShape: Qt.PointingHandCursor | |
| 546 | − | onEntered: if (win.current !== row.index) { win.current = row.index; win.armed = "" } | |
| 547 | − | onClicked: win.trigger(row.index, false) | |
| 596 | + | color: row.selected || row.isArmed ? row.ink : Theme.grey | |
| 597 | + | opacity: row.selected ? 0.75 : 1 | |
| 548 | 598 | } | |
| 549 | 599 | } | |
| 600 | + | MouseArea { | |
| 601 | + | id: rm | |
| 602 | + | anchors.fill: parent | |
| 603 | + | hoverEnabled: true | |
| 604 | + | cursorShape: Qt.PointingHandCursor | |
| 605 | + | onEntered: if (win.current !== row.index) { win.current = row.index; win.armed = "" } | |
| 606 | + | onClicked: win.trigger(row.index, false) | |
| 607 | + | } | |
| 550 | 608 | } | |
| 609 | + | } | |
| 610 | + | } | |
| 551 | 611 | ||
| 552 | − | Text { | |
| 553 | − | Layout.fillWidth: true | |
| 554 | − | Layout.topMargin: 6 | |
| 555 | − | text: !win.dmenu ? (win.shown.length ? "enter run ctrl+enter terminal =calc >cmd esc close" : "no match") | |
| 556 | − | : win.rowsShown === 0 ? "enter ok esc cancel" | |
| 557 | − | : win.shown.length ? "enter select ctrl+enter typed text esc cancel" : "no match enter typed text esc cancel" | |
| 558 | − | color: win.shown.length || win.dmenu ? Theme.dim : Theme.err | |
| 559 | − | font.family: Theme.font | |
| 560 | − | font.pixelSize: Theme.fontSize - 2 | |
| 561 | − | } | |
| 612 | + | Row { | |
| 613 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 614 | + | anchors.top: card.bottom | |
| 615 | + | anchors.topMargin: 26 | |
| 616 | + | opacity: win.t | |
| 617 | + | spacing: 34 | |
| 618 | + | Repeater { | |
| 619 | + | model: !win.dmenu ? (win.shown.length ? ["↑↓ select", "↵ run", "ctrl ↵ terminal", "=calc", ">cmd", "esc close"] : ["no match", "esc close"]) | |
| 620 | + | : win.rowsShown === 0 ? ["↵ ok", "esc cancel"] | |
| 621 | + | : win.shown.length ? ["↑↓ select", "↵ choose", "ctrl ↵ typed text", "esc cancel"] : ["no match", "↵ typed text", "esc cancel"] | |
| 622 | + | Lb { required property string modelData; text: modelData } | |
| 562 | 623 | } | |
| 563 | 624 | } | |
| 564 | 625 | } | |
| @@ -17,7 +17,7 @@ Singleton { | |||
| 17 | 17 | function take(mode) { | |
| 18 | 18 | var script | |
| 19 | 19 | if (mode === "ocr") { | |
| 20 | − | script = 'slurp | grim -g - - | tesseract stdin stdout -l "$2" | wl-copy' | |
| 20 | + | script = 'slurp ' + Theme.slurpArgs.join(" ") + ' | grim -g - - | tesseract stdin stdout -l "$2" | wl-copy' | |
| 21 | 21 | + ' && notify-send -a screenshot "OCR" "text copied to clipboard"' | |
| 22 | 22 | } else if (names[mode] === undefined) { | |
| 23 | 23 | return | |
| @@ -90,6 +90,8 @@ Singleton { | |||
| 90 | 90 | ||
| 91 | 91 | function fade(c, a) { return Qt.rgba(c.r, c.g, c.b, a) } | |
| 92 | 92 | function hex(c) { return c.toString().slice(1, 7).toUpperCase() } | |
| 93 | + | // slurp in the theme: the screen dims, the selection gets a 1px primary outline (Rec, Shot's OCR) | |
| 94 | + | readonly property var slurpArgs: ["-b", "#" + hex(bg) + "88", "-c", "#" + hex(fill) + "ff", "-s", "#00000000", "-w", "1"] | |
| 93 | 95 | // hours for the 12/24h setting: "09" or "9" | |
| 94 | 96 | function hh(d) { | |
| 95 | 97 | var h = d.getHours() | |
| @@ -235,6 +237,9 @@ Singleton { | |||
| 235 | 237 | readonly property string iBolt: cp(0xF140B) // lightning-bolt (charging) | |
| 236 | 238 | readonly property string iDots: cp(0xF01D8) // dots-horizontal | |
| 237 | 239 | readonly property string iDashboard: cp(0xF056E) // view-dashboard (overview) | |
| 240 | + | readonly property string iText: cp(0xF0262) // format-align-left | |
| 241 | + | readonly property string iLink: cp(0xF0339) // link-variant | |
| 242 | + | readonly property string iCode: cp(0xF0169) // code-braces | |
| 238 | 243 | ||
| 239 | 244 | // workspace icons: the exact Font Awesome glyphs from your waybar config. | |
| 240 | 245 | // 1 browser, 2 code, 3 book, 4 cog, 5 chat, 6 file, 7 folder, 8 terminal, 9 flask, 10 image | |