/* This file is part of Telegram Desktop, the official desktop version of Telegram messaging app, see https://telegram.org Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "stickers/emoji_list_widget.h" #include "ui/widgets/buttons.h" #include "styles/style_stickers.h" #include "ui/widgets/shadow.h" #include "lang.h" namespace ChatHelpers { namespace { constexpr auto kEmojiPanelPerRow = Ui::Emoji::kPanelPerRow; constexpr auto kEmojiPanelRowsPerPage = Ui::Emoji::kPanelRowsPerPage; constexpr auto kSaveRecentEmojiTimeout = 3000; } // namespace class EmojiListWidget::Footer : public EmojiPanel::InnerFooter { public: Footer(gsl::not_null parent); void setCurrentSectionIcon(Section section); protected: void processPanelHideFinished() override; private: void prepareSection(int &left, int top, int _width, Ui::IconButton *sectionIcon, Section section); void setActiveSection(Section section); gsl::not_null _pan; std::array, kEmojiSectionCount> _sections; }; EmojiListWidget::Footer::Footer(gsl::not_null parent) : InnerFooter(parent) , _pan(parent) , _sections { { object_ptr(this, st::emojiCategoryRecent), object_ptr(this, st::emojiCategoryPeople), object_ptr(this, st::emojiCategoryNature), object_ptr(this, st::emojiCategoryFood), object_ptr(this, st::emojiCategoryActivity), object_ptr(this, st::emojiCategoryTravel), object_ptr(this, st::emojiCategoryObjects), object_ptr(this, st::emojiCategorySymbols), } } { auto left = (st::emojiPanWidth - _sections.size() * st::emojiCategory.width) / 2; for (auto i = 0; i != _sections.size(); ++i) { auto &button = _sections[i]; button->moveToLeft(left, 0); left += button->width(); button->setClickedCallback([this, value = static_cast
(i)] { setActiveSection(value); }); } setCurrentSectionIcon(Section::Recent); } void EmojiListWidget::Footer::processPanelHideFinished() { setCurrentSectionIcon(Section::Recent); } void EmojiListWidget::Footer::setCurrentSectionIcon(Section section) { std::array overrides = { { &st::emojiRecentActive, &st::emojiPeopleActive, &st::emojiNatureActive, &st::emojiFoodActive, &st::emojiActivityActive, &st::emojiTravelActive, &st::emojiObjectsActive, &st::emojiSymbolsActive, } }; for (auto i = 0; i != _sections.size(); ++i) { _sections[i]->setIconOverride((section == static_cast
(i)) ? overrides[i] : nullptr); } } void EmojiListWidget::Footer::setActiveSection(Ui::Emoji::Section section) { _pan->showEmojiSection(section); } EmojiColorPicker::EmojiColorPicker(QWidget *parent) : TWidget(parent) { setMouseTracking(true); auto w = st::emojiPanMargins.left() + st::emojiPanSize.width() + st::emojiColorsSep + st::emojiPanMargins.right(); auto h = st::emojiPanMargins.top() + 2 * st::emojiColorsPadding + st::emojiPanSize.height() + st::emojiPanMargins.bottom(); resize(w, h); _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideAnimated())); } void EmojiColorPicker::showEmoji(EmojiPtr emoji) { if (!emoji || !emoji->hasVariants()) { return; } _ignoreShow = false; _variants.resize(emoji->variantsCount() + 1); for (auto i = 0, size = _variants.size(); i != size; ++i) { _variants[i] = emoji->variant(i); } auto w = st::emojiPanMargins.left() + st::emojiPanSize.width() * _variants.size() + (_variants.size() - 2) * st::emojiColorsPadding + st::emojiColorsSep + st::emojiPanMargins.right(); auto h = st::emojiPanMargins.top() + 2 * st::emojiColorsPadding + st::emojiPanSize.height() + st::emojiPanMargins.bottom(); resize(w, h); if (!_cache.isNull()) _cache = QPixmap(); showAnimated(); } void EmojiColorPicker::paintEvent(QPaintEvent *e) { Painter p(this); auto opacity = _a_opacity.current(getms(), _hiding ? 0. : 1.); if (opacity < 1.) { if (opacity > 0.) { p.setOpacity(opacity); } else { return; } } if (e->rect() != rect()) { p.setClipRect(e->rect()); } auto inner = rect().marginsRemoved(st::emojiPanMargins); if (!_cache.isNull()) { p.drawPixmap(0, 0, _cache); return; } Ui::Shadow::paint(p, inner, width(), st::defaultRoundShadow); App::roundRect(p, inner, st::boxBg, BoxCorners); auto x = st::emojiPanMargins.left() + 2 * st::emojiColorsPadding + st::emojiPanSize.width(); if (rtl()) x = width() - x - st::emojiColorsSep; p.fillRect(x, st::emojiPanMargins.top() + st::emojiColorsPadding, st::emojiColorsSep, inner.height() - st::emojiColorsPadding * 2, st::emojiColorsSepColor); if (_variants.isEmpty()) return; for (auto i = 0, count = _variants.size(); i != count; ++i) { drawVariant(p, i); } } void EmojiColorPicker::enterEventHook(QEvent *e) { _hideTimer.stop(); if (_hiding) showAnimated(); TWidget::enterEventHook(e); } void EmojiColorPicker::leaveEventHook(QEvent *e) { TWidget::leaveEventHook(e); } void EmojiColorPicker::mousePressEvent(QMouseEvent *e) { if (e->button() != Qt::LeftButton) { return; } _lastMousePos = e->globalPos(); updateSelected(); _pressedSel = _selected; } void EmojiColorPicker::mouseReleaseEvent(QMouseEvent *e) { handleMouseRelease(e->globalPos()); } void EmojiColorPicker::handleMouseRelease(QPoint globalPos) { _lastMousePos = globalPos; int32 pressed = _pressedSel; _pressedSel = -1; updateSelected(); if (_selected >= 0 && (pressed < 0 || _selected == pressed)) { emit emojiSelected(_variants[_selected]); } _ignoreShow = true; hideAnimated(); } void EmojiColorPicker::handleMouseMove(QPoint globalPos) { _lastMousePos = globalPos; updateSelected(); } void EmojiColorPicker::mouseMoveEvent(QMouseEvent *e) { handleMouseMove(e->globalPos()); } void EmojiColorPicker::animationCallback() { update(); if (!_a_opacity.animating()) { _cache = QPixmap(); if (_hiding) { hide(); emit hidden(); } else { _lastMousePos = QCursor::pos(); updateSelected(); } } } void EmojiColorPicker::hideFast() { clearSelection(); _a_opacity.finish(); _cache = QPixmap(); hide(); emit hidden(); } void EmojiColorPicker::hideAnimated() { if (_cache.isNull()) { _cache = myGrab(this); clearSelection(); } _hiding = true; _a_opacity.start([this] { animationCallback(); }, 1., 0., st::emojiPanDuration); } void EmojiColorPicker::showAnimated() { if (_ignoreShow) return; if (!isHidden() && !_hiding) { return; } _hiding = false; if (_cache.isNull()) { _cache = myGrab(this); clearSelection(); } show(); _a_opacity.start([this] { animationCallback(); }, 0., 1., st::emojiPanDuration); } void EmojiColorPicker::clearSelection() { _pressedSel = -1; setSelected(-1); _lastMousePos = mapToGlobal(QPoint(-10, -10)); } void EmojiColorPicker::updateSelected() { auto newSelected = -1; auto p = mapFromGlobal(_lastMousePos); auto sx = rtl() ? (width() - p.x()) : p.x(), y = p.y() - st::emojiPanMargins.top() - st::emojiColorsPadding; if (y >= 0 && y < st::emojiPanSize.height()) { auto x = sx - st::emojiPanMargins.left() - st::emojiColorsPadding; if (x >= 0 && x < st::emojiPanSize.width()) { newSelected = 0; } else { x -= st::emojiPanSize.width() + 2 * st::emojiColorsPadding + st::emojiColorsSep; if (x >= 0 && x < st::emojiPanSize.width() * (_variants.size() - 1)) { newSelected = (x / st::emojiPanSize.width()) + 1; } } } setSelected(newSelected); } void EmojiColorPicker::setSelected(int newSelected) { if (_selected == newSelected) { return; } auto updateSelectedRect = [this] { if (_selected < 0) return; rtlupdate(st::emojiPanMargins.left() + st::emojiColorsPadding + _selected * st::emojiPanSize.width() + (_selected ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::emojiPanMargins.top() + st::emojiColorsPadding, st::emojiPanSize.width(), st::emojiPanSize.height()); }; updateSelectedRect(); _selected = newSelected; updateSelectedRect(); setCursor((_selected >= 0) ? style::cur_pointer : style::cur_default); } void EmojiColorPicker::drawVariant(Painter &p, int variant) { QPoint w(st::emojiPanMargins.left() + st::emojiColorsPadding + variant * st::emojiPanSize.width() + (variant ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::emojiPanMargins.top() + st::emojiColorsPadding); if (variant == _selected) { QPoint tl(w); if (rtl()) tl.setX(width() - tl.x() - st::emojiPanSize.width()); App::roundRect(p, QRect(tl, st::emojiPanSize), st::emojiPanHover, StickerHoverCorners); } auto esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1); p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_variants[variant]->x() * esize, _variants[variant]->y() * esize, esize, esize)); } EmojiListWidget::EmojiListWidget(QWidget *parent) : Inner(parent) , _picker(this) { resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, countHeight()); setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent); _picker->hide(); _esize = Ui::Emoji::Size(Ui::Emoji::Index() + 1); for (auto i = 0; i != kEmojiSectionCount; ++i) { _counts[i] = Ui::Emoji::GetSectionCount(static_cast
(i)); } _showPickerTimer.setSingleShot(true); connect(&_showPickerTimer, SIGNAL(timeout()), this, SLOT(onShowPicker())); connect(_picker, SIGNAL(emojiSelected(EmojiPtr)), this, SLOT(onColorSelected(EmojiPtr))); connect(_picker, SIGNAL(hidden()), this, SLOT(onPickerHidden())); } void EmojiListWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { Inner::setVisibleTopBottom(visibleTop, visibleBottom); if (_footer) { _footer->setCurrentSectionIcon(currentSection(visibleTop)); } } object_ptr EmojiListWidget::createFooter() { Expects(_footer == nullptr); auto result = object_ptr