chatgui: rlottie animated emoji with hover-to-animate, drag-to-select

- Add LottieIcon class (TGS gzip decompress + rlottie C API render)
- Add AnimTimer singleton (33ms tick, activate/deactivate refcount)
- Add EmojiButton (hover-triggered animation in emoji picker)
- Add AnimatedEmoji support to EmojiData (5 TGS mappings)
- MessageDelegate: QTextLayout-based animated emoji overlay (text + reactions)
- ChatView: drag-to-select (click deselects, 5px drag starts selection)
- ChatView: hoveredIndexChanged signal for hover-triggered animations
- MessageList: wire AnimTimer tick to viewport update, hover → activate
- CMake: AUTORCC, rlottie, zlib dependencies
- Add 5 TGS animations as Qt resources
This commit is contained in:
Evgeny committed 2026-06-24 13:45:25 +03:00
1 parent 0e2b0e28d8
commit 62b3695a77
42 files changed
+286658 -73

No files matched your search

+116
View File
@@ -1,10 +1,16 @@
#include "chatview.h"
#include "messagedelegate.h"
#include <QPainter>
#include <QClipboard>
#include <QApplication>
#include <QDebug>
#include <QMouseEvent>
#include <QKeyEvent>
ChatView::ChatView(QWidget *parent)
: QListView(parent)
{
setMouseTracking(true);
}
void ChatView::setBackgroundImage(const QString &path) {
@@ -32,3 +38,113 @@ void ChatView::paintEvent(QPaintEvent *event) {
}
QListView::paintEvent(event);
}
bool ChatView::isRowSelected(const QModelIndex &idx) const {
if (!m_selStart.isValid() || !m_selEnd.isValid() || !idx.isValid())
return false;
int r = idx.row();
int a = m_selStart.row();
int b = m_selEnd.row();
if (a > b) qSwap(a, b);
return r >= a && r <= b;
}
void ChatView::mousePressEvent(QMouseEvent *event) {
if (event->button() != Qt::LeftButton) {
QListView::mousePressEvent(event);
return;
}
if (hasSelection()) {
m_selStart = QPersistentModelIndex();
m_selEnd = QPersistentModelIndex();
m_textSel = ChatTextSelection{};
m_selecting = false;
viewport()->update();
}
QModelIndex idx = indexAt(viewport()->mapFrom(this, event->pos()));
m_pressIndex = QPersistentModelIndex(idx);
m_pressPos = event->pos();
event->accept();
}
void ChatView::mouseMoveEvent(QMouseEvent *event) {
if (!m_selecting && m_pressIndex.isValid()) {
int dx = event->pos().x() - m_pressPos.x();
int dy = event->pos().y() - m_pressPos.y();
if (dx * dx + dy * dy >= 25) {
m_selStart = m_pressIndex;
m_selEnd = m_pressIndex;
m_selecting = true;
QRect r = visualRect(QModelIndex(m_pressIndex));
m_textSel.index = m_pressIndex;
m_textSel.clickPos = m_pressPos - r.topLeft();
m_textSel.dragPos = m_textSel.clickPos;
m_textSel.active = true;
viewport()->update();
}
}
if (!m_selecting) {
QModelIndex idx = indexAt(viewport()->mapFrom(this, event->pos()));
if (idx != QModelIndex(m_lastHovered)) {
m_lastHovered = idx;
emit hoveredIndexChanged(idx);
}
}
if (!m_selecting) {
QListView::mouseMoveEvent(event);
return;
}
QModelIndex idx = indexAt(viewport()->mapFrom(this, event->pos()));
if (idx.isValid()) {
m_selEnd = idx;
}
if (m_textSel.active) {
QRect r = visualRect(QModelIndex(m_textSel.index));
m_textSel.dragPos = event->pos() - r.topLeft();
}
viewport()->update();
event->accept();
}
void ChatView::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton && m_selecting) {
m_selecting = false;
viewport()->update();
}
m_pressIndex = QPersistentModelIndex();
QListView::mouseReleaseEvent(event);
}
void ChatView::keyPressEvent(QKeyEvent *event) {
if (event == QKeySequence::Copy) {
copySelection();
return;
}
QListView::keyPressEvent(event);
}
void ChatView::copySelection() {
if (!hasSelection()) return;
auto *m = model();
if (!m) return;
if (selStart() == selEnd()) {
QModelIndex idx(selStart());
QString text = idx.data(MsgTextRole).toString();
if (m_textSel.startChar >= 0 && m_textSel.endChar > m_textSel.startChar)
text = text.mid(m_textSel.startChar, m_textSel.endChar - m_textSel.startChar);
QApplication::clipboard()->setText(text);
} else {
QString r;
int a = selStart().row(), b = selEnd().row();
if (a > b) qSwap(a, b);
for (int i = a; i <= b; ++i) {
QModelIndex idx = m->index(i, 0);
if (!r.isEmpty()) r += '\n';
r += '[' + idx.data(MsgTimeRole).toString() + "] ";
r += idx.data(MsgAuthorRole).toString() + ": ";
r += idx.data(MsgTextRole).toString();
}
QApplication::clipboard()->setText(r);
}
}