Improve emojis & reactions
This commit is contained in:
@@ -11,6 +11,10 @@ struct CustomEmoji {
|
|||||||
QString name; // with :semicolons:
|
QString name; // with :semicolons:
|
||||||
QString url; // mxc://
|
QString url; // mxc://
|
||||||
QRegularExpression regexp;
|
QRegularExpression regexp;
|
||||||
|
|
||||||
|
Q_GADGET
|
||||||
|
Q_PROPERTY(QString unicode MEMBER url)
|
||||||
|
Q_PROPERTY(QString name MEMBER name)
|
||||||
};
|
};
|
||||||
|
|
||||||
class CustomEmojiModel : public QAbstractListModel
|
class CustomEmojiModel : public QAbstractListModel
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "customemojimodel.h"
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
#include <qnamespace.h>
|
|
||||||
|
|
||||||
EmojiModel::EmojiModel(QObject *parent)
|
EmojiModel::EmojiModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
@@ -68,6 +68,13 @@ QVariantList EmojiModel::history() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
QVariantList EmojiModel::filterModel(const QString &filter, bool limit)
|
QVariantList EmojiModel::filterModel(const QString &filter, bool limit)
|
||||||
|
{
|
||||||
|
auto emojis = CustomEmojiModel::instance().filterModel(filter);
|
||||||
|
emojis += filterModelNoCustom(filter, limit);
|
||||||
|
return emojis;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList EmojiModel::filterModelNoCustom(const QString &filter, bool limit)
|
||||||
{
|
{
|
||||||
QVariantList result;
|
QVariantList result;
|
||||||
|
|
||||||
@@ -82,7 +89,6 @@ QVariantList EmojiModel::filterModel(const QString &filter, bool limit)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,11 +116,27 @@ QVariantList EmojiModel::emojis(Category category) const
|
|||||||
if (category == History) {
|
if (category == History) {
|
||||||
return history();
|
return history();
|
||||||
}
|
}
|
||||||
|
if (category == HistoryNoCustom) {
|
||||||
|
QVariantList list;
|
||||||
|
for (const auto &e : history()) {
|
||||||
|
auto emoji = qvariant_cast<Emoji>(e);
|
||||||
|
if (!emoji.isCustom) {
|
||||||
|
list.append(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
if (category == Custom) {
|
||||||
|
return CustomEmojiModel::instance().filterModel({});
|
||||||
|
}
|
||||||
return _emojis[category];
|
return _emojis[category];
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantList EmojiModel::tones(const QString &baseEmoji) const
|
QVariantList EmojiModel::tones(const QString &baseEmoji) const
|
||||||
{
|
{
|
||||||
|
if (baseEmoji.endsWith("tone")) {
|
||||||
|
return _tones.values(baseEmoji.split(":")[0]);
|
||||||
|
}
|
||||||
return _tones.values(baseEmoji);
|
return _tones.values(baseEmoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,21 +145,11 @@ QHash<EmojiModel::Category, QVariantList> EmojiModel::_emojis;
|
|||||||
QVariantList EmojiModel::categories() const
|
QVariantList EmojiModel::categories() const
|
||||||
{
|
{
|
||||||
return QVariantList{
|
return QVariantList{
|
||||||
// {QVariantMap{
|
|
||||||
// {"category", EmojiModel::Search},
|
|
||||||
// {"name", i18nc("Search for emojis", "Search")},
|
|
||||||
// {"emoji", QStringLiteral("🔎")},
|
|
||||||
// }},
|
|
||||||
{QVariantMap{
|
{QVariantMap{
|
||||||
{"category", EmojiModel::History},
|
{"category", EmojiModel::HistoryNoCustom},
|
||||||
{"name", i18nc("Previously used emojis", "History")},
|
{"name", i18nc("Previously used emojis", "History")},
|
||||||
{"emoji", QStringLiteral("⌛️")},
|
{"emoji", QStringLiteral("⌛️")},
|
||||||
}},
|
}},
|
||||||
{QVariantMap{
|
|
||||||
{"category", EmojiModel::Custom},
|
|
||||||
{"name", i18nc("'Custom' is a category of emoji", "Custom")},
|
|
||||||
{"emoji", QStringLiteral("😏")},
|
|
||||||
}},
|
|
||||||
{QVariantMap{
|
{QVariantMap{
|
||||||
{"category", EmojiModel::Smileys},
|
{"category", EmojiModel::Smileys},
|
||||||
{"name", i18nc("'Smileys' is a category of emoji", "Smileys")},
|
{"name", i18nc("'Smileys' is a category of emoji", "Smileys")},
|
||||||
@@ -185,3 +197,23 @@ QVariantList EmojiModel::categories() const
|
|||||||
}},
|
}},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantList EmojiModel::categoriesWithCustom() const
|
||||||
|
{
|
||||||
|
auto cats = categories();
|
||||||
|
cats.removeAt(0);
|
||||||
|
cats.insert(0,
|
||||||
|
QVariantMap{
|
||||||
|
{"category", EmojiModel::History},
|
||||||
|
{"name", i18nc("Previously used emojis", "History")},
|
||||||
|
{"emoji", QStringLiteral("⌛️")},
|
||||||
|
});
|
||||||
|
cats.insert(1,
|
||||||
|
QVariantMap{
|
||||||
|
{"category", EmojiModel::Custom},
|
||||||
|
{"name", i18nc("'Custom' is a category of emoji", "Custom")},
|
||||||
|
{"emoji", QStringLiteral("🖼️")},
|
||||||
|
});
|
||||||
|
;
|
||||||
|
return cats;
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class EmojiModel : public QAbstractListModel
|
|||||||
|
|
||||||
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
|
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
|
||||||
Q_PROPERTY(QVariantList categories READ categories CONSTANT)
|
Q_PROPERTY(QVariantList categories READ categories CONSTANT)
|
||||||
|
Q_PROPERTY(QVariantList categoriesWithCustom READ categoriesWithCustom CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static EmojiModel &instance()
|
static EmojiModel &instance()
|
||||||
@@ -69,7 +70,9 @@ public:
|
|||||||
enum Category {
|
enum Category {
|
||||||
Custom,
|
Custom,
|
||||||
Search,
|
Search,
|
||||||
|
SearchNoCustom,
|
||||||
History,
|
History,
|
||||||
|
HistoryNoCustom,
|
||||||
Smileys,
|
Smileys,
|
||||||
People,
|
People,
|
||||||
Nature,
|
Nature,
|
||||||
@@ -89,11 +92,14 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE QVariantList history() const;
|
Q_INVOKABLE QVariantList history() const;
|
||||||
Q_INVOKABLE static QVariantList filterModel(const QString &filter, bool limit = true);
|
Q_INVOKABLE static QVariantList filterModel(const QString &filter, bool limit = true);
|
||||||
|
Q_INVOKABLE static QVariantList filterModelNoCustom(const QString &filter, bool limit = true);
|
||||||
|
|
||||||
Q_INVOKABLE QVariantList emojis(Category category) const;
|
Q_INVOKABLE QVariantList emojis(Category category) const;
|
||||||
|
|
||||||
Q_INVOKABLE QVariantList tones(const QString &baseEmoji) const;
|
Q_INVOKABLE QVariantList tones(const QString &baseEmoji) const;
|
||||||
|
|
||||||
QVariantList categories() const;
|
QVariantList categories() const;
|
||||||
|
QVariantList categoriesWithCustom() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void historyChanged();
|
void historyChanged();
|
||||||
|
|||||||
@@ -57,15 +57,21 @@ ColumnLayout {
|
|||||||
id: emojiPickerLoader
|
id: emojiPickerLoader
|
||||||
active: visible
|
active: visible
|
||||||
visible: chatBar.emojiPaneOpened
|
visible: chatBar.emojiPaneOpened
|
||||||
|
onItemChanged: if (visible) {
|
||||||
|
emojiPickerLoader.item.forceActiveFocus()
|
||||||
|
}
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
sourceComponent: QQC2.Pane {
|
sourceComponent: QQC2.Pane {
|
||||||
|
onActiveFocusChanged: if(activeFocus) {
|
||||||
|
emojiPicker.forceActiveFocus()
|
||||||
|
}
|
||||||
topPadding: 0
|
topPadding: 0
|
||||||
bottomPadding: 0
|
bottomPadding: 0
|
||||||
rightPadding: 0
|
rightPadding: 0
|
||||||
leftPadding: 0
|
leftPadding: 0
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
contentItem: EmojiPicker {
|
contentItem: EmojiPicker {
|
||||||
textArea: chatBar.textField
|
id: emojiPicker
|
||||||
onChosen: insertText(emoji)
|
onChosen: insertText(emoji)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
53
src/qml/Component/Emoji/EmojiDelegate.qml
Normal file
53
src/qml/Component/Emoji/EmojiDelegate.qml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
|
||||||
|
QQC2.ItemDelegate {
|
||||||
|
id: emojiDelegate
|
||||||
|
|
||||||
|
property string name
|
||||||
|
property string emoji
|
||||||
|
property bool showTones: false
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: emojiDelegate.name
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
Kirigami.Heading {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: !emojiDelegate.emoji.startsWith("image")
|
||||||
|
text: emojiDelegate.emoji
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font.family: "emoji"
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
width: Kirigami.Units.gridUnit * 0.5
|
||||||
|
height: Kirigami.Units.gridUnit * 0.5
|
||||||
|
source: "arrow-down"
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
visible: emojiDelegate.showTones
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: emojiDelegate.emoji.startsWith("image")
|
||||||
|
source: visible ? emojiDelegate.emoji : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: emojiDelegate.checked ? Kirigami.Theme.highlightColor : Kirigami.Theme.backgroundColor
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Kirigami.Theme.highlightColor
|
||||||
|
opacity: emojiDelegate.hovered && !emojiDelegate.pressed ? 0.2 : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
src/qml/Component/Emoji/EmojiGrid.qml
Normal file
87
src/qml/Component/Emoji/EmojiGrid.qml
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Tobias Fella
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
QQC2.ScrollView {
|
||||||
|
id: emojiGrid
|
||||||
|
|
||||||
|
property alias model: emojis.model
|
||||||
|
property alias count: emojis.count
|
||||||
|
required property int targetIconSize
|
||||||
|
readonly property int emojisPerRow: emojis.width / targetIconSize
|
||||||
|
required property bool withCustom
|
||||||
|
readonly property var searchCategory: withCustom ? EmojiModel.Search : EmojiModel.SearchNoCustom
|
||||||
|
required property QtObject header
|
||||||
|
|
||||||
|
signal chosen(string unicode)
|
||||||
|
|
||||||
|
onActiveFocusChanged: if (activeFocus) {
|
||||||
|
emojis.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
GridView {
|
||||||
|
id: emojis
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: parent.QQC2.ScrollBar.vertical.visible ? parent.QQC2.ScrollBar.vertical.width : 0
|
||||||
|
|
||||||
|
currentIndex: -1
|
||||||
|
keyNavigationEnabled: true
|
||||||
|
onActiveFocusChanged: if (activeFocus && currentIndex === -1) {
|
||||||
|
currentIndex = 0
|
||||||
|
} else {
|
||||||
|
currentIndex = -1
|
||||||
|
}
|
||||||
|
onModelChanged: currentIndex = -1
|
||||||
|
|
||||||
|
cellWidth: emojis.width / emojiGrid.emojisPerRow
|
||||||
|
cellHeight: emojiGrid.targetIconSize
|
||||||
|
|
||||||
|
KeyNavigation.up: emojiGrid.header
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
delegate: EmojiDelegate {
|
||||||
|
id: emojiDelegate
|
||||||
|
checked: emojis.currentIndex === model.index
|
||||||
|
emoji: modelData.unicode
|
||||||
|
name: modelData.shortName
|
||||||
|
|
||||||
|
width: emojis.cellWidth
|
||||||
|
height: emojis.cellHeight
|
||||||
|
|
||||||
|
Keys.onEnterPressed: clicked()
|
||||||
|
Keys.onReturnPressed: clicked()
|
||||||
|
onClicked: {
|
||||||
|
emojiGrid.chosen(modelData.isCustom ? modelData.shortName : modelData.unicode)
|
||||||
|
EmojiModel.emojiUsed(modelData)
|
||||||
|
}
|
||||||
|
Keys.onSpacePressed: pressAndHold()
|
||||||
|
onPressAndHold: {
|
||||||
|
if (EmojiModel.tones(modelData.shortName).length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let tones = tonesPopupComponent.createObject(emojiDelegate, {shortName: modelData.shortName, unicode: modelData.unicode, categoryIconSize: emojiGrid.targetIconSize})
|
||||||
|
tones.open()
|
||||||
|
tones.forceActiveFocus()
|
||||||
|
}
|
||||||
|
showTones: EmojiModel.tones(modelData.shortName).length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.PlaceholderMessage {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: i18n("No emojis")
|
||||||
|
visible: emojis.count === 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: tonesPopupComponent
|
||||||
|
EmojiTonesPicker {
|
||||||
|
onChosen: emojiGrid.chosen(emoji)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,63 +1,54 @@
|
|||||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15 as QQC2
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: _picker
|
id: emojiPicker
|
||||||
|
|
||||||
property var emojiCategory: EmojiModel.History
|
readonly property int categoryIconSize: 45
|
||||||
property var textArea
|
readonly property var currentCategory: EmojiModel.categoriesWithCustom[categories.currentIndex].category
|
||||||
readonly property var emojiModel: EmojiModel
|
readonly property int categoryCount: categories.count
|
||||||
|
|
||||||
signal chosen(string emoji)
|
signal chosen(string emoji)
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
|
onActiveFocusChanged: if (activeFocus) categories.forceActiveFocus()
|
||||||
|
|
||||||
QQC2.ScrollView {
|
QQC2.ScrollView {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2 + QQC2.ScrollBar.horizontal.height + 2 // for the focus line
|
|
||||||
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
|
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
|
||||||
|
Layout.preferredHeight: emojiPicker.categoryIconSize + QQC2.ScrollBar.horizontal.height
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
|
id: categories
|
||||||
clip: true
|
clip: true
|
||||||
orientation: ListView.Horizontal
|
orientation: ListView.Horizontal
|
||||||
|
|
||||||
model: EmojiModel.categories
|
keyNavigationEnabled: true
|
||||||
delegate: QQC2.ItemDelegate {
|
keyNavigationWraps: true
|
||||||
id: del
|
Keys.forwardTo: searchField
|
||||||
|
interactive: width !== contentWidth
|
||||||
|
|
||||||
width: contentItem.Layout.preferredWidth
|
model: EmojiModel.categoriesWithCustom
|
||||||
height: Kirigami.Units.gridUnit * 2
|
delegate: EmojiDelegate {
|
||||||
|
id: emojiDelegate
|
||||||
|
|
||||||
contentItem: Kirigami.Heading {
|
width: emojiPicker.categoryIconSize
|
||||||
horizontalAlignment: Text.AlignHCenter
|
height: width
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
level: modelData.category === EmojiModel.Custom ? 4 : 1
|
|
||||||
|
|
||||||
Layout.preferredWidth: modelData.category === EmojiModel.Custom ? implicitWidth + Kirigami.Units.largeSpacing : Kirigami.Units.gridUnit * 2
|
checked: categories.currentIndex === model.index
|
||||||
|
emoji: modelData.emoji
|
||||||
|
name: modelData.name
|
||||||
|
|
||||||
font.family: modelData.category === EmojiModel.Custom ? Kirigami.Theme.defaultFont.family : 'emoji'
|
onClicked: {
|
||||||
text: modelData.category === EmojiModel.Custom ? i18n("Custom") : modelData.emoji
|
categories.currentIndex = index
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: 2
|
|
||||||
|
|
||||||
visible: _picker.emojiCategory === modelData.category
|
|
||||||
|
|
||||||
color: Kirigami.Theme.focusColor
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: _picker.emojiCategory = modelData.category
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,57 +58,22 @@ ColumnLayout {
|
|||||||
Layout.preferredHeight: 1
|
Layout.preferredHeight: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.ScrollView {
|
Kirigami.SearchField {
|
||||||
|
id: searchField
|
||||||
|
Layout.margins: Kirigami.Units.smallSpacing
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 8
|
}
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
GridView {
|
EmojiGrid {
|
||||||
cellWidth: Kirigami.Units.gridUnit * 2
|
id: emojiGrid
|
||||||
cellHeight: Kirigami.Units.gridUnit * 2
|
targetIconSize: emojiPicker.categoryIconSize
|
||||||
|
model: searchField.text.length === 0 ? EmojiModel.emojis(emojiPicker.currentCategory) : EmojiModel.filterModel(searchField.text, false)
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 350
|
||||||
|
onChosen: emojiPicker.chosen(unicode)
|
||||||
|
withCustom: true
|
||||||
|
header: categories
|
||||||
|
|
||||||
clip: true
|
Keys.forwardTo: searchField
|
||||||
|
|
||||||
model: _picker.emojiCategory === EmojiModel.Custom ? CustomEmojiModel : EmojiModel.emojis(_picker.emojiCategory)
|
|
||||||
|
|
||||||
delegate: QQC2.ItemDelegate {
|
|
||||||
width: Kirigami.Units.gridUnit * 2
|
|
||||||
height: Kirigami.Units.gridUnit * 2
|
|
||||||
|
|
||||||
contentItem: Kirigami.Heading {
|
|
||||||
level: 1
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
font.family: 'emoji'
|
|
||||||
text: modelData.isCustom ? "" : modelData.unicode
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
|
||||||
visible: modelData.isCustom
|
|
||||||
source: visible ? modelData.unicode : ""
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 2
|
|
||||||
|
|
||||||
sourceSize.width: width
|
|
||||||
sourceSize.height: height
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: parent.status === Image.Loading
|
|
||||||
radius: height/2
|
|
||||||
gradient: ShimmerGradient { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (modelData.isCustom) {
|
|
||||||
chosen(modelData.shortName)
|
|
||||||
} else {
|
|
||||||
chosen(modelData.unicode)
|
|
||||||
}
|
|
||||||
emojiModel.emojiUsed(modelData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
src/qml/Component/Emoji/EmojiTonesPicker.qml
Normal file
66
src/qml/Component/Emoji/EmojiTonesPicker.qml
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
QQC2.Popup {
|
||||||
|
id: tones
|
||||||
|
|
||||||
|
signal chosen(string emoji)
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
tonesList.currentIndex = 0;
|
||||||
|
tonesList.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
required property string shortName
|
||||||
|
required property string unicode
|
||||||
|
required property int categoryIconSize
|
||||||
|
width: tones.categoryIconSize * tonesList.count + 2 * padding
|
||||||
|
height: tones.categoryIconSize + 2 * padding
|
||||||
|
y: -height
|
||||||
|
padding: 2
|
||||||
|
modal: true
|
||||||
|
dim: true
|
||||||
|
onOpened: x = Math.min(parent.mapFromGlobal(QQC2.Overlay.overlay.width - tones.width, 0).x, -(width - parent.width) / 2)
|
||||||
|
background: Kirigami.ShadowedRectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
radius: Kirigami.Units.smallSpacing
|
||||||
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
|
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
||||||
|
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: tonesList
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
orientation: Qt.Horizontal
|
||||||
|
model: EmojiModel.tones(tones.shortName)
|
||||||
|
keyNavigationEnabled: true
|
||||||
|
keyNavigationWraps: true
|
||||||
|
|
||||||
|
delegate: EmojiDelegate {
|
||||||
|
id: emojiDelegate
|
||||||
|
checked: tonesList.currentIndex === model.index
|
||||||
|
emoji: modelData.unicode
|
||||||
|
name: modelData.shortName
|
||||||
|
|
||||||
|
width: tones.categoryIconSize
|
||||||
|
height: width
|
||||||
|
|
||||||
|
Keys.onEnterPressed: clicked()
|
||||||
|
Keys.onReturnPressed: clicked()
|
||||||
|
onClicked: {
|
||||||
|
tones.chosen(modelData.unicode)
|
||||||
|
EmojiModel.emojiUsed(modelData)
|
||||||
|
tones.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
src/qml/Component/Emoji/ReactionPicker.qml
Normal file
86
src/qml/Component/Emoji/ReactionPicker.qml
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: reactionPicker
|
||||||
|
height: 400
|
||||||
|
|
||||||
|
readonly property int categoryIconSize: 45
|
||||||
|
readonly property var currentCategory: EmojiModel.categories[categories.currentIndex].category
|
||||||
|
readonly property alias categoryCount: categories.count
|
||||||
|
|
||||||
|
signal chosen(string emoji)
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
QQC2.ScrollView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: reactionPicker.categoryIconSize + QQC2.ScrollBar.horizontal.height
|
||||||
|
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: categories
|
||||||
|
|
||||||
|
keyNavigationEnabled: true
|
||||||
|
focus: true
|
||||||
|
height: reactionPicker.categoryIconSize
|
||||||
|
Keys.onReturnPressed: if (emojiGrid.count > 0) emojiGrid.focus = true
|
||||||
|
Keys.onEnterPressed: if (emojiGrid.count > 0) emojiGrid.focus = true
|
||||||
|
currentIndex: 2
|
||||||
|
keyNavigationWraps: true
|
||||||
|
Keys.forwardTo: searchField
|
||||||
|
interactive: width !== contentWidth
|
||||||
|
|
||||||
|
model: EmojiModel.categories
|
||||||
|
Component.onCompleted: categories.forceActiveFocus()
|
||||||
|
|
||||||
|
delegate: EmojiDelegate {
|
||||||
|
checked: categories.currentIndex === model.index
|
||||||
|
emoji: modelData.emoji
|
||||||
|
name: modelData.name
|
||||||
|
|
||||||
|
height: reactionPicker.categoryIconSize
|
||||||
|
width: height
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
categories.currentIndex = index
|
||||||
|
categories.focus = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
orientation: Qt.Horizontal
|
||||||
|
KeyNavigation.down: emojiGrid.count > 0 ? emojiGrid : categories
|
||||||
|
KeyNavigation.tab: emojiGrid.count > 0 ? emojiGrid : categories
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.SearchField {
|
||||||
|
id: searchField
|
||||||
|
|
||||||
|
Layout.margins: Kirigami.Units.smallSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
EmojiGrid {
|
||||||
|
id: emojiGrid
|
||||||
|
targetIconSize: reactionPicker.categoryIconSize
|
||||||
|
model: searchField.text.length === 0 ? EmojiModel.emojis(reactionPicker.currentCategory) : EmojiModel.filterModelNoCustom(searchField.text, false)
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
withCustom: false
|
||||||
|
onChosen: reactionPicker.chosen(unicode)
|
||||||
|
header: categories
|
||||||
|
Keys.forwardTo: searchField
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,29 +9,38 @@ import org.kde.kirigami 2.15 as Kirigami
|
|||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
QQC2.Popup {
|
QQC2.Popup {
|
||||||
id: root
|
id: emojiPopup
|
||||||
|
|
||||||
signal react(string emoji)
|
signal react(string emoji)
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: RoomManager
|
||||||
|
function onCurrentRoomChanged() {
|
||||||
|
emojiPopup.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Kirigami.ShadowedRectangle {
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
radius: Kirigami.Units.smallSpacing
|
||||||
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
|
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
||||||
|
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
|
border.width: 2
|
||||||
|
}
|
||||||
|
|
||||||
modal: true
|
modal: true
|
||||||
focus: true
|
focus: true
|
||||||
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent
|
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent
|
||||||
margins: 0
|
margins: 0
|
||||||
padding: 1
|
padding: 2
|
||||||
implicitWidth: Kirigami.Units.gridUnit * 16
|
|
||||||
implicitHeight: Kirigami.Units.gridUnit * 20
|
|
||||||
|
|
||||||
background: Rectangle {
|
implicitHeight: Kirigami.Units.gridUnit * 20 + 2 * padding
|
||||||
Kirigami.Theme.inherit: false
|
width: Math.min(contentItem.categoryIconSize * contentItem.categoryCount + 2 * padding, QQC2.Overlay.overlay.width)
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
contentItem: ReactionPicker {
|
||||||
color: Kirigami.Theme.backgroundColor
|
onChosen: {
|
||||||
border.width: 1
|
react(emoji)
|
||||||
border.color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor,
|
emojiPopup.close()
|
||||||
Kirigami.Theme.textColor,
|
}
|
||||||
0.15)
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: EmojiPicker {
|
|
||||||
onChosen: react(emoji)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,5 +93,9 @@
|
|||||||
<file alias="ConfirmEncryptionDialog.qml">qml/Dialog/ConfirmEncryptionDialog.qml</file>
|
<file alias="ConfirmEncryptionDialog.qml">qml/Dialog/ConfirmEncryptionDialog.qml</file>
|
||||||
<file alias="RemoveSheet.qml">qml/Menu/Timeline/RemoveSheet.qml</file>
|
<file alias="RemoveSheet.qml">qml/Menu/Timeline/RemoveSheet.qml</file>
|
||||||
<file alias="BanSheet.qml">qml/Menu/Timeline/BanSheet.qml</file>
|
<file alias="BanSheet.qml">qml/Menu/Timeline/BanSheet.qml</file>
|
||||||
|
<file alias="ReactionPicker.qml">qml/Component/Emoji/ReactionPicker.qml</file>
|
||||||
|
<file alias="EmojiTonesPicker.qml">qml/Component/Emoji/EmojiTonesPicker.qml</file>
|
||||||
|
<file alias="EmojiDelegate.qml">qml/Component/Emoji/EmojiDelegate.qml</file>
|
||||||
|
<file alias="EmojiGrid.qml">qml/Component/Emoji/EmojiGrid.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
Reference in New Issue
Block a user