From c1b7c026e488a40f1b8b87b12e80a649b2b8cf4f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 20 May 2023 00:31:47 +0200 Subject: [PATCH] Allow scrolling to specific sections and order packs in sticker search by match quality --- resources/qml/emoji/StickerPicker.qml | 72 ++++++++++++++++++--------- src/GridImagePackModel.cpp | 67 ++++++++++++++++++++++++- src/GridImagePackModel.h | 17 +++++++ 3 files changed, 130 insertions(+), 26 deletions(-) diff --git a/resources/qml/emoji/StickerPicker.qml b/resources/qml/emoji/StickerPicker.qml index ce4d5200..2e1956b1 100644 --- a/resources/qml/emoji/StickerPicker.qml +++ b/resources/qml/emoji/StickerPicker.qml @@ -24,6 +24,7 @@ Menu { readonly property int stickerDim: 128 readonly property int stickerDimPad: 128 + Nheko.paddingSmall readonly property int stickersPerRow: 3 + readonly property int sidebarAvatarSize: 24 function show(showAt, roomid_, callback) { console.debug("Showing sticker picker"); @@ -40,28 +41,31 @@ Menu { modal: true focus: true closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside - width: stickersPerRow * stickerDimPad + 20 + width: sidebarAvatarSize + Nheko.paddingSmall + stickersPerRow * stickerDimPad + 20 Rectangle { color: Nheko.colors.window height: columnView.implicitHeight + Nheko.paddingSmall*2 - width: stickersPerRow * stickerDimPad + 20 + width: sidebarAvatarSize + Nheko.paddingSmall + stickersPerRow * stickerDimPad + 20 - ColumnLayout { + GridLayout { id: columnView - spacing: Nheko.paddingSmall anchors.leftMargin: Nheko.paddingSmall anchors.rightMargin: Nheko.paddingSmall anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right + columns: 2 + rows: 2 // Search field TextField { id: emojiSearch Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall + Layout.row: 0 + Layout.column: 1 palette: Nheko.colors background: null placeholderTextColor: Nheko.colors.buttonText @@ -102,9 +106,23 @@ Menu { } } - Component { - id: sectionHeading - Rectangle { + // sticker grid + ListView { + id: gridView + + model: roomid ? TimelineManager.completerFor("stickergrid", roomid) : null + Layout.row: 1 + Layout.column: 1 + Layout.preferredHeight: cellHeight * 3.5 + Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall + property int cellHeight: stickerDimPad + boundsBehavior: Flickable.StopAtBounds + clip: true + currentIndex: -1 // prevent sorting from stealing focus + + section.property: "packname" + section.criteria: ViewSection.FullString + section.delegate: Rectangle { width: gridView.width height: childrenRect.height color: Nheko.colors.alternateBase @@ -119,23 +137,6 @@ Menu { font.bold: true } } - } - - // sticker grid - ListView { - id: gridView - - model: roomid ? TimelineManager.completerFor("stickergrid", roomid) : null - Layout.preferredHeight: cellHeight * 3.5 - Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - Nheko.paddingSmall - property int cellHeight: stickerDimPad - boundsBehavior: Flickable.StopAtBounds - clip: true - currentIndex: -1 // prevent sorting from stealing focus - - section.property: "packname" - section.criteria: ViewSection.FullString - section.delegate: sectionHeading section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart spacing: Nheko.paddingSmall @@ -191,6 +192,29 @@ Menu { } + ListView { + Layout.row: 1 + Layout.column: 0 + Layout.preferredWidth: sidebarAvatarSize + Layout.fillHeight: true + Layout.rightMargin: Nheko.paddingSmall + + model: gridView.model ? gridView.model.sections : null + spacing: Nheko.paddingSmall + + delegate: Avatar { + height: sidebarAvatarSize + width: sidebarAvatarSize + url: modelData.url.replace("mxc://", "image://MxcImage/") + displayName: modelData.name + roomid: modelData.name + + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: modelData.name + onClicked: gridView.positionViewAtIndex(modelData.firstRowWith, ListView.Beginning) + } + } } } diff --git a/src/GridImagePackModel.cpp b/src/GridImagePackModel.cpp index 59b1725a..5db8c0cc 100644 --- a/src/GridImagePackModel.cpp +++ b/src/GridImagePackModel.cpp @@ -12,12 +12,16 @@ #include "Cache_p.h" Q_DECLARE_METATYPE(StickerImage) +Q_DECLARE_METATYPE(SectionDescription) +Q_DECLARE_METATYPE(QList) GridImagePackModel::GridImagePackModel(const std::string &roomId, bool stickers, QObject *parent) : QAbstractListModel(parent) , room_id(roomId) { - [[maybe_unused]] static auto id = qRegisterMetaType(); + [[maybe_unused]] static auto id = qRegisterMetaType(); + [[maybe_unused]] static auto id2 = qRegisterMetaType(); + [[maybe_unused]] static auto id3 = qRegisterMetaType>(); auto originalPacks = cache::client()->getImagePacks(room_id, stickers); @@ -182,6 +186,58 @@ GridImagePackModel::nameFromPack(const PackDesc &pack) const return tr("Account Pack"); } +QString +GridImagePackModel::avatarFromPack(const PackDesc &pack) const +{ + if (!pack.packavatar.isEmpty()) { + return pack.packavatar; + } + + if (!pack.images.empty()) { + return QString::fromStdString(pack.images.begin()->first.url); + } + + return ""; +} + +QList +GridImagePackModel::sections() const +{ + QList sectionNames; + if (searchString_.isEmpty()) { + std::size_t packIdx = -1; + for (std::size_t i = 0; i < rowToPack.size(); i++) { + if (rowToPack[i] != packIdx) { + const auto &pack = packs[rowToPack[i]]; + sectionNames.push_back({ + .name = nameFromPack(pack), + .url = avatarFromPack(pack), + .firstRowWith = static_cast(i), + }); + packIdx = rowToPack[i]; + } + } + } else { + std::uint32_t packIdx = -1; + int row = 0; + for (const auto &i : rowToFirstRowEntryFromSearch) { + const auto res = currentSearchResult[i]; + if (res.first != packIdx) { + packIdx = res.first; + const auto &pack = packs[packIdx]; + sectionNames.push_back({ + .name = nameFromPack(pack), + .url = avatarFromPack(pack), + .firstRowWith = row, + }); + } + row++; + } + } + + return sectionNames; +} + void GridImagePackModel::setSearchString(QString key) { @@ -194,7 +250,14 @@ GridImagePackModel::setSearchString(QString key) auto searchParts = key.toCaseFolded().toUcs4(); auto tempResults = trie_.search(searchParts, static_cast(columns * columns * 4)); - std::ranges::sort(tempResults); + + std::map firstPositionOfPack; + for (const auto &e : tempResults) + firstPositionOfPack.emplace(e.first, firstPositionOfPack.size()); + + std::ranges::stable_sort(tempResults, [&firstPositionOfPack](auto a, auto b) { + return firstPositionOfPack[a.first] < firstPositionOfPack[b.first]; + }); currentSearchResult = std::move(tempResults); std::size_t lastPack = -1; diff --git a/src/GridImagePackModel.h b/src/GridImagePackModel.h index 8da61b8e..c6be3346 100644 --- a/src/GridImagePackModel.h +++ b/src/GridImagePackModel.h @@ -41,10 +41,24 @@ public: std::vector descriptor_; // roomid, statekey, shortcode }; +struct SectionDescription +{ + Q_GADGET + Q_PROPERTY(QString url MEMBER url CONSTANT) + Q_PROPERTY(QString name MEMBER name CONSTANT) + Q_PROPERTY(int firstRowWith MEMBER firstRowWith CONSTANT) + +public: + QString name; + QString url; + int firstRowWith = 0; +}; + class GridImagePackModel final : public QAbstractListModel { Q_OBJECT Q_PROPERTY(QString searchString READ searchString WRITE setSearchString NOTIFY newSearchString) + Q_PROPERTY(QList sections READ sections NOTIFY newSearchString) public: enum Roles @@ -61,6 +75,8 @@ public: QString searchString() const { return searchString_; } void setSearchString(QString newValue); + QList sections() const; + signals: void newSearchString(); @@ -87,4 +103,5 @@ private: std::vector rowToFirstRowEntryFromSearch; QString nameFromPack(const PackDesc &pack) const; + QString avatarFromPack(const PackDesc &pack) const; };