#include "emojipanel.h" #include "emoji.h" #include "emojitabbar.h" #include "animtimer.h" #include "lottieicon.h" #include #include #include #include #include #include #include #include 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(); 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()) { m_hovered = true; m_frame = 0; m_timer.start(); } } void leaveEvent(QEvent *e) override { QPushButton::leaveEvent(e); m_hovered = false; m_timer.stop(); update(); } void paintEvent(QPaintEvent *e) override { if (m_hovered && 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; bool m_hovered = false; }; } // 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 &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(sender()); if (!btn) return; auto *eb = dynamic_cast(btn); emit emojiSelected(eb ? eb->unicode() : btn->text()); }