167 lines
5.4 KiB
C++
167 lines
5.4 KiB
C++
#include "emojipanel.h"
|
|
#include "emoji.h"
|
|
#include "emojitabbar.h"
|
|
#include "animtimer.h"
|
|
#include "lottieicon.h"
|
|
#include "debug_ui.h"
|
|
#include <QPushButton>
|
|
#include <QScrollArea>
|
|
#include <QTabWidget>
|
|
#include <QGridLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QVBoxLayout>
|
|
#include <QPainter>
|
|
#include <QTimer>
|
|
|
|
namespace {
|
|
|
|
class EmojiTabWidget : public QTabWidget {
|
|
public:
|
|
using QTabWidget::setTabBar;
|
|
};
|
|
|
|
class EmojiButton : public QPushButton {
|
|
public:
|
|
EmojiButton(const EmojiData &data, QWidget *parent = nullptr)
|
|
: QPushButton(data.unicode, parent)
|
|
, m_data(data)
|
|
{
|
|
setFixedSize(32, 32);
|
|
setCursor(Qt::PointingHandCursor);
|
|
setStyleSheet(
|
|
"QPushButton { border: none; border-radius: 4px; font-size: 18px; background: transparent; }"
|
|
"QPushButton:hover { background: rgba(0,0,0,0.08); }"
|
|
);
|
|
if (data.type == EmojiType::Animation) {
|
|
setMouseTracking(true);
|
|
m_icon = AnimTimer::instance()->icon(data.animPath);
|
|
m_timer.setInterval(33);
|
|
connect(&m_timer, &QTimer::timeout, this, [this]() {
|
|
if (m_icon && m_icon->isValid()) {
|
|
m_frame = (m_frame + 1) % m_icon->frameCount();
|
|
if (m_playback.elapsed() >= AnimTimer::PlaybackMs && m_frame == 0) {
|
|
GUI_DEBUG("EmojiButton: playback finished path=%s elapsed_ms=%lld frame=0",
|
|
qPrintable(m_data.animPath), (long long)m_playback.elapsed());
|
|
m_timer.stop();
|
|
m_playback.invalidate();
|
|
}
|
|
update();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
const QString &unicode() const { return m_data.unicode; }
|
|
|
|
protected:
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
void enterEvent(QEnterEvent *e) override {
|
|
#else
|
|
void enterEvent(QEvent *e) override {
|
|
#endif
|
|
QPushButton::enterEvent(e);
|
|
if (m_icon && m_icon->isValid()) {
|
|
GUI_DEBUG("EmojiButton: hover trigger path=%s running=%d frame=%d playback_ms=%d",
|
|
qPrintable(m_data.animPath), m_timer.isActive(), m_frame, AnimTimer::PlaybackMs);
|
|
if (!m_timer.isActive()) m_frame = 0;
|
|
m_playback.start();
|
|
m_timer.start();
|
|
update();
|
|
}
|
|
}
|
|
void paintEvent(QPaintEvent *e) override {
|
|
if (m_timer.isActive() && m_icon && m_icon->isValid()) {
|
|
QPainter p(this);
|
|
p.setRenderHint(QPainter::SmoothPixmapTransform);
|
|
int sz = qMin(width(), height()) - 4;
|
|
QImage frame = m_icon->renderFrame(m_frame, QSize(sz, sz));
|
|
int x = (width() - sz) / 2, y = (height() - sz) / 2;
|
|
p.drawImage(x, y, frame);
|
|
} else {
|
|
QPushButton::paintEvent(e);
|
|
}
|
|
}
|
|
|
|
private:
|
|
EmojiData m_data;
|
|
LottieIcon *m_icon = nullptr;
|
|
QTimer m_timer;
|
|
int m_frame = 0;
|
|
QElapsedTimer m_playback;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
EmojiPanel::EmojiPanel(QWidget *parent)
|
|
: QWidget(parent)
|
|
{
|
|
setStyleSheet("EmojiPanel { background: palette(window); }");
|
|
buildPanel(builtinEmojiSet());
|
|
hide();
|
|
}
|
|
|
|
void EmojiPanel::showEvent(QShowEvent *event) {
|
|
if (parentWidget()) {
|
|
setFixedHeight(qBound(200, parentWidget()->height() * 3 / 10, 800));
|
|
}
|
|
QWidget::showEvent(event);
|
|
}
|
|
|
|
void EmojiPanel::buildPanel(const QVector<EmojiCategory> &categories) {
|
|
auto *tabs = new EmojiTabWidget();
|
|
tabs->setDocumentMode(true);
|
|
tabs->setTabBar(new EmojiTabBar(tabs));
|
|
tabs->setStyleSheet("QTabWidget::pane { border: none; background: rgba(255,255,255,0.6); }");
|
|
|
|
for (const auto &cat : categories) {
|
|
tabs->addTab(buildCategoryPage(cat), cat.name);
|
|
}
|
|
|
|
auto *layout = new QVBoxLayout(this);
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
layout->setSpacing(0);
|
|
layout->addWidget(tabs);
|
|
}
|
|
|
|
QWidget *EmojiPanel::buildCategoryPage(const EmojiCategory &cat) {
|
|
auto *scroll = new QScrollArea();
|
|
scroll->setWidgetResizable(true);
|
|
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
scroll->setStyleSheet("QScrollArea { border: none; }");
|
|
|
|
auto *container = new QWidget();
|
|
auto *grid = new QGridLayout(container);
|
|
grid->setContentsMargins(4, 4, 4, 4);
|
|
grid->setSpacing(2);
|
|
|
|
int cols = 9;
|
|
for (int i = 0; i < cat.items.size(); ++i) {
|
|
const auto &e = cat.items[i];
|
|
QPushButton *btn;
|
|
if (e.type == EmojiType::Animation)
|
|
btn = new EmojiButton(e, container);
|
|
else {
|
|
btn = new QPushButton(e.unicode, container);
|
|
btn->setFixedSize(32, 32);
|
|
btn->setCursor(Qt::PointingHandCursor);
|
|
btn->setStyleSheet(
|
|
"QPushButton { border: none; border-radius: 4px; font-size: 18px; background: transparent; }"
|
|
"QPushButton:hover { background: rgba(0,0,0,0.08); }"
|
|
);
|
|
}
|
|
btn->setToolTip(e.shortcode);
|
|
connect(btn, &QPushButton::clicked, this, &EmojiPanel::onEmojiClicked);
|
|
grid->addWidget(btn, i / cols, i % cols);
|
|
}
|
|
grid->setRowStretch(grid->rowCount(), 1);
|
|
|
|
scroll->setWidget(container);
|
|
return scroll;
|
|
}
|
|
|
|
void EmojiPanel::onEmojiClicked() {
|
|
auto *btn = qobject_cast<QPushButton *>(sender());
|
|
if (!btn) return;
|
|
auto *eb = dynamic_cast<EmojiButton *>(btn);
|
|
emit emojiSelected(eb ? eb->unicode() : btn->text());
|
|
}
|