| 1 | import QtQuick |
| 2 | import QtQuick.Layouts |
| 3 | import Quickshell |
| 4 | import Quickshell.Wayland |
| 5 | import Quickshell.Hyprland |
| 6 | |
| 7 | // Glyph picker (SUPER+grave, `chars` IPC): emoji, nerd font glyphs, unicode symbols, kaomoji, fancy text. |
| 8 | // Type to fuzzy search; arrows / Ctrl+HJKL move, Tab section, Ctrl+Left/Right group, Enter copies, Shift+Enter types it |
| 9 | // into the focused window, Ctrl+Enter collects into a buffer, Alt+Enter copies the codepoint. |
| 10 | PanelWindow { |
| 11 | id: win |
| 12 | property bool opened: false |
| 13 | signal closeRequested() |
| 14 | |
| 15 | visible: opened |
| 16 | implicitWidth: 780 |
| 17 | implicitHeight: 590 |
| 18 | exclusiveZone: 0 |
| 19 | color: "transparent" |
| 20 | WlrLayershell.layer: WlrLayer.Overlay |
| 21 | WlrLayershell.namespace: "aurora-chars" |
| 22 | WlrLayershell.keyboardFocus: opened ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None |
| 23 | |
| 24 | HyprlandFocusGrab { |
| 25 | windows: [win] |
| 26 | active: win.opened |
| 27 | onCleared: win.closeRequested() |
| 28 | } |
| 29 | |
| 30 | readonly property var sections: [ |
| 31 | { id: "all", label: "all", icon: Theme.iGrid, sets: ["emoji", "nerd", "sym"] }, |
| 32 | { id: "recent", label: "recent", icon: Theme.iHistory, sets: ["emoji", "nerd", "sym"] }, |
| 33 | { id: "emoji", label: "emoji", icon: Theme.iEmoji, sets: ["emoji"] }, |
| 34 | { id: "nerd", label: "nerd", icon: Theme.iFontGlyph, sets: ["nerd"] }, |
| 35 | { id: "sym", label: "symbols", icon: Theme.iSigma, sets: ["sym"] }, |
| 36 | { id: "kao", label: "kaomoji", icon: Theme.iKaomoji, sets: ["kao"] }, |
| 37 | { id: "fancy", label: "fancy", icon: Theme.iFancy, sets: [] } |
| 38 | ] |
| 39 | property int sec: 0 |
| 40 | property int grp: -1 |
| 41 | property string buffer: "" |
| 42 | property var results: [] |
| 43 | property var hovered: null |
| 44 | readonly property var section: sections[sec] |
| 45 | readonly property bool listMode: section.id === "kao" || section.id === "fancy" |
| 46 | readonly property int tone: Settings.charTone |
| 47 | // groups of the shown set (single-set sections only) |
| 48 | readonly property var groups: { |
| 49 | if (!Chars.loaded || section.sets.length !== 1) return [] |
| 50 | var i = Chars.setIndex(section.sets[0]) |
| 51 | return i >= 0 ? Chars.sets[i].groups : [] |
| 52 | } |
| 53 | readonly property var view: listMode ? list : grid |
| 54 | readonly property var current: hovered ? hovered : (results.length && view.currentIndex >= 0 ? results[view.currentIndex] : null) |
| 55 | |
| 56 | onOpenedChanged: { |
| 57 | if (!opened) return |
| 58 | hovered = null |
| 59 | buffer = "" |
| 60 | filter.text = "" |
| 61 | grp = -1 |
| 62 | Chars.load() |
| 63 | sec = Chars.loaded && Chars.recent(1).length ? 1 : 0 |
| 64 | recompute() |
| 65 | filter.forceActiveFocus() |
| 66 | } |
| 67 | onSecChanged: { grp = -1; recompute() } |
| 68 | onGrpChanged: recompute() |
| 69 | Connections { |
| 70 | target: Chars |
| 71 | function onLoadedChanged() { |
| 72 | if (win.opened && Chars.recent(1).length && filter.text === "") win.sec = 1 |
| 73 | win.recompute() |
| 74 | } |
| 75 | } |
| 76 | Timer { id: debounce; interval: 40; onTriggered: win.recompute() } |
| 77 | |
| 78 | function setList(id) { |
| 79 | var out = [] |
| 80 | for (var i = 0; i < section.sets.length; i++) out.push(Chars.setIndex(section.sets[i])) |
| 81 | return out |
| 82 | } |
| 83 | function recompute() { |
| 84 | hovered = null |
| 85 | var q = filter.text |
| 86 | if (section.id === "fancy") results = Chars.fancy(q.trim() === "" ? "aurora" : q) |
| 87 | else if (!Chars.loaded) results = [] |
| 88 | else if (section.id === "recent") { |
| 89 | if (q.trim() === "") results = Chars.recent(300) |
| 90 | else { |
| 91 | var l = Chars.search(q, setList(), -1), out = [] |
| 92 | for (var i = 0; i < l.length; i++) if (Chars.counts[l[i].c]) out.push(l[i]) |
| 93 | results = out |
| 94 | } |
| 95 | } else if (section.id === "kao") { |
| 96 | var k = Chars.search(q, setList(), grp) |
| 97 | results = q.trim() === "" ? Chars.byUse(k) : k |
| 98 | } else results = Chars.search(q, setList(), grp) |
| 99 | grid.currentIndex = 0 |
| 100 | list.currentIndex = 0 |
| 101 | grid.positionViewAtBeginning() |
| 102 | list.positionViewAtBeginning() |
| 103 | } |
| 104 | function cycleSection(d) { sec = (sec + d + sections.length) % sections.length } |
| 105 | function cycleGroup(d) { |
| 106 | if (!groups.length) return |
| 107 | var n = groups.length + 1 // -1 = all |
| 108 | grp = ((grp + 1 + d + n) % n) - 1 |
| 109 | } |
| 110 | function outOf(it) { return section.id === "fancy" ? it.c : Chars.glyph(it, tone) } |
| 111 | function codeOf(s) { |
| 112 | var cps = Array.from(s) |
| 113 | if (cps.length === 1) return "0x" + cps[0].codePointAt(0).toString(16).toUpperCase() |
| 114 | return Chars.hex(s) |
| 115 | } |
| 116 | // how: copy | type | buffer | code |
| 117 | function pick(it, how) { |
| 118 | if (!it && buffer === "") return |
| 119 | var s = it ? outOf(it) : "" |
| 120 | if (it && section.id !== "fancy" && (how === "buffer" || buffer === "")) Chars.bump(it.c) |
| 121 | if (how === "buffer") { buffer += s; return } |
| 122 | if (how === "code") { Chars.copy(codeOf(s)); closeRequested(); return } |
| 123 | if (buffer !== "") s = buffer |
| 124 | closeRequested() |
| 125 | if (how === "type" && Chars.hasWtype) Chars.type(s) |
| 126 | else Chars.copy(s) |
| 127 | } |
| 128 | function move(key) { |
| 129 | var v = view |
| 130 | if (listMode) { |
| 131 | if (key === Qt.Key_Up) v.decrementCurrentIndex() |
| 132 | else if (key === Qt.Key_Down) v.incrementCurrentIndex() |
| 133 | return |
| 134 | } |
| 135 | if (key === Qt.Key_Up) v.moveCurrentIndexUp() |
| 136 | else if (key === Qt.Key_Down) v.moveCurrentIndexDown() |
| 137 | else if (key === Qt.Key_Left) v.moveCurrentIndexLeft() |
| 138 | else if (key === Qt.Key_Right) v.moveCurrentIndexRight() |
| 139 | } |
| 140 | function page(d) { |
| 141 | var step = listMode ? 10 : grid.cols * Math.max(1, Math.floor(grid.height / grid.cellHeight)) |
| 142 | view.currentIndex = Math.max(0, Math.min(results.length - 1, view.currentIndex + d * step)) |
| 143 | } |
| 144 | function fontOf(it) { |
| 145 | return it && it.s !== undefined && Chars.sets[it.s] && Chars.sets[it.s].id === "emoji" ? "Noto Color Emoji" : Theme.font |
| 146 | } |
| 147 | |
| 148 | Rectangle { |
| 149 | anchors.fill: parent |
| 150 | color: Theme.bgA |
| 151 | border.width: 2 |
| 152 | border.color: Theme.accent |
| 153 | |
| 154 | ColumnLayout { |
| 155 | anchors.fill: parent |
| 156 | anchors.margins: 12 |
| 157 | spacing: 8 |
| 158 | |
| 159 | // ---- search + tone + close ---- |
| 160 | RowLayout { |
| 161 | Layout.fillWidth: true |
| 162 | spacing: 6 |
| 163 | Rectangle { |
| 164 | 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 |
| 172 | anchors.verticalCenter: parent.verticalCenter |
| 173 | text: Theme.iSearch |
| 174 | color: Theme.dim |
| 175 | font.family: Theme.font |
| 176 | font.pixelSize: Theme.fontSize |
| 177 | } |
| 178 | Text { |
| 179 | anchors.left: parent.left |
| 180 | anchors.leftMargin: 32 |
| 181 | anchors.verticalCenter: parent.verticalCenter |
| 182 | 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 |
| 187 | } |
| 188 | TextInput { |
| 189 | id: filter |
| 190 | anchors.fill: parent |
| 191 | anchors.leftMargin: 32 |
| 192 | anchors.rightMargin: 10 |
| 193 | verticalAlignment: TextInput.AlignVCenter |
| 194 | color: Theme.fg |
| 195 | selectionColor: Theme.accent |
| 196 | selectedTextColor: Theme.bg |
| 197 | font.family: Theme.font |
| 198 | font.pixelSize: Theme.fontSize |
| 199 | clip: true |
| 200 | onTextChanged: { |
| 201 | // recent is the landing page: typing there searches everything (used ones rank first anyway) |
| 202 | if (text !== "" && win.section.id === "recent") win.sec = 0 |
| 203 | if (text === "") win.recompute(); else debounce.restart() |
| 204 | } |
| 205 | Keys.onEscapePressed: win.closeRequested() |
| 206 | Keys.onPressed: event => { |
| 207 | var k = event.key, m = event.modifiers |
| 208 | // ctrl+h/j/k/l move through the results like the arrows |
| 209 | var vim = { [Qt.Key_H]: Qt.Key_Left, [Qt.Key_J]: Qt.Key_Down, [Qt.Key_K]: Qt.Key_Up, [Qt.Key_L]: Qt.Key_Right } |
| 210 | if ((m & Qt.ControlModifier) && vim[k] !== undefined) { |
| 211 | win.hovered = null |
| 212 | win.move(vim[k]) |
| 213 | event.accepted = true |
| 214 | return |
| 215 | } |
| 216 | if (k === Qt.Key_Return || k === Qt.Key_Enter) { |
| 217 | if (debounce.running) { debounce.stop(); win.recompute() } |
| 218 | var it = win.results[win.view.currentIndex] |
| 219 | if (m & Qt.ControlModifier) win.pick(it, "buffer") |
| 220 | else if (m & Qt.ShiftModifier) win.pick(it, "type") |
| 221 | else if (m & Qt.AltModifier) win.pick(it, "code") |
| 222 | else win.pick(it, "copy") |
| 223 | } else if (k === Qt.Key_Tab) win.cycleSection(1) |
| 224 | else if (k === Qt.Key_Backtab) win.cycleSection(-1) |
| 225 | else if ((m & Qt.ControlModifier) && (k === Qt.Key_Left || k === Qt.Key_Right)) win.cycleGroup(k === Qt.Key_Left ? -1 : 1) |
| 226 | else if ((m & Qt.AltModifier) && k >= Qt.Key_1 && k < Qt.Key_1 + win.sections.length) win.sec = k - Qt.Key_1 |
| 227 | else if (k === Qt.Key_Up || k === Qt.Key_Down || (!win.listMode && (k === Qt.Key_Left || k === Qt.Key_Right))) { |
| 228 | win.hovered = null |
| 229 | win.move(k) |
| 230 | } else if (k === Qt.Key_PageDown) win.page(1) |
| 231 | else if (k === Qt.Key_PageUp) win.page(-1) |
| 232 | else if (k === Qt.Key_Backspace && filter.text === "" && win.buffer !== "") |
| 233 | win.buffer = Array.from(win.buffer).slice(0, -1).join("") |
| 234 | else return |
| 235 | event.accepted = true |
| 236 | } |
| 237 | } |
| 238 | } |
| 239 | Text { |
| 240 | text: win.results.length |
| 241 | color: Theme.dim |
| 242 | font.family: Theme.font |
| 243 | font.pixelSize: Theme.fontSize - 1 |
| 244 | } |
| 245 | // skin tone: the raised hand in each tone, click to choose (persisted) |
| 246 | Row { |
| 247 | visible: Chars.loaded && (win.section.id === "emoji" || win.section.id === "all" || win.section.id === "recent") |
| 248 | spacing: 0 |
| 249 | Repeater { |
| 250 | model: 6 |
| 251 | delegate: Rectangle { |
| 252 | required property int index |
| 253 | 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 |
| 259 | Text { |
| 260 | anchors.centerIn: parent |
| 261 | text: parent.hand ? Chars.glyph(parent.hand, parent.index) : "" |
| 262 | font.family: "Noto Color Emoji" |
| 263 | font.pixelSize: 15 |
| 264 | } |
| 265 | MouseArea { |
| 266 | id: tm |
| 267 | anchors.fill: parent |
| 268 | hoverEnabled: true |
| 269 | cursorShape: Qt.PointingHandCursor |
| 270 | onClicked: { Settings.charTone = parent.index; filter.forceActiveFocus() } |
| 271 | } |
| 272 | } |
| 273 | } |
| 274 | } |
| 275 | IconBtn { icon: Theme.iClose; onClicked: win.closeRequested() } |
| 276 | } |
| 277 | |
| 278 | // ---- sections ---- |
| 279 | RowLayout { |
| 280 | Layout.fillWidth: true |
| 281 | spacing: 2 |
| 282 | Repeater { |
| 283 | model: win.sections |
| 284 | delegate: Rectangle { |
| 285 | id: tab |
| 286 | required property var modelData |
| 287 | 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 | } |
| 325 | } |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | // ---- groups of the section ---- |
| 330 | ListView { |
| 331 | id: chips |
| 332 | Layout.fillWidth: true |
| 333 | implicitHeight: 22 |
| 334 | visible: win.groups.length > 0 |
| 335 | orientation: ListView.Horizontal |
| 336 | spacing: 4 |
| 337 | clip: true |
| 338 | boundsBehavior: Flickable.StopAtBounds |
| 339 | model: win.groups.length ? ["all"].concat(win.groups) : [] |
| 340 | currentIndex: win.grp + 1 |
| 341 | onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain) |
| 342 | delegate: Rectangle { |
| 343 | id: chip |
| 344 | required property var modelData |
| 345 | 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 | } |
| 367 | } |
| 368 | } |
| 369 | |
| 370 | // ---- results ---- |
| 371 | Item { |
| 372 | Layout.fillWidth: true |
| 373 | Layout.fillHeight: true |
| 374 | |
| 375 | GridView { |
| 376 | id: grid |
| 377 | anchors.fill: parent |
| 378 | visible: !win.listMode |
| 379 | readonly property int cols: Math.max(1, Math.floor(width / 50)) |
| 380 | cellWidth: Math.floor(width / cols) |
| 381 | cellHeight: 50 |
| 382 | clip: true |
| 383 | model: win.listMode ? 0 : win.results.length |
| 384 | boundsBehavior: Flickable.StopAtBounds |
| 385 | highlightMoveDuration: 0 |
| 386 | keyNavigationWraps: false |
| 387 | cacheBuffer: 400 |
| 388 | delegate: Rectangle { |
| 389 | id: cell |
| 390 | required property int index |
| 391 | readonly property var it: win.results[index] |
| 392 | 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 |
| 398 | Text { |
| 399 | anchors.centerIn: parent |
| 400 | text: cell.it ? Chars.glyph(cell.it, win.tone) : "" |
| 401 | color: Theme.fg |
| 402 | font.family: win.fontOf(cell.it) |
| 403 | font.pixelSize: 26 |
| 404 | } |
| 405 | MouseArea { |
| 406 | id: gm |
| 407 | anchors.fill: parent |
| 408 | hoverEnabled: true |
| 409 | acceptedButtons: Qt.LeftButton | Qt.RightButton |
| 410 | cursorShape: Qt.PointingHandCursor |
| 411 | onContainsMouseChanged: win.hovered = containsMouse ? cell.it : null |
| 412 | onClicked: mouse => { |
| 413 | grid.currentIndex = cell.index |
| 414 | win.pick(cell.it, mouse.button === Qt.RightButton ? "buffer" : "copy") |
| 415 | } |
| 416 | } |
| 417 | } |
| 418 | } |
| 419 | |
| 420 | ListView { |
| 421 | id: list |
| 422 | anchors.fill: parent |
| 423 | visible: win.listMode |
| 424 | clip: true |
| 425 | model: win.listMode ? win.results.length : 0 |
| 426 | spacing: 2 |
| 427 | boundsBehavior: Flickable.StopAtBounds |
| 428 | highlightMoveDuration: 0 |
| 429 | delegate: Rectangle { |
| 430 | id: row |
| 431 | required property int index |
| 432 | readonly property var it: win.results[index] |
| 433 | readonly property bool selected: list.currentIndex === index |
| 434 | 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 | } |
| 445 | Text { |
| 446 | anchors.left: parent.left |
| 447 | anchors.leftMargin: 12 |
| 448 | anchors.right: tag.left |
| 449 | anchors.rightMargin: 12 |
| 450 | anchors.verticalCenter: parent.verticalCenter |
| 451 | text: row.it ? row.it.c : "" |
| 452 | elide: Text.ElideRight |
| 453 | maximumLineCount: 1 |
| 454 | color: Theme.fg |
| 455 | font.family: Theme.font |
| 456 | font.pixelSize: Theme.fontSize + 4 |
| 457 | } |
| 458 | Text { |
| 459 | id: tag |
| 460 | anchors.right: parent.right |
| 461 | anchors.rightMargin: 12 |
| 462 | anchors.verticalCenter: parent.verticalCenter |
| 463 | 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 |
| 467 | } |
| 468 | MouseArea { |
| 469 | id: lm |
| 470 | anchors.fill: parent |
| 471 | hoverEnabled: true |
| 472 | acceptedButtons: Qt.LeftButton | Qt.RightButton |
| 473 | cursorShape: Qt.PointingHandCursor |
| 474 | onContainsMouseChanged: win.hovered = containsMouse ? row.it : null |
| 475 | onClicked: mouse => { |
| 476 | list.currentIndex = row.index |
| 477 | win.pick(row.it, mouse.button === Qt.RightButton ? "buffer" : "copy") |
| 478 | } |
| 479 | } |
| 480 | } |
| 481 | } |
| 482 | |
| 483 | Text { |
| 484 | anchors.centerIn: parent |
| 485 | visible: win.results.length === 0 |
| 486 | text: !Chars.loaded && win.section.id !== "fancy" ? "loading glyphs..." |
| 487 | : (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 | } |
| 492 | } |
| 493 | |
| 494 | // ---- preview of the selected / hovered glyph ---- |
| 495 | Rectangle { |
| 496 | Layout.fillWidth: true |
| 497 | implicitHeight: 62 |
| 498 | color: Theme.surface |
| 499 | border.width: 1 |
| 500 | border.color: Theme.line |
| 501 | |
| 502 | Rectangle { // big glyph |
| 503 | id: big |
| 504 | 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 |
| 512 | Text { |
| 513 | anchors.centerIn: parent |
| 514 | text: win.current ? Chars.glyph(win.current, win.tone) : "" |
| 515 | color: Theme.accent |
| 516 | font.family: win.fontOf(win.current) |
| 517 | font.pixelSize: 38 |
| 518 | } |
| 519 | } |
| 520 | Column { |
| 521 | anchors.left: big.right |
| 522 | anchors.leftMargin: 12 |
| 523 | anchors.right: buf.left |
| 524 | anchors.rightMargin: 12 |
| 525 | anchors.verticalCenter: parent.verticalCenter |
| 526 | spacing: 3 |
| 527 | Text { |
| 528 | width: parent.width |
| 529 | text: win.current ? win.current.n : "" |
| 530 | elide: Text.ElideRight |
| 531 | color: Theme.fg |
| 532 | font.family: Theme.font |
| 533 | font.pixelSize: Theme.fontSize + 1 |
| 534 | font.bold: true |
| 535 | } |
| 536 | Text { |
| 537 | width: parent.width |
| 538 | visible: win.current !== null && win.section.id !== "fancy" |
| 539 | text: { |
| 540 | var c = win.current |
| 541 | if (!c || c.s === undefined || !Chars.sets[c.s]) return "" |
| 542 | 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() |
| 545 | } |
| 546 | elide: Text.ElideRight |
| 547 | color: Theme.accent |
| 548 | font.family: Theme.font |
| 549 | font.pixelSize: Theme.fontSize - 1 |
| 550 | } |
| 551 | Text { |
| 552 | width: parent.width |
| 553 | visible: text !== "" |
| 554 | text: win.current && win.current.k && win.section.id !== "kao" ? win.current.k : "" |
| 555 | elide: Text.ElideRight |
| 556 | color: Theme.dim |
| 557 | font.family: Theme.font |
| 558 | font.pixelSize: Theme.fontSize - 2 |
| 559 | } |
| 560 | } |
| 561 | Row { // collected with ctrl+enter / right click |
| 562 | id: buf |
| 563 | anchors.right: parent.right |
| 564 | anchors.rightMargin: 12 |
| 565 | anchors.verticalCenter: parent.verticalCenter |
| 566 | spacing: 8 |
| 567 | visible: win.buffer !== "" |
| 568 | width: visible ? implicitWidth : 0 |
| 569 | Text { |
| 570 | anchors.verticalCenter: parent.verticalCenter |
| 571 | text: Theme.iBuffer |
| 572 | color: Theme.accent |
| 573 | font.family: Theme.font |
| 574 | font.pixelSize: Theme.fontSize + 2 |
| 575 | } |
| 576 | Text { |
| 577 | anchors.verticalCenter: parent.verticalCenter |
| 578 | width: Math.min(implicitWidth, 220) |
| 579 | text: win.buffer |
| 580 | elide: Text.ElideLeft |
| 581 | color: Theme.fg |
| 582 | font.family: Theme.font |
| 583 | font.pixelSize: Theme.fontSize + 4 |
| 584 | } |
| 585 | } |
| 586 | } |
| 587 | |
| 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 | } |
| 597 | } |
| 598 | } |
| 599 | } |