diff options
| author | littleblack111 <littleblack11111@gmail.com> | 2025-08-18 00:30:35 +0800 |
|---|---|---|
| committer | littleblack111 <littleblack11111@gmail.com> | 2025-08-19 20:15:21 +0800 |
| commit | 1edfa9b00d61e963148444c79726bc7eb89364db (patch) | |
| tree | 16058bebf74b59de7120f3b4a63cc8ba08ec247a /launcher | |
| parent | 947a9a41c204e0e2aa877e80abf765301ca3c757 (diff) | |
feat(Emoji): add emoji component and service for emoji selection
Diffstat (limited to 'launcher')
| -rw-r--r-- | launcher/components/Emoji.qml | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/launcher/components/Emoji.qml b/launcher/components/Emoji.qml new file mode 100644 index 0000000..57be01a --- /dev/null +++ b/launcher/components/Emoji.qml @@ -0,0 +1,176 @@ +import Quickshell +import Quickshell.Io +import QtQuick +import QtQuick.Layouts + +import qs.components +import qs.services +import qs.config + +IComponent { + id: root + + property var emojis: Emoji.query(input) + property int selectedIndex: -1 + + property int cols: Math.max(1, Math.floor(innerLoader.width / Math.max(1, Math.round(Launcher.widgetFontSize * 4)))) + property int cellWidth: Math.floor(innerLoader.width / cols) + property int cellHeight: Math.ceil(Launcher.widgetFontSize * 2) + Launcher.innerMargin * 2 + + name: "Emoji" + prefix: name.toLowerCase() + " " + + onEmojisChanged: { + Qt.callLater(() => { + selectedIndex = emojis.length > 0 ? 0 : -1; + syncSelectionState(); + if (loader.item && loader.item.flickable) + loader.item.flickable.contentY = 0; + }); + } + + onSelectedIndexChanged: { + if (!loader.view || !loader.item || !loader.item.flickable) + return; + if (selectedIndex < 0 || selectedIndex >= loader.view.count) + return; + Qt.callLater(() => { + syncSelectionState(); + loader.view.currentIndex = selectedIndex; + loader.view.positionViewAtIndex(selectedIndex, GridView.Contain); + }); + } + + process: function () { + const isValid = selectedIndex >= 0 && emojis.length > 0; + const select = isValid ? emojis[selectedIndex] : null; + return { + valid: isValid, + priority: isValid, + answer: isValid ? select.emoji : "" + }; + } + + home: function () { + if (!loader.view.count) + return true; + selectedIndex = 0; + } + end: function () { + if (!loader.view.count) + return true; + selectedIndex = loader.view.count - 1; + } + prev: function () { + if (selectedIndex <= 0) + return true; + selectedIndex--; + } + next: function () { + if (selectedIndex + 1 >= emojis.length) + return true; + selectedIndex++; + } + up: function () { + if (selectedIndex - cols < 0) + return true; + selectedIndex -= cols; + } + down: function () { + if (selectedIndex + cols >= emojis.length) + return true; + selectedIndex += cols; + } + + exec: function () { + if (selectedIndex >= 0 && emojis.length > 0) + Emoji.copy(emojis[selectedIndex].emoji); + } + + syncSelectionState: function () { + if (!loader.view) + return; + Qt.callLater(() => state.selected = loader.view.currentItem); + } + + IInnerComponent { + id: innerLoader + + Loader { + id: loader + Layout.fillWidth: true + Layout.fillHeight: true + Layout.preferredHeight: parent.height + sourceComponent: show + property var view: null + + readonly property Component show: Component { + Item { + property alias flickable: grid + + GridView { + id: grid + anchors.fill: parent + clip: true + boundsBehavior: Flickable.StopAtBounds + cacheBuffer: cellHeight * 2 + model: emojis + interactive: true + flow: GridView.FlowLeftToRight + snapMode: GridView.NoSnap + + cellWidth: root.cellWidth + cellHeight: root.cellHeight + + delegate: Item { + required property var modelData + required property int index + width: grid.cellWidth + height: chipContent.implicitHeight + Launcher.innerMargin * 2 + scale: index === selectedIndex ? 1.03 : 0.97 + + IRect { + anchors.fill: parent + color: Qt.rgba(Colors.background2.r, Colors.background2.g, Colors.background2.b, Launcher.widgetBgTransparency) + radius: Launcher.widgetRadius + + Column { + id: chipContent + anchors.centerIn: parent + spacing: 2 + + Icon { + anchors.horizontalCenter: parent.horizontalCenter + text: modelData.emoji + font.pixelSize: Launcher.widgetFontSize * 1.2 + } + IText { + anchors.horizontalCenter: parent.horizontalCenter + text: modelData.name + font.pixelSize: Launcher.widgetFontSize * 0.7 + color: index === selectedIndex ? Colors.foreground1 : Colors.foreground2 + elide: Text.ElideRight + width: parent.parent.width - Launcher.innerMargin * 2 + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onPositionChanged: root.selectedIndex = index + onPressed: root._exec() + } + } + + Behavior on scale { + ISpringAnimation {} + } + } + + Component.onCompleted: loader.view = grid + } + } + } + } + } +} |
