summaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
authorlittleblack111 <littleblack11111@gmail.com>2025-08-18 00:30:35 +0800
committerlittleblack111 <littleblack11111@gmail.com>2025-08-19 20:15:21 +0800
commit1edfa9b00d61e963148444c79726bc7eb89364db (patch)
tree16058bebf74b59de7120f3b4a63cc8ba08ec247a /launcher
parent947a9a41c204e0e2aa877e80abf765301ca3c757 (diff)
feat(Emoji): add emoji component and service for emoji selection
Diffstat (limited to 'launcher')
-rw-r--r--launcher/components/Emoji.qml176
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
+ }
+ }
+ }
+ }
+ }
+}