From 6d27172b20d9f9782d433ea528b2416334e66453 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 29 Mar 2017 17:04:00 +0300 Subject: [PATCH] Move [Emoji|Sticker]PanInner to separate modules. Now we have three separate [Emoji|Stickers|Gifs]ListWidget. --- Telegram/SourceFiles/historywidget.h | 7 +- .../stickers/emoji_list_widget.cpp | 695 +++++ .../SourceFiles/stickers/emoji_list_widget.h | 162 ++ Telegram/SourceFiles/stickers/emoji_panel.cpp | 2312 +---------------- Telegram/SourceFiles/stickers/emoji_panel.h | 452 +--- .../SourceFiles/stickers/gifs_list_widget.cpp | 764 ++++++ .../SourceFiles/stickers/gifs_list_widget.h | 132 + .../stickers/stickers_list_widget.cpp | 931 +++++++ .../stickers/stickers_list_widget.h | 170 ++ Telegram/gyp/telegram_sources.txt | 6 + 10 files changed, 2976 insertions(+), 2655 deletions(-) create mode 100644 Telegram/SourceFiles/stickers/emoji_list_widget.cpp create mode 100644 Telegram/SourceFiles/stickers/emoji_list_widget.h create mode 100644 Telegram/SourceFiles/stickers/gifs_list_widget.cpp create mode 100644 Telegram/SourceFiles/stickers/gifs_list_widget.h create mode 100644 Telegram/SourceFiles/stickers/stickers_list_widget.cpp create mode 100644 Telegram/SourceFiles/stickers/stickers_list_widget.h diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 8011bac3d3..ac29982b69 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -57,8 +57,11 @@ class Controller; class TopBarWidget; } // namespace Window -class DragArea; +namespace ChatHelpers { class EmojiPanel; +} // namespace ChatHelpers + +class DragArea; class SilentToggle; class SendFilesBox; @@ -1160,7 +1163,7 @@ private: QTimer _membersDropdownShowTimer; object_ptr _inlineResults = { nullptr }; - object_ptr _emojiPanel; + object_ptr _emojiPanel; DragState _attachDrag = DragStateNone; object_ptr _attachDragDocument, _attachDragPhoto; diff --git a/Telegram/SourceFiles/stickers/emoji_list_widget.cpp b/Telegram/SourceFiles/stickers/emoji_list_widget.cpp new file mode 100644 index 0000000000..51d59ad220 --- /dev/null +++ b/Telegram/SourceFiles/stickers/emoji_list_widget.cpp @@ -0,0 +1,695 @@ +/* +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 "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 + +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)); +} + +class EmojiListWidget::Controller : public TWidget { +public: + Controller(gsl::not_null parent); + +private: + gsl::not_null _pan; + +}; + +EmojiListWidget::Controller::Controller(gsl::not_null parent) : TWidget(parent) +, _pan(parent) { +} + +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::GetPackCount(EmojiSectionAtIndex(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())); +} + +object_ptr EmojiListWidget::createController() { + return object_ptr(this); +} + +template +bool EmojiListWidget::enumerateSections(Callback callback) const { + auto info = SectionInfo(); + for (auto i = 0; i != kEmojiSectionCount; ++i) { + info.section = i; + info.count = Ui::Emoji::GetPackCount(EmojiSectionAtIndex(i)); + info.rowsCount = (info.count / kEmojiPanelPerRow) + ((info.count % kEmojiPanelPerRow) ? 1 : 0); + info.rowsTop = info.top + (i == 0 ? st::emojiPanPadding : st::emojiPanHeader); + info.rowsBottom = info.rowsTop + info.rowsCount * st::emojiPanSize.height(); + if (!callback(info)) { + return false; + } + info.top = info.rowsBottom; + } + return true; +} + +EmojiListWidget::SectionInfo EmojiListWidget::sectionInfo(int section) const { + Expects(section >= 0 && section < kEmojiSectionCount); + auto result = SectionInfo(); + enumerateSections([searchForSection = section, &result](const SectionInfo &info) { + if (info.section == searchForSection) { + result = info; + return false; + } + return true; + }); + return result; +} + +EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset(int yOffset) const { + auto result = SectionInfo(); + enumerateSections([&result, yOffset](const SectionInfo &info) { + if (yOffset < info.rowsBottom || info.section == kEmojiSectionCount - 1) { + result = info; + return false; + } + return true; + }); + return result; +} + +int EmojiListWidget::countHeight() { + return sectionInfo(kEmojiSectionCount - 1).top + st::emojiPanPadding; +} + +void EmojiListWidget::ensureLoaded(int section) { + if (!_emoji[section].isEmpty()) { + return; + } + _emoji[section] = Ui::Emoji::GetPack(EmojiSectionAtIndex(section)); + if (EmojiSectionAtIndex(section) == dbiesRecent) { + return; + } + for (auto &emoji : _emoji[section]) { + if (emoji->hasVariants()) { + auto j = cEmojiVariants().constFind(emoji->nonColoredId()); + if (j != cEmojiVariants().cend()) { + emoji = emoji->variant(j.value()); + } + } + } +} + +void EmojiListWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + QRect r = e ? e->rect() : rect(); + if (r != rect()) { + p.setClipRect(r); + } + p.fillRect(r, st::emojiPanBg); + + auto fromColumn = floorclamp(r.x() - st::emojiPanPadding, st::emojiPanSize.width(), 0, kEmojiPanelPerRow); + auto toColumn = ceilclamp(r.x() + r.width() - st::emojiPanPadding, st::emojiPanSize.width(), 0, kEmojiPanelPerRow); + if (rtl()) { + qSwap(fromColumn, toColumn); + fromColumn = kEmojiPanelPerRow - fromColumn; + toColumn = kEmojiPanelPerRow - toColumn; + } + + enumerateSections([this, &p, r, fromColumn, toColumn](const SectionInfo &info) { + if (r.top() >= info.rowsBottom) { + return true; + } else if (r.top() + r.height() <= info.top) { + return false; + } + if (info.section > 0 && r.top() < info.rowsTop) { + p.setFont(st::emojiPanHeaderFont); + p.setPen(st::emojiPanHeaderFg); + p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, info.top + st::emojiPanHeaderTop, width(), lang(LangKey(lng_emoji_category0 + info.section))); + } + if (r.top() + r.height() > info.rowsTop) { + ensureLoaded(info.section); + auto fromRow = floorclamp(r.y() - info.rowsTop, st::emojiPanSize.height(), 0, info.rowsCount); + auto toRow = ceilclamp(r.y() + r.height() - info.rowsTop, st::emojiPanSize.height(), 0, info.rowsCount); + for (auto i = fromRow; i < toRow; ++i) { + for (auto j = fromColumn; j < toColumn; ++j) { + auto index = i * kEmojiPanelPerRow + j; + if (index >= info.count) break; + + auto selected = (!_picker->isHidden() && info.section * MatrixRowShift + index == _pickerSel) || (info.section * MatrixRowShift + index == _selected); + + auto w = QPoint(st::emojiPanPadding + j * st::emojiPanSize.width(), info.rowsTop + i * st::emojiPanSize.height()); + if (selected) { + auto tl = w; + if (rtl()) tl.setX(width() - tl.x() - st::emojiPanSize.width()); + App::roundRect(p, QRect(tl, st::emojiPanSize), st::emojiPanHover, StickerHoverCorners); + } + auto sourceRect = QRect(_emoji[info.section][index]->x() * _esize, _emoji[info.section][index]->y() * _esize, _esize, _esize); + auto imageLeft = w.x() + (st::emojiPanSize.width() - (_esize / cIntRetinaFactor())) / 2; + auto imageTop = w.y() + (st::emojiPanSize.height() - (_esize / cIntRetinaFactor())) / 2; + p.drawPixmapLeft(imageLeft, imageTop, width(), App::emojiLarge(), sourceRect); + } + } + } + return true; + }); +} + +bool EmojiListWidget::checkPickerHide() { + if (!_picker->isHidden() && _pickerSel >= 0) { + _picker->hideAnimated(); + _pickerSel = -1; + updateSelected(); + return true; + } + return false; +} + +void EmojiListWidget::mousePressEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); + if (checkPickerHide() || e->button() != Qt::LeftButton) { + return; + } + _pressedSel = _selected; + + if (_selected >= 0) { + auto section = (_selected / MatrixRowShift); + auto sel = _selected % MatrixRowShift; + if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) { + _pickerSel = _selected; + setCursor(style::cur_default); + if (!cEmojiVariants().contains(_emoji[section][sel]->nonColoredId())) { + onShowPicker(); + } else { + _showPickerTimer.start(500); + } + } + } +} + +void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { + int32 pressed = _pressedSel; + _pressedSel = -1; + + _lastMousePos = e->globalPos(); + if (!_picker->isHidden()) { + if (_picker->rect().contains(_picker->mapFromGlobal(_lastMousePos))) { + return _picker->handleMouseRelease(QCursor::pos()); + } else if (_pickerSel >= 0) { + auto section = (_pickerSel / MatrixRowShift); + auto sel = _pickerSel % MatrixRowShift; + if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) { + if (cEmojiVariants().contains(_emoji[section][sel]->nonColoredId())) { + _picker->hideAnimated(); + _pickerSel = -1; + } + } + } + } + updateSelected(); + + if (_showPickerTimer.isActive()) { + _showPickerTimer.stop(); + _pickerSel = -1; + _picker->hide(); + } + + if (_selected < 0 || _selected != pressed) return; + + if (_selected >= kEmojiSectionCount * MatrixRowShift) { + return; + } + + auto section = (_selected / MatrixRowShift); + auto sel = _selected % MatrixRowShift; + if (sel < _emoji[section].size()) { + auto emoji = _emoji[section][sel]; + if (emoji->hasVariants() && !_picker->isHidden()) return; + + selectEmoji(emoji); + } +} + +void EmojiListWidget::selectEmoji(EmojiPtr emoji) { + auto &recent = Ui::Emoji::GetRecent(); + auto i = recent.begin(), e = recent.end(); + for (; i != e; ++i) { + if (i->first == emoji) { + ++i->second; + if (i->second > 0x8000) { + for (RecentEmojiPack::iterator j = recent.begin(); j != e; ++j) { + if (j->second > 1) { + j->second /= 2; + } else { + j->second = 1; + } + } + } + for (; i != recent.begin(); --i) { + if ((i - 1)->second > i->second) { + break; + } + qSwap(*i, *(i - 1)); + } + break; + } + } + if (i == e) { + while (recent.size() >= kEmojiPanelPerRow * kEmojiPanelRowsPerPage) recent.pop_back(); + recent.push_back(qMakePair(emoji, 1)); + for (i = recent.end() - 1; i != recent.begin(); --i) { + if ((i - 1)->second > i->second) { + break; + } + qSwap(*i, *(i - 1)); + } + } + emit saveConfigDelayed(kSaveRecentEmojiTimeout); + + emit selected(emoji); +} + +void EmojiListWidget::onShowPicker() { + if (_pickerSel < 0) return; + + auto section = (_pickerSel / MatrixRowShift); + auto sel = _pickerSel % MatrixRowShift; + if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) { + _picker->showEmoji(_emoji[section][sel]); + + auto y = emojiRect(section, sel).y(); + y -= _picker->height() - st::buttonRadius + getVisibleTop(); + if (y < st::emojiPanHeader) { + y += _picker->height() - st::buttonRadius + st::emojiPanSize.height() - st::buttonRadius; + } + auto xmax = width() - _picker->width(); + auto coef = float64(sel % kEmojiPanelPerRow) / float64(kEmojiPanelPerRow - 1); + if (rtl()) coef = 1. - coef; + _picker->move(qRound(xmax * coef), y); + + emit disableScroll(true); + } +} + +void EmojiListWidget::onPickerHidden() { + _pickerSel = -1; + update(); + emit disableScroll(false); + + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +QRect EmojiListWidget::emojiRect(int section, int sel) { + auto info = sectionInfo(section); + auto countTillItem = (sel - (sel % kEmojiPanelPerRow)); + auto rowsToSkip = (countTillItem / kEmojiPanelPerRow) + ((countTillItem % kEmojiPanelPerRow) ? 1 : 0); + auto x = st::emojiPanPadding + ((sel % kEmojiPanelPerRow) * st::emojiPanSize.width()); + auto y = info.rowsTop + rowsToSkip * st::emojiPanSize.height(); + return QRect(x, y, st::emojiPanSize.width(), st::emojiPanSize.height()); +} + +void EmojiListWidget::onColorSelected(EmojiPtr emoji) { + if (emoji->hasVariants()) { + cRefEmojiVariants().insert(emoji->nonColoredId(), emoji->variantIndex(emoji)); + } + if (_pickerSel >= 0) { + auto section = (_pickerSel / MatrixRowShift); + auto sel = _pickerSel % MatrixRowShift; + if (section >= 0 && section < kEmojiSectionCount) { + _emoji[section][sel] = emoji; + rtlupdate(emojiRect(section, sel)); + } + } + selectEmoji(emoji); + _picker->hideAnimated(); +} + +void EmojiListWidget::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + if (!_picker->isHidden()) { + if (_picker->rect().contains(_picker->mapFromGlobal(_lastMousePos))) { + return _picker->handleMouseMove(QCursor::pos()); + } else { + _picker->clearSelection(); + } + } + updateSelected(); +} + +void EmojiListWidget::leaveEventHook(QEvent *e) { + clearSelection(); +} + +void EmojiListWidget::leaveToChildEvent(QEvent *e, QWidget *child) { + clearSelection(); +} + +void EmojiListWidget::enterFromChildEvent(QEvent *e, QWidget *child) { + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +void EmojiListWidget::clearSelection() { + _lastMousePos = mapToGlobal(QPoint(-10, -10)); + _pressedSel = -1; + setSelected(-1); +} + +DBIEmojiSection EmojiListWidget::currentSection(int yOffset) const { + return EmojiSectionAtIndex(sectionInfoByOffset(yOffset).section); +} + +void EmojiListWidget::hideFinish(bool completely) { + if (!_picker->isHidden()) { + _picker->hideFast(); + _pickerSel = -1; + } + clearSelection(); +} + +void EmojiListWidget::refreshRecent() { + clearSelection(); + _counts[0] = Ui::Emoji::GetPackCount(dbiesRecent); + _emoji[0] = Ui::Emoji::GetPack(dbiesRecent); + auto h = countHeight(); + if (h != height()) { + resize(width(), h); + update(); + } +} + +bool EmojiListWidget::event(QEvent *e) { + if (e->type() == QEvent::ParentChange) { + if (_picker->parentWidget() != parentWidget()) { + _picker->setParent(parentWidget()); + } + _picker->raise(); + } + return Inner::event(e); +} + +void EmojiListWidget::updateSelected() { + if (_pressedSel >= 0 || _pickerSel >= 0) return; + + auto newSelected = -1; + auto p = mapFromGlobal(_lastMousePos); + auto info = sectionInfoByOffset(p.y()); + if (p.y() >= info.rowsTop && p.y() < info.rowsBottom) { + auto sx = (rtl() ? width() - p.x() : p.x()) - st::emojiPanPadding; + if (sx >= 0 && sx < kEmojiPanelPerRow * st::emojiPanSize.width()) { + newSelected = qFloor((p.y() - info.rowsTop) / st::emojiPanSize.height()) * kEmojiPanelPerRow + qFloor(sx / st::emojiPanSize.width()); + if (newSelected >= _emoji[info.section].size()) { + newSelected = -1; + } else { + newSelected += info.section * MatrixRowShift; + } + } + } + + setSelected(newSelected); +} + +void EmojiListWidget::setSelected(int newSelected) { + if (_selected == newSelected) { + return; + } + auto updateSelected = [this]() { + if (_selected < 0) return; + rtlupdate(emojiRect(_selected / MatrixRowShift, _selected % MatrixRowShift)); + }; + updateSelected(); + _selected = newSelected; + updateSelected(); + + setCursor((_selected >= 0) ? style::cur_pointer : style::cur_default); + if (_selected >= 0 && !_picker->isHidden()) { + if (_selected != _pickerSel) { + _picker->hideAnimated(); + } else { + _picker->showAnimated(); + } + } +} + +void EmojiListWidget::showEmojiSection(DBIEmojiSection section) { + clearSelection(); + + refreshRecent(); + + auto y = 0; + enumerateSections([&y, sectionForSearch = section](const SectionInfo &info) { + if (EmojiSectionAtIndex(info.section) == sectionForSearch) { + y = info.top; + return false; + } + return true; + }); + emit scrollToY(y); + + _lastMousePos = QCursor::pos(); + + update(); +} + +} // namespace ChatHelpers diff --git a/Telegram/SourceFiles/stickers/emoji_list_widget.h b/Telegram/SourceFiles/stickers/emoji_list_widget.h new file mode 100644 index 0000000000..7f6e806e15 --- /dev/null +++ b/Telegram/SourceFiles/stickers/emoji_list_widget.h @@ -0,0 +1,162 @@ +/* +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 +*/ +#pragma once + +#include "stickers/emoji_panel.h" + +namespace ChatHelpers { + +constexpr auto kEmojiSectionCount = 8; +inline DBIEmojiSection EmojiSectionAtIndex(int index) { + return (index < 0 || index >= kEmojiSectionCount) ? dbiesRecent : DBIEmojiSection(index - 1); +} + +class EmojiColorPicker : public TWidget { + Q_OBJECT + +public: + EmojiColorPicker(QWidget *parent); + + void showEmoji(EmojiPtr emoji); + + void clearSelection(); + void handleMouseMove(QPoint globalPos); + void handleMouseRelease(QPoint globalPos); + + void hideFast(); + +public slots: + void showAnimated(); + void hideAnimated(); + +signals: + void emojiSelected(EmojiPtr emoji); + void hidden(); + +protected: + void paintEvent(QPaintEvent *e) override; + void enterEventHook(QEvent *e) override; + void leaveEventHook(QEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + +private: + void animationCallback(); + + void drawVariant(Painter &p, int variant); + + void updateSelected(); + void setSelected(int newSelected); + + bool _ignoreShow = false; + + QVector _variants; + + int _selected = -1; + int _pressedSel = -1; + QPoint _lastMousePos; + + bool _hiding = false; + QPixmap _cache; + Animation _a_opacity; + + QTimer _hideTimer; + +}; + +class EmojiListWidget : public EmojiPanel::Inner { + Q_OBJECT + +public: + EmojiListWidget(QWidget *parent); + + void refreshRecent() override; + void hideFinish(bool completely) override; + void clearSelection() override; + object_ptr createController() override; + + void showEmojiSection(DBIEmojiSection section); + DBIEmojiSection currentSection(int yOffset) const; + +public slots: + void onShowPicker(); + void onPickerHidden(); + void onColorSelected(EmojiPtr emoji); + + bool checkPickerHide(); + +signals: + void selected(EmojiPtr emoji); + void switchToStickers(); + +protected: + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void leaveEventHook(QEvent *e) override; + void leaveToChildEvent(QEvent *e, QWidget *child) override; + void enterFromChildEvent(QEvent *e, QWidget *child) override; + bool event(QEvent *e) override; + int countHeight() override; + +private: + class Controller; + + struct SectionInfo { + int section = 0; + int count = 0; + int top = 0; + int rowsCount = 0; + int rowsTop = 0; + int rowsBottom = 0; + }; + template + bool enumerateSections(Callback callback) const; + SectionInfo sectionInfo(int section) const; + SectionInfo sectionInfoByOffset(int yOffset) const; + + void ensureLoaded(int section); + int countSectionTop(int section) const; + void updateSelected(); + void setSelected(int newSelected); + + void selectEmoji(EmojiPtr emoji); + + QRect emojiRect(int section, int sel); + + int _counts[kEmojiSectionCount]; + QVector _emoji[kEmojiSectionCount]; + + int32 _esize; + + int _selected = -1; + int _pressedSel = -1; + int _pickerSel = -1; + QPoint _lastMousePos; + + object_ptr _picker; + QTimer _showPickerTimer; + +}; + +} // namespace ChatHelpers diff --git a/Telegram/SourceFiles/stickers/emoji_panel.cpp b/Telegram/SourceFiles/stickers/emoji_panel.cpp index 6b03bfc474..c60c6a642e 100644 --- a/Telegram/SourceFiles/stickers/emoji_panel.cpp +++ b/Telegram/SourceFiles/stickers/emoji_panel.cpp @@ -20,2259 +20,28 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "stickers/emoji_panel.h" +#include "stickers/emoji_list_widget.h" +#include "stickers/stickers_list_widget.h" +#include "stickers/gifs_list_widget.h" #include "styles/style_stickers.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/widgets/discrete_sliders.h" -#include "ui/effects/ripple_animation.h" -#include "boxes/confirmbox.h" +#include "ui/widgets/scroll_area.h" #include "boxes/stickersetbox.h" #include "boxes/stickers_box.h" +#include "boxes/confirmbox.h" #include "inline_bots/inline_bot_result.h" -#include "inline_bots/inline_bot_layout_item.h" #include "dialogs/dialogs_layout.h" #include "stickers/stickers.h" -#include "historywidget.h" #include "storage/localstorage.h" #include "lang.h" #include "mainwindow.h" -#include "apiwrap.h" -#include "mainwidget.h" -#include "auth_session.h" -namespace internal { +namespace ChatHelpers { namespace { -constexpr auto kSaveRecentEmojiTimeout = 3000; constexpr auto kSaveChosenTabTimeout = 1000; -constexpr auto kEmojiPanelPerRow = Ui::Emoji::kPanelPerRow; -constexpr auto kEmojiPanelRowsPerPage = Ui::Emoji::kPanelRowsPerPage; -constexpr auto kStickersPanelPerRow = Stickers::kPanelPerRow; - -} // namespace - -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)); -} - -BasicPanInner::BasicPanInner(QWidget *parent) : TWidget(parent) { -} - -void BasicPanInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { - auto oldVisibleHeight = getVisibleBottom() - getVisibleTop(); - _visibleTop = visibleTop; - _visibleBottom = visibleBottom; - auto visibleHeight = getVisibleBottom() - getVisibleTop(); - if (visibleHeight != oldVisibleHeight) { - resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, countHeight()); - } -} - -class EmojiPanInner::Controller : public TWidget { -public: - Controller(gsl::not_null parent); - -private: - gsl::not_null _pan; - -}; - -EmojiPanInner::Controller::Controller(gsl::not_null parent) : TWidget(parent) -, _pan(parent) { -} - -EmojiPanInner::EmojiPanInner(QWidget *parent) : BasicPanInner(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::GetPackCount(EmojiSectionAtIndex(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())); -} - -object_ptr EmojiPanInner::createController() { - return object_ptr(this); -} - -template -bool EmojiPanInner::enumerateSections(Callback callback) const { - auto info = SectionInfo(); - for (auto i = 0; i != kEmojiSectionCount; ++i) { - info.section = i; - info.count = Ui::Emoji::GetPackCount(EmojiSectionAtIndex(i)); - info.rowsCount = (info.count / kEmojiPanelPerRow) + ((info.count % kEmojiPanelPerRow) ? 1 : 0); - info.rowsTop = info.top + (i == 0 ? st::emojiPanPadding : st::emojiPanHeader); - info.rowsBottom = info.rowsTop + info.rowsCount * st::emojiPanSize.height(); - if (!callback(info)) { - return false; - } - info.top = info.rowsBottom; - } - return true; -} - -EmojiPanInner::SectionInfo EmojiPanInner::sectionInfo(int section) const { - Expects(section >= 0 && section < kEmojiSectionCount); - auto result = SectionInfo(); - enumerateSections([searchForSection = section, &result](const SectionInfo &info) { - if (info.section == searchForSection) { - result = info; - return false; - } - return true; - }); - return result; -} - -EmojiPanInner::SectionInfo EmojiPanInner::sectionInfoByOffset(int yOffset) const { - auto result = SectionInfo(); - enumerateSections([&result, yOffset](const SectionInfo &info) { - if (yOffset < info.rowsBottom || info.section == kEmojiSectionCount - 1) { - result = info; - return false; - } - return true; - }); - return result; -} - -int EmojiPanInner::countHeight() { - return sectionInfo(kEmojiSectionCount - 1).top + st::emojiPanPadding; -} - -void EmojiPanInner::ensureLoaded(int section) { - if (!_emoji[section].isEmpty()) { - return; - } - _emoji[section] = Ui::Emoji::GetPack(EmojiSectionAtIndex(section)); - if (EmojiSectionAtIndex(section) == dbiesRecent) { - return; - } - for (auto &emoji : _emoji[section]) { - if (emoji->hasVariants()) { - auto j = cEmojiVariants().constFind(emoji->nonColoredId()); - if (j != cEmojiVariants().cend()) { - emoji = emoji->variant(j.value()); - } - } - } -} - -void EmojiPanInner::paintEvent(QPaintEvent *e) { - Painter p(this); - QRect r = e ? e->rect() : rect(); - if (r != rect()) { - p.setClipRect(r); - } - p.fillRect(r, st::emojiPanBg); - - auto fromColumn = floorclamp(r.x() - st::emojiPanPadding, st::emojiPanSize.width(), 0, kEmojiPanelPerRow); - auto toColumn = ceilclamp(r.x() + r.width() - st::emojiPanPadding, st::emojiPanSize.width(), 0, kEmojiPanelPerRow); - if (rtl()) { - qSwap(fromColumn, toColumn); - fromColumn = kEmojiPanelPerRow - fromColumn; - toColumn = kEmojiPanelPerRow - toColumn; - } - - enumerateSections([this, &p, r, fromColumn, toColumn](const SectionInfo &info) { - if (r.top() >= info.rowsBottom) { - return true; - } else if (r.top() + r.height() <= info.top) { - return false; - } - if (info.section > 0 && r.top() < info.rowsTop) { - p.setFont(st::emojiPanHeaderFont); - p.setPen(st::emojiPanHeaderFg); - p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, info.top + st::emojiPanHeaderTop, width(), lang(LangKey(lng_emoji_category0 + info.section))); - } - if (r.top() + r.height() > info.rowsTop) { - ensureLoaded(info.section); - auto fromRow = floorclamp(r.y() - info.rowsTop, st::emojiPanSize.height(), 0, info.rowsCount); - auto toRow = ceilclamp(r.y() + r.height() - info.rowsTop, st::emojiPanSize.height(), 0, info.rowsCount); - for (auto i = fromRow; i < toRow; ++i) { - for (auto j = fromColumn; j < toColumn; ++j) { - auto index = i * kEmojiPanelPerRow + j; - if (index >= info.count) break; - - auto selected = (!_picker->isHidden() && info.section * MatrixRowShift + index == _pickerSel) || (info.section * MatrixRowShift + index == _selected); - - auto w = QPoint(st::emojiPanPadding + j * st::emojiPanSize.width(), info.rowsTop + i * st::emojiPanSize.height()); - if (selected) { - auto tl = w; - if (rtl()) tl.setX(width() - tl.x() - st::emojiPanSize.width()); - App::roundRect(p, QRect(tl, st::emojiPanSize), st::emojiPanHover, StickerHoverCorners); - } - auto sourceRect = QRect(_emoji[info.section][index]->x() * _esize, _emoji[info.section][index]->y() * _esize, _esize, _esize); - auto imageLeft = w.x() + (st::emojiPanSize.width() - (_esize / cIntRetinaFactor())) / 2; - auto imageTop = w.y() + (st::emojiPanSize.height() - (_esize / cIntRetinaFactor())) / 2; - p.drawPixmapLeft(imageLeft, imageTop, width(), App::emojiLarge(), sourceRect); - } - } - } - return true; - }); -} - -bool EmojiPanInner::checkPickerHide() { - if (!_picker->isHidden() && _pickerSel >= 0) { - _picker->hideAnimated(); - _pickerSel = -1; - updateSelected(); - return true; - } - return false; -} - -void EmojiPanInner::mousePressEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateSelected(); - if (checkPickerHide() || e->button() != Qt::LeftButton) { - return; - } - _pressedSel = _selected; - - if (_selected >= 0) { - auto section = (_selected / MatrixRowShift); - auto sel = _selected % MatrixRowShift; - if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) { - _pickerSel = _selected; - setCursor(style::cur_default); - if (!cEmojiVariants().contains(_emoji[section][sel]->nonColoredId())) { - onShowPicker(); - } else { - _showPickerTimer.start(500); - } - } - } -} - -void EmojiPanInner::mouseReleaseEvent(QMouseEvent *e) { - int32 pressed = _pressedSel; - _pressedSel = -1; - - _lastMousePos = e->globalPos(); - if (!_picker->isHidden()) { - if (_picker->rect().contains(_picker->mapFromGlobal(_lastMousePos))) { - return _picker->handleMouseRelease(QCursor::pos()); - } else if (_pickerSel >= 0) { - auto section = (_pickerSel / MatrixRowShift); - auto sel = _pickerSel % MatrixRowShift; - if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) { - if (cEmojiVariants().contains(_emoji[section][sel]->nonColoredId())) { - _picker->hideAnimated(); - _pickerSel = -1; - } - } - } - } - updateSelected(); - - if (_showPickerTimer.isActive()) { - _showPickerTimer.stop(); - _pickerSel = -1; - _picker->hide(); - } - - if (_selected < 0 || _selected != pressed) return; - - if (_selected >= kEmojiSectionCount * MatrixRowShift) { - return; - } - - auto section = (_selected / MatrixRowShift); - auto sel = _selected % MatrixRowShift; - if (sel < _emoji[section].size()) { - auto emoji = _emoji[section][sel]; - if (emoji->hasVariants() && !_picker->isHidden()) return; - - selectEmoji(emoji); - } -} - -void EmojiPanInner::selectEmoji(EmojiPtr emoji) { - auto &recent = Ui::Emoji::GetRecent(); - auto i = recent.begin(), e = recent.end(); - for (; i != e; ++i) { - if (i->first == emoji) { - ++i->second; - if (i->second > 0x8000) { - for (RecentEmojiPack::iterator j = recent.begin(); j != e; ++j) { - if (j->second > 1) { - j->second /= 2; - } else { - j->second = 1; - } - } - } - for (; i != recent.begin(); --i) { - if ((i - 1)->second > i->second) { - break; - } - qSwap(*i, *(i - 1)); - } - break; - } - } - if (i == e) { - while (recent.size() >= kEmojiPanelPerRow * kEmojiPanelRowsPerPage) recent.pop_back(); - recent.push_back(qMakePair(emoji, 1)); - for (i = recent.end() - 1; i != recent.begin(); --i) { - if ((i - 1)->second > i->second) { - break; - } - qSwap(*i, *(i - 1)); - } - } - emit saveConfigDelayed(kSaveRecentEmojiTimeout); - - emit selected(emoji); -} - -void EmojiPanInner::onShowPicker() { - if (_pickerSel < 0) return; - - auto section = (_pickerSel / MatrixRowShift); - auto sel = _pickerSel % MatrixRowShift; - if (section < kEmojiSectionCount && sel < _emoji[section].size() && _emoji[section][sel]->hasVariants()) { - _picker->showEmoji(_emoji[section][sel]); - - auto y = emojiRect(section, sel).y(); - y -= _picker->height() - st::buttonRadius + getVisibleTop(); - if (y < st::emojiPanHeader) { - y += _picker->height() - st::buttonRadius + st::emojiPanSize.height() - st::buttonRadius; - } - auto xmax = width() - _picker->width(); - auto coef = float64(sel % kEmojiPanelPerRow) / float64(kEmojiPanelPerRow - 1); - if (rtl()) coef = 1. - coef; - _picker->move(qRound(xmax * coef), y); - - emit disableScroll(true); - } -} - -void EmojiPanInner::onPickerHidden() { - _pickerSel = -1; - update(); - emit disableScroll(false); - - _lastMousePos = QCursor::pos(); - updateSelected(); -} - -QRect EmojiPanInner::emojiRect(int section, int sel) { - auto info = sectionInfo(section); - auto countTillItem = (sel - (sel % kEmojiPanelPerRow)); - auto rowsToSkip = (countTillItem / kEmojiPanelPerRow) + ((countTillItem % kEmojiPanelPerRow) ? 1 : 0); - auto x = st::emojiPanPadding + ((sel % kEmojiPanelPerRow) * st::emojiPanSize.width()); - auto y = info.rowsTop + rowsToSkip * st::emojiPanSize.height(); - return QRect(x, y, st::emojiPanSize.width(), st::emojiPanSize.height()); -} - -void EmojiPanInner::onColorSelected(EmojiPtr emoji) { - if (emoji->hasVariants()) { - cRefEmojiVariants().insert(emoji->nonColoredId(), emoji->variantIndex(emoji)); - } - if (_pickerSel >= 0) { - auto section = (_pickerSel / MatrixRowShift); - auto sel = _pickerSel % MatrixRowShift; - if (section >= 0 && section < kEmojiSectionCount) { - _emoji[section][sel] = emoji; - rtlupdate(emojiRect(section, sel)); - } - } - selectEmoji(emoji); - _picker->hideAnimated(); -} - -void EmojiPanInner::mouseMoveEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - if (!_picker->isHidden()) { - if (_picker->rect().contains(_picker->mapFromGlobal(_lastMousePos))) { - return _picker->handleMouseMove(QCursor::pos()); - } else { - _picker->clearSelection(); - } - } - updateSelected(); -} - -void EmojiPanInner::leaveEventHook(QEvent *e) { - clearSelection(); -} - -void EmojiPanInner::leaveToChildEvent(QEvent *e, QWidget *child) { - clearSelection(); -} - -void EmojiPanInner::enterFromChildEvent(QEvent *e, QWidget *child) { - _lastMousePos = QCursor::pos(); - updateSelected(); -} - -void EmojiPanInner::clearSelection() { - _lastMousePos = mapToGlobal(QPoint(-10, -10)); - _pressedSel = -1; - setSelected(-1); -} - -DBIEmojiSection EmojiPanInner::currentSection(int yOffset) const { - return EmojiSectionAtIndex(sectionInfoByOffset(yOffset).section); -} - -void EmojiPanInner::hideFinish(bool completely) { - if (!_picker->isHidden()) { - _picker->hideFast(); - _pickerSel = -1; - } - clearSelection(); -} - -void EmojiPanInner::refreshRecent() { - clearSelection(); - _counts[0] = Ui::Emoji::GetPackCount(dbiesRecent); - _emoji[0] = Ui::Emoji::GetPack(dbiesRecent); - auto h = countHeight(); - if (h != height()) { - resize(width(), h); - update(); - } -} - -bool EmojiPanInner::event(QEvent *e) { - if (e->type() == QEvent::ParentChange) { - if (_picker->parentWidget() != parentWidget()) { - _picker->setParent(parentWidget()); - } - _picker->raise(); - } - return BasicPanInner::event(e); -} - -void EmojiPanInner::updateSelected() { - if (_pressedSel >= 0 || _pickerSel >= 0) return; - - auto newSelected = -1; - auto p = mapFromGlobal(_lastMousePos); - auto info = sectionInfoByOffset(p.y()); - if (p.y() >= info.rowsTop && p.y() < info.rowsBottom) { - auto sx = (rtl() ? width() - p.x() : p.x()) - st::emojiPanPadding; - if (sx >= 0 && sx < kEmojiPanelPerRow * st::emojiPanSize.width()) { - newSelected = qFloor((p.y() - info.rowsTop) / st::emojiPanSize.height()) * kEmojiPanelPerRow + qFloor(sx / st::emojiPanSize.width()); - if (newSelected >= _emoji[info.section].size()) { - newSelected = -1; - } else { - newSelected += info.section * MatrixRowShift; - } - } - } - - setSelected(newSelected); -} - -void EmojiPanInner::setSelected(int newSelected) { - if (_selected == newSelected) { - return; - } - auto updateSelected = [this]() { - if (_selected < 0) return; - rtlupdate(emojiRect(_selected / MatrixRowShift, _selected % MatrixRowShift)); - }; - updateSelected(); - _selected = newSelected; - updateSelected(); - - setCursor((_selected >= 0) ? style::cur_pointer : style::cur_default); - if (_selected >= 0 && !_picker->isHidden()) { - if (_selected != _pickerSel) { - _picker->hideAnimated(); - } else { - _picker->showAnimated(); - } - } -} - -void EmojiPanInner::showEmojiSection(DBIEmojiSection section) { - clearSelection(); - - refreshRecent(); - - auto y = 0; - enumerateSections([&y, sectionForSearch = section](const SectionInfo &info) { - if (EmojiSectionAtIndex(info.section) == sectionForSearch) { - y = info.top; - return false; - } - return true; - }); - emit scrollToY(y); - - _lastMousePos = QCursor::pos(); - - update(); -} - -class StickerPanInner::Controller : public TWidget { -public: - Controller(gsl::not_null parent); - -private: - gsl::not_null _pan; - -}; - -StickerPanInner::Controller::Controller(gsl::not_null parent) : TWidget(parent) -, _pan(parent) { -} - -StickerPanInner::StickerPanInner(QWidget *parent, bool gifs) : BasicPanInner(parent) -, _section(gifs ? Section::Gifs : Section::Stickers) -, _addText(lang(lng_stickers_featured_add).toUpper()) -, _addWidth(st::stickersTrendingAdd.font->width(_addText)) -, _settings(this, lang(lng_stickers_you_have)) { - resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, countHeight()); - - setMouseTracking(true); - setAttribute(Qt::WA_OpaquePaintEvent); - - connect(_settings, SIGNAL(clicked()), this, SLOT(onSettings())); - - _previewTimer.setSingleShot(true); - connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); - - _updateInlineItems.setSingleShot(true); - connect(&_updateInlineItems, SIGNAL(timeout()), this, SLOT(onUpdateInlineItems())); - - subscribe(AuthSession::CurrentDownloaderTaskFinished(), [this] { - update(); - readVisibleSets(); - }); -} - -object_ptr StickerPanInner::createController() { - return object_ptr(this); -} - -void StickerPanInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { - auto top = getVisibleTop(); - BasicPanInner::setVisibleTopBottom(visibleTop, visibleBottom); - if (top != getVisibleTop()) { - _lastScrolled = getms(); - } - if (_section == Section::Featured) { - readVisibleSets(); - } -} - -void StickerPanInner::readVisibleSets() { - auto itemsVisibleTop = getVisibleTop(); - auto itemsVisibleBottom = getVisibleBottom(); - auto rowHeight = featuredRowHeight(); - int rowFrom = floorclamp(itemsVisibleTop, rowHeight, 0, _featuredSets.size()); - int rowTo = ceilclamp(itemsVisibleBottom, rowHeight, 0, _featuredSets.size()); - for (int i = rowFrom; i < rowTo; ++i) { - auto &set = _featuredSets[i]; - if (!(set.flags & MTPDstickerSet_ClientFlag::f_unread)) { - continue; - } - if (i * rowHeight < itemsVisibleTop || (i + 1) * rowHeight > itemsVisibleBottom) { - continue; - } - int count = qMin(set.pack.size(), static_cast(kStickersPanelPerRow)); - int loaded = 0; - for (int j = 0; j < count; ++j) { - if (set.pack[j]->thumb->loaded() || set.pack[j]->loaded()) { - ++loaded; - } - } - if (loaded == count) { - Stickers::markFeaturedAsRead(set.id); - } - } -} - -int StickerPanInner::featuredRowHeight() const { - return st::stickersTrendingHeader + st::stickerPanSize.height() + st::stickersTrendingSkip; -} - -template -bool StickerPanInner::enumerateSections(Callback callback) const { - Expects(_section == Section::Stickers); - auto info = SectionInfo(); - for (auto i = 0; i != _mySets.size(); ++i) { - info.section = i; - info.count = _mySets[i].pack.size(); - info.rowsCount = (info.count / kStickersPanelPerRow) + ((info.count % kStickersPanelPerRow) ? 1 : 0); - info.rowsTop = info.top + (i == 0 ? st::stickerPanPadding : st::emojiPanHeader); - info.rowsBottom = info.rowsTop + info.rowsCount * st::stickerPanSize.height(); - if (!callback(info)) { - return false; - } - info.top = info.rowsBottom; - } - return true; -} - -StickerPanInner::SectionInfo StickerPanInner::sectionInfo(int section) const { - Expects(section >= 0 && section < _mySets.size()); - auto result = SectionInfo(); - enumerateSections([searchForSection = section, &result](const SectionInfo &info) { - if (info.section == searchForSection) { - result = info; - return false; - } - return true; - }); - return result; -} - -StickerPanInner::SectionInfo StickerPanInner::sectionInfoByOffset(int yOffset) const { - auto result = SectionInfo(); - enumerateSections([this, &result, yOffset](const SectionInfo &info) { - if (yOffset < info.rowsBottom || info.section == _mySets.size() - 1) { - result = info; - return false; - } - return true; - }); - return result; -} - -int StickerPanInner::countHeight() { - auto visibleHeight = getVisibleBottom() - getVisibleTop(); - if (visibleHeight <= 0) { - visibleHeight = st::emojiPanMaxHeight - st::emojiCategory.height; - } - auto minimalLastHeight = (visibleHeight - st::stickerPanPadding); - auto countResult = [this, minimalLastHeight] { - if (showingInlineItems()) { - auto result = st::stickerPanPadding; - if (_switchPmButton) { - result += _switchPmButton->height() + st::inlineResultsSkip; - } - for (int i = 0, l = _inlineRows.count(); i < l; ++i) { - result += _inlineRows[i].height; - } - return result; - } else if (_section == Section::Featured) { - return st::stickerPanPadding + shownSets().size() * featuredRowHeight(); - } else if (!shownSets().empty()) { - auto info = sectionInfo(shownSets().size() - 1); - return info.top + qMax(info.rowsBottom - info.top, minimalLastHeight); - } - return 0; - }; - return qMax(minimalLastHeight, countResult()) + st::stickerPanPadding; -} - -void StickerPanInner::installedLocally(uint64 setId) { - _installedLocallySets.insert(setId); -} - -void StickerPanInner::notInstalledLocally(uint64 setId) { - _installedLocallySets.remove(setId); -} - -void StickerPanInner::clearInstalledLocally() { - if (!_installedLocallySets.empty()) { - _installedLocallySets.clear(); - refreshStickers(); - } -} - -StickerPanInner::~StickerPanInner() { - clearInlineRows(true); - deleteUnusedGifLayouts(); - deleteUnusedInlineLayouts(); -} - -int StickerPanInner::stickersLeft() const { - return (st::stickerPanPadding - st::buttonRadius); -} - -QRect StickerPanInner::stickerRect(int section, int sel) { - int x = 0, y = 0; - if (_section == Section::Featured) { - x = stickersLeft() + (sel * st::stickerPanSize.width()); - y = st::stickerPanPadding + (section * featuredRowHeight()) + st::stickersTrendingHeader; - } else if (_section == Section::Stickers) { - auto info = sectionInfo(section); - if (sel >= _mySets[section].pack.size()) { - sel -= _mySets[section].pack.size(); - } - auto countTillItem = (sel - (sel % kStickersPanelPerRow)); - auto rowsToSkip = (countTillItem / kStickersPanelPerRow) + ((countTillItem % kStickersPanelPerRow) ? 1 : 0); - x = stickersLeft() + ((sel % kStickersPanelPerRow) * st::stickerPanSize.width()); - y = info.rowsTop + rowsToSkip * st::stickerPanSize.height(); - } - return QRect(x, y, st::stickerPanSize.width(), st::stickerPanSize.height()); -} - -void StickerPanInner::paintEvent(QPaintEvent *e) { - Painter p(this); - auto clip = e->rect(); - p.fillRect(clip, st::emojiPanBg); - - if (showingInlineItems()) { - paintInlineItems(p, clip); - } else if (_section == Section::Featured) { - paintFeaturedStickers(p, clip); - } else { - paintStickers(p, clip); - } -} - -void StickerPanInner::paintInlineItems(Painter &p, QRect clip) { - if (_inlineRows.isEmpty() && !_switchPmButton) { - p.setFont(st::normalFont); - p.setPen(st::noContactsColor); - p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), lang(lng_inline_bot_no_results), style::al_center); - return; - } - auto gifPaused = Ui::isLayerShown() || Ui::isMediaViewShown() || _previewShown || !App::wnd()->isActive(); - InlineBots::Layout::PaintContext context(getms(), false, gifPaused, false); - - auto top = st::stickerPanPadding; - if (_switchPmButton) { - top += _switchPmButton->height() + st::inlineResultsSkip; - } - - auto fromx = rtl() ? (width() - clip.x() - clip.width()) : clip.x(); - auto tox = rtl() ? (width() - clip.x()) : (clip.x() + clip.width()); - for (auto row = 0, rows = _inlineRows.size(); row != rows; ++row) { - auto &inlineRow = _inlineRows[row]; - if (top >= clip.top() + clip.height()) { - break; - } - if (top + inlineRow.height > clip.top()) { - auto left = st::inlineResultsLeft - st::buttonRadius; - if (row == rows - 1) context.lastRow = true; - for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { - if (left >= tox) break; - - auto item = inlineRow.items.at(col); - auto w = item->width(); - if (left + w > fromx) { - p.translate(left, top); - item->paint(p, clip.translated(-left, -top), &context); - p.translate(-left, -top); - } - left += w; - if (item->hasRightSkip()) { - left += st::inlineResultsSkip; - } - } - } - top += inlineRow.height; - } -} - -void StickerPanInner::paintFeaturedStickers(Painter &p, QRect clip) { - auto fromColumn = floorclamp(clip.x() - stickersLeft(), st::stickerPanSize.width(), 0, kStickersPanelPerRow); - auto toColumn = ceilclamp(clip.x() + clip.width() - stickersLeft(), st::stickerPanSize.width(), 0, kStickersPanelPerRow); - if (rtl()) { - qSwap(fromColumn, toColumn); - fromColumn = kStickersPanelPerRow - fromColumn; - toColumn = kStickersPanelPerRow - toColumn; - } - - auto &sets = shownSets(); - auto selsection = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; - auto selindex = (selsection >= 0) ? (_selected % MatrixRowShift) : -1; - auto seldelete = false; - if (selsection >= sets.size()) { - selsection = -1; - } else if (selsection >= 0 && selindex >= sets[selsection].pack.size()) { - selindex -= sets[selsection].pack.size(); - seldelete = true; - } - - auto tilly = st::stickerPanPadding; - auto ms = getms(); - for (auto c = 0, l = sets.size(); c != l; ++c) { - auto y = tilly; - auto &set = sets[c]; - tilly = y + featuredRowHeight(); - if (clip.top() >= tilly) continue; - if (y >= clip.y() + clip.height()) break; - - int size = set.pack.size(); - - int widthForTitle = featuredContentWidth() - (st::emojiPanHeaderLeft - st::buttonRadius); - if (featuredHasAddButton(c)) { - auto add = featuredAddRect(c); - auto selected = (_selectedFeaturedSetAdd == c) || (_pressedFeaturedSetAdd == c); - auto &textBg = selected ? st::stickersTrendingAdd.textBgOver : st::stickersTrendingAdd.textBg; - - App::roundRect(p, myrtlrect(add), textBg, ImageRoundRadius::Small); - if (set.ripple) { - set.ripple->paint(p, add.x(), add.y(), width(), ms); - if (set.ripple->empty()) { - set.ripple.reset(); - } - } - p.setFont(st::stickersTrendingAdd.font); - p.setPen(selected ? st::stickersTrendingAdd.textFgOver : st::stickersTrendingAdd.textFg); - p.drawTextLeft(add.x() - (st::stickersTrendingAdd.width / 2), add.y() + st::stickersTrendingAdd.textTop, width(), _addText, _addWidth); - - widthForTitle -= add.width() - (st::stickersTrendingAdd.width / 2); - } else { - auto add = featuredAddRect(c); - int checkx = add.left() + (add.width() - st::stickersFeaturedInstalled.width()) / 2; - int checky = add.top() + (add.height() - st::stickersFeaturedInstalled.height()) / 2; - st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width()); - } - if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { - widthForTitle -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; - } - - auto titleText = set.title; - auto titleWidth = st::stickersTrendingHeaderFont->width(titleText); - if (titleWidth > widthForTitle) { - titleText = st::stickersTrendingHeaderFont->elided(titleText, widthForTitle); - titleWidth = st::stickersTrendingHeaderFont->width(titleText); - } - p.setFont(st::stickersTrendingHeaderFont); - p.setPen(st::stickersTrendingHeaderFg); - p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, y + st::stickersTrendingHeaderTop, width(), titleText, titleWidth); - - if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { - p.setPen(Qt::NoPen); - p.setBrush(st::stickersFeaturedUnreadBg); - - { - PainterHighQualityEnabler hq(p); - p.drawEllipse(rtlrect(st::emojiPanHeaderLeft - st::buttonRadius + titleWidth + st::stickersFeaturedUnreadSkip, y + st::stickersTrendingHeaderTop + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); - } - } - - p.setFont(st::stickersTrendingSubheaderFont); - p.setPen(st::stickersTrendingSubheaderFg); - p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, y + st::stickersTrendingSubheaderTop, width(), lng_stickers_count(lt_count, size)); - - y += st::stickersTrendingHeader; - if (y >= clip.y() + clip.height()) break; - - for (int j = fromColumn; j < toColumn; ++j) { - int index = j; - if (index >= size) break; - - auto selected = (selsection == c && selindex == index); - auto deleteSelected = selected && seldelete; - paintSticker(p, set, y, index, selected, deleteSelected); - } - } -} - -void StickerPanInner::paintStickers(Painter &p, QRect clip) { - auto fromColumn = floorclamp(clip.x() - stickersLeft(), st::stickerPanSize.width(), 0, kStickersPanelPerRow); - auto toColumn = ceilclamp(clip.x() + clip.width() - stickersLeft(), st::stickerPanSize.width(), 0, kStickersPanelPerRow); - if (rtl()) { - qSwap(fromColumn, toColumn); - fromColumn = kStickersPanelPerRow - fromColumn; - toColumn = kStickersPanelPerRow - toColumn; - } - - auto &sets = shownSets(); - auto selsection = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; - auto selindex = (selsection >= 0) ? (_selected % MatrixRowShift) : -1; - auto seldelete = false; - if (selsection >= sets.size()) { - selsection = -1; - } else if (selsection >= 0 && selindex >= sets[selsection].pack.size()) { - selindex -= sets[selsection].pack.size(); - seldelete = true; - } - enumerateSections([this, &p, clip, fromColumn, toColumn, selsection, selindex, seldelete](const SectionInfo &info) { - if (clip.top() >= info.rowsBottom) { - return true; - } else if (clip.top() + clip.height() <= info.top) { - return false; - } - auto &set = _mySets[info.section]; - if (info.section > 0 && clip.top() < info.rowsTop) { - // TODO delete button, elided text - p.setFont(st::emojiPanHeaderFont); - p.setPen(st::emojiPanHeaderFg); - p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, info.top + st::emojiPanHeaderTop, width(), set.title); - } - if (clip.top() + clip.height() > info.rowsTop) { - auto special = (set.flags & MTPDstickerSet::Flag::f_official) != 0; - auto fromRow = floorclamp(clip.y() - info.rowsTop, st::stickerPanSize.height(), 0, info.rowsCount); - auto toRow = ceilclamp(clip.y() + clip.height() - info.rowsTop, st::stickerPanSize.height(), 0, info.rowsCount); - for (int i = fromRow; i < toRow; ++i) { - for (int j = fromColumn; j < toColumn; ++j) { - int index = i * kStickersPanelPerRow + j; - if (index >= info.count) break; - - auto selected = (selsection == info.section && selindex == index); - auto deleteSelected = selected && seldelete; - paintSticker(p, set, info.rowsTop, index, selected, deleteSelected); - } - } - } - return true; - }); -} - -void StickerPanInner::paintSticker(Painter &p, Set &set, int y, int index, bool selected, bool deleteSelected) { - auto sticker = set.pack[index]; - if (!sticker->sticker()) return; - - int row = (index / kStickersPanelPerRow), col = (index % kStickersPanelPerRow); - - auto pos = QPoint(stickersLeft() + col * st::stickerPanSize.width(), y + row * st::stickerPanSize.height()); - if (selected) { - auto tl = pos; - if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width()); - App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners); - } - - auto goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); - if (goodThumb) { - sticker->thumb->load(); - } else { - sticker->checkSticker(); - } - - auto coef = qMin((st::stickerPanSize.width() - st::buttonRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::buttonRadius * 2) / float64(sticker->dimensions.height())); - if (coef > 1) coef = 1; - auto w = qMax(qRound(coef * sticker->dimensions.width()), 1); - auto h = qMax(qRound(coef * sticker->dimensions.height()), 1); - auto ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); - if (goodThumb) { - p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h)); - } else if (!sticker->sticker()->img->isNull()) { - p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h)); - } - - if (selected && set.id == Stickers::RecentSetId && _custom.at(index)) { - auto xPos = pos + QPoint(st::stickerPanSize.width() - st::stickerPanDelete.width(), 0); - if (!deleteSelected) p.setOpacity(st::stickerPanDeleteOpacity); - st::stickerPanDelete.paint(p, xPos, width()); - if (!deleteSelected) p.setOpacity(1.); - } -} - -bool StickerPanInner::featuredHasAddButton(int index) const { - if (index < 0 || index >= _featuredSets.size()) { - return false; - } - auto flags = _featuredSets[index].flags; - return !(flags & MTPDstickerSet::Flag::f_installed) || (flags & MTPDstickerSet::Flag::f_archived); -} - -int StickerPanInner::featuredContentWidth() const { - return stickersLeft() + (kStickersPanelPerRow * st::stickerPanSize.width()); -} - -QRect StickerPanInner::featuredAddRect(int index) const { - int addw = _addWidth - st::stickersTrendingAdd.width; - int addh = st::stickersTrendingAdd.height; - int addx = featuredContentWidth() - addw; - int addy = st::stickerPanPadding + index * featuredRowHeight() + st::stickersTrendingAddTop; - return QRect(addx, addy, addw, addh); -} - -void StickerPanInner::mousePressEvent(QMouseEvent *e) { - if (e->button() != Qt::LeftButton) { - return; - } - _lastMousePos = e->globalPos(); - updateSelected(); - - _pressed = _selected; - _pressedFeaturedSet = _selectedFeaturedSet; - setPressedFeaturedSetAdd(_selectedFeaturedSetAdd); - ClickHandler::pressed(); - _previewTimer.start(QApplication::startDragTime()); -} - -void StickerPanInner::setPressedFeaturedSetAdd(int newPressedFeaturedSetAdd) { - if (_pressedFeaturedSetAdd >= 0 && _pressedFeaturedSetAdd < _featuredSets.size()) { - auto &set = _featuredSets[_pressedFeaturedSetAdd]; - if (set.ripple) { - set.ripple->lastStop(); - } - } - _pressedFeaturedSetAdd = newPressedFeaturedSetAdd; - if (_pressedFeaturedSetAdd >= 0 && _pressedFeaturedSetAdd < _featuredSets.size()) { - auto &set = _featuredSets[_pressedFeaturedSetAdd]; - if (!set.ripple) { - auto maskSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height); - auto mask = Ui::RippleAnimation::roundRectMask(maskSize, st::buttonRadius); - set.ripple = MakeShared(st::stickersTrendingAdd.ripple, std::move(mask), [this, index = _pressedFeaturedSetAdd] { - update(myrtlrect(featuredAddRect(index))); - }); - } - auto rect = myrtlrect(featuredAddRect(_pressedFeaturedSetAdd)); - set.ripple->add(mapFromGlobal(QCursor::pos()) - rect.topLeft()); - } -} - -void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { - _previewTimer.stop(); - - auto pressed = std::exchange(_pressed, -1); - auto pressedFeaturedSet = std::exchange(_pressedFeaturedSet, -1); - auto pressedFeaturedSetAdd = _pressedFeaturedSetAdd; - setPressedFeaturedSetAdd(-1); - if (pressedFeaturedSetAdd != _selectedFeaturedSetAdd) { - update(); - } - - auto activated = ClickHandler::unpressed(); - - if (_previewShown) { - _previewShown = false; - return; - } - - _lastMousePos = e->globalPos(); - updateSelected(); - - if (showingInlineItems()) { - if (_selected < 0 || _selected != pressed || !activated) { - return; - } - - if (dynamic_cast(activated.data())) { - int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift; - selectInlineResult(row, column); - } else { - App::activateClickHandler(activated, e->button()); - } - return; - } - - auto &sets = shownSets(); - if (_selected >= 0 && _selected < MatrixRowShift * sets.size() && _selected == pressed) { - auto section = (_selected / MatrixRowShift); - auto sel = _selected % MatrixRowShift; - if (sets[section].id == Stickers::RecentSetId && sel >= sets[section].pack.size() && sel < sets[section].pack.size() * 2 && _custom.at(sel - sets[section].pack.size())) { - removeRecentSticker(section, sel - sets[section].pack.size()); - return; - } - if (sel < sets[section].pack.size()) { - emit selected(sets[section].pack[sel]); - } - } else if (_selectedFeaturedSet >= 0 && _selectedFeaturedSet < sets.size() && _selectedFeaturedSet == pressedFeaturedSet) { - emit displaySet(sets[_selectedFeaturedSet].id); - } else if (_selectedFeaturedSetAdd >= 0 && _selectedFeaturedSetAdd < sets.size() && _selectedFeaturedSetAdd == pressedFeaturedSetAdd) { - emit installSet(sets[_selectedFeaturedSetAdd].id); - } -} - -void StickerPanInner::selectInlineResult(int row, int column) { - if (row >= _inlineRows.size() || column >= _inlineRows.at(row).items.size()) { - return; - } - - auto item = _inlineRows[row].items[column]; - if (auto photo = item->getPhoto()) { - if (photo->medium->loaded() || photo->thumb->loaded()) { - emit selected(photo); - } else if (!photo->medium->loading()) { - photo->thumb->loadEvenCancelled(); - photo->medium->loadEvenCancelled(); - } - } else if (auto document = item->getDocument()) { - if (document->loaded()) { - emit selected(document); - } else if (document->loading()) { - document->cancel(); - } else { - DocumentOpenClickHandler::doOpen(document, nullptr, ActionOnLoadNone); - } - } else if (auto inlineResult = item->getResult()) { - if (inlineResult->onChoose(item)) { - emit selected(inlineResult, _inlineBot); - } - } -} - -void StickerPanInner::removeRecentSticker(int section, int index) { - if (_section != Section::Stickers || section >= _mySets.size() || _mySets[section].id != Stickers::RecentSetId) { - return; - } - - clearSelection(); - bool refresh = false; - auto sticker = _mySets[section].pack[index]; - auto &recent = cGetRecentStickers(); - for (int32 i = 0, l = recent.size(); i < l; ++i) { - if (recent.at(i).first == sticker) { - recent.removeAt(i); - Local::writeUserSettings(); - refresh = true; - break; - } - } - auto &sets = Global::RefStickerSets(); - auto it = sets.find(Stickers::CustomSetId); - if (it != sets.cend()) { - for (int i = 0, l = it->stickers.size(); i < l; ++i) { - if (it->stickers.at(i) == sticker) { - it->stickers.removeAt(i); - if (it->stickers.isEmpty()) { - sets.erase(it); - } - Local::writeInstalledStickers(); - refresh = true; - break; - } - } - } - if (refresh) { - refreshRecentStickers(); - updateSelected(); - update(); - } -} - -void StickerPanInner::mouseMoveEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateSelected(); -} - -void StickerPanInner::resizeEvent(QResizeEvent *e) { - _settings->moveToLeft((st::emojiPanWidth - _settings->width()) / 2, height() / 3); -} - -void StickerPanInner::leaveEventHook(QEvent *e) { - clearSelection(); -} - -void StickerPanInner::leaveToChildEvent(QEvent *e, QWidget *child) { - clearSelection(); -} - -void StickerPanInner::enterFromChildEvent(QEvent *e, QWidget *child) { - _lastMousePos = QCursor::pos(); - updateSelected(); -} - -void StickerPanInner::clearSelection() { - if (showingInlineItems()) { - if (_selected >= 0) { - int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; - t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); - ClickHandler::clearActive(_inlineRows.at(srow).items.at(scol)); - setCursor(style::cur_default); - } - _selected = _pressed = -1; - } else { - _pressed = -1; - _pressedFeaturedSet = -1; - setSelected(-1, -1, -1); - setPressedFeaturedSetAdd(-1); - } - update(); -} - -void StickerPanInner::hideFinish(bool completely) { - clearSelection(); - if (completely) { - auto itemForget = [](auto &item) { - if (auto document = item->getDocument()) { - document->forget(); - } - if (auto photo = item->getPhoto()) { - photo->forget(); - } - if (auto result = item->getResult()) { - result->forget(); - } - }; - clearInlineRows(false); - for_const (auto &item, _gifLayouts) { - itemForget(item.second); - } - for_const (auto &item, _inlineLayouts) { - itemForget(item.second); - } - clearInstalledLocally(); - } - - // Reset to the recent stickers section. - if (_section == Section::Featured) { - _section = Section::Stickers; - } -} - -void StickerPanInner::refreshStickers() { - auto stickersShown = (_section == Section::Stickers || _section == Section::Featured); - if (stickersShown) { - clearSelection(); - } - - _mySets.clear(); - _mySets.reserve(Global::StickerSetsOrder().size() + 1); - - refreshRecentStickers(false); - for_const (auto setId, Global::StickerSetsOrder()) { - appendSet(_mySets, setId, AppendSkip::Archived); - } - - _featuredSets.clear(); - _featuredSets.reserve(Global::FeaturedStickerSetsOrder().size()); - - for_const (auto setId, Global::FeaturedStickerSetsOrder()) { - appendSet(_featuredSets, setId, AppendSkip::Installed); - } - - if (stickersShown) { - int h = countHeight(); - if (h != height()) resize(width(), h); - - _settings->setVisible(_section == Section::Stickers && _mySets.isEmpty()); - } else { - _settings->hide(); - } - - emit refreshIcons(kRefreshIconsNoAnimation); - - if (stickersShown) updateSelected(); -} - -bool StickerPanInner::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth) { - InlineItem *layout = nullptr; - if (savedGif) { - layout = layoutPrepareSavedGif(savedGif, (_inlineRows.size() * MatrixRowShift) + row.items.size()); - } else if (result) { - layout = layoutPrepareInlineResult(result, (_inlineRows.size() * MatrixRowShift) + row.items.size()); - } - if (!layout) return false; - - layout->preload(); - if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { - layout->setPosition(_inlineRows.size() * MatrixRowShift); - } - - sumWidth += layout->maxWidth(); - if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { - sumWidth += st::inlineResultsSkip; - } - - row.items.push_back(layout); - return true; -} - -bool StickerPanInner::inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force) { - if (row.items.isEmpty()) return false; - - auto full = (row.items.size() >= kInlineItemsMaxPerRow); - auto big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); - if (full || big || force) { - _inlineRows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); - row = InlineRow(); - row.items.reserve(kInlineItemsMaxPerRow); - sumWidth = 0; - return true; - } - return false; -} - -void StickerPanInner::refreshSavedGifs() { - if (_section == Section::Gifs) { - _settings->hide(); - clearInlineRows(false); - - auto &saved = cSavedGifs(); - if (saved.isEmpty()) { - showStickerSet(Stickers::RecentSetId); - return; - } else { - _inlineRows.reserve(saved.size()); - auto row = InlineRow(); - row.items.reserve(kInlineItemsMaxPerRow); - auto sumWidth = 0; - for_const (auto &gif, saved) { - inlineRowsAddItem(gif, 0, row, sumWidth); - } - inlineRowFinalize(row, sumWidth, true); - } - deleteUnusedGifLayouts(); - - int32 h = countHeight(); - if (h != height()) resize(width(), h); - - update(); - } - emit refreshIcons(kRefreshIconsNoAnimation); - - updateSelected(); -} - -void StickerPanInner::inlineBotChanged() { - refreshInlineRows(nullptr, nullptr, true); -} - -void StickerPanInner::clearInlineRows(bool resultsDeleted) { - if (resultsDeleted) { - if (showingInlineItems()) { - _selected = _pressed = -1; - } - } else { - if (showingInlineItems()) { - clearSelection(); - } - for_const (auto &row, _inlineRows) { - for_const (auto &item, row.items) { - item->setPosition(-1); - } - } - } - _inlineRows.clear(); -} - -InlineItem *StickerPanInner::layoutPrepareSavedGif(DocumentData *doc, int32 position) { - auto it = _gifLayouts.find(doc); - if (it == _gifLayouts.cend()) { - if (auto layout = InlineItem::createLayoutGif(this, doc)) { - it = _gifLayouts.emplace(doc, std::move(layout)).first; - it->second->initDimensions(); - } else { - return nullptr; - } - } - if (!it->second->maxWidth()) return nullptr; - - it->second->setPosition(position); - return it->second.get(); -} - -InlineItem *StickerPanInner::layoutPrepareInlineResult(InlineResult *result, int32 position) { - auto it = _inlineLayouts.find(result); - if (it == _inlineLayouts.cend()) { - if (auto layout = InlineItem::createLayout(this, result, _inlineWithThumb)) { - it = _inlineLayouts.emplace(result, std::move(layout)).first; - it->second->initDimensions(); - } else { - return nullptr; - } - } - if (!it->second->maxWidth()) return nullptr; - - it->second->setPosition(position); - return it->second.get(); -} - -void StickerPanInner::deleteUnusedGifLayouts() { - if (_inlineRows.isEmpty() || _section != Section::Gifs) { // delete all - _gifLayouts.clear(); - } else { - for (auto i = _gifLayouts.begin(); i != _gifLayouts.cend();) { - if (i->second->position() < 0) { - i = _gifLayouts.erase(i); - } else { - ++i; - } - } - } -} - -void StickerPanInner::deleteUnusedInlineLayouts() { - if (_inlineRows.isEmpty() || _section == Section::Gifs) { // delete all - _inlineLayouts.clear(); - } else { - for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { - if (i->second->position() < 0) { - i = _inlineLayouts.erase(i); - } else { - ++i; - } - } - } -} - -StickerPanInner::InlineRow &StickerPanInner::layoutInlineRow(InlineRow &row, int32 sumWidth) { - auto count = int(row.items.size()); - t_assert(count <= kInlineItemsMaxPerRow); - - // enumerate items in the order of growing maxWidth() - // for that sort item indices by maxWidth() - int indices[kInlineItemsMaxPerRow]; - for (auto i = 0; i != count; ++i) { - indices[i] = i; - } - std::sort(indices, indices + count, [&row](int a, int b) -> bool { - return row.items.at(a)->maxWidth() < row.items.at(b)->maxWidth(); - }); - - row.height = 0; - int availw = width() - (st::inlineResultsLeft - st::buttonRadius); - for (int i = 0; i < count; ++i) { - int index = indices[i]; - int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth(); - int actualw = qMax(w, int(st::inlineResultsMinWidth)); - row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw)); - if (sumWidth) { - availw -= actualw; - sumWidth -= row.items.at(index)->maxWidth(); - if (index > 0 && row.items.at(index - 1)->hasRightSkip()) { - availw -= st::inlineResultsSkip; - sumWidth -= st::inlineResultsSkip; - } - } - } - return row; -} - -void StickerPanInner::preloadImages() { - if (showingInlineItems()) { - for (auto row = 0, rows = _inlineRows.size(); row != rows; ++row) { - for (auto col = 0, cols = _inlineRows[row].items.size(); col != cols; ++col) { - _inlineRows[row].items[col]->preload(); - } - } - return; - } - - auto &sets = shownSets(); - for (int i = 0, l = sets.size(), k = 0; i < l; ++i) { - int count = sets[i].pack.size(); - if (_section == Section::Featured) { - accumulate_min(count, kStickersPanelPerRow); - } - for (int j = 0; j != count; ++j) { - if (++k > kStickersPanelPerRow * (kStickersPanelPerRow + 1)) break; - - auto sticker = sets.at(i).pack.at(j); - if (!sticker || !sticker->sticker()) continue; - - bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); - if (goodThumb) { - sticker->thumb->load(); - } else { - sticker->automaticLoad(0); - } - } - if (k > kStickersPanelPerRow * (kStickersPanelPerRow + 1)) break; - } -} - -uint64 StickerPanInner::currentSet(int yOffset) const { - if (showingInlineItems()) { - return Stickers::NoneSetId; - } else if (_section == Section::Featured) { - return Stickers::FeaturedSetId; - } - return _mySets.isEmpty() ? Stickers::RecentSetId : _mySets[sectionInfoByOffset(yOffset).section].id; -} - -void StickerPanInner::hideInlineRowsPanel() { - clearInlineRows(false); - if (showingInlineItems()) { - _section = Section::Gifs; - refreshSavedGifs(); - emit scrollToY(0); - emit scrollUpdated(); - } -} - -void StickerPanInner::clearInlineRowsPanel() { - clearInlineRows(false); -} - -void StickerPanInner::refreshSwitchPmButton(const InlineCacheEntry *entry) { - if (!entry || entry->switchPmText.isEmpty()) { - _switchPmButton.destroy(); - _switchPmStartToken.clear(); - } else { - if (!_switchPmButton) { - _switchPmButton.create(this, QString(), st::switchPmButton); - _switchPmButton->show(); - _switchPmButton->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - connect(_switchPmButton, SIGNAL(clicked()), this, SLOT(onSwitchPm())); - } - _switchPmButton->setText(entry->switchPmText); // doesn't perform text.toUpper() - _switchPmStartToken = entry->switchPmStartToken; - auto buttonTop = st::stickerPanPadding; - _switchPmButton->move(st::inlineResultsLeft - st::buttonRadius, buttonTop); - } - update(); -} - -int StickerPanInner::refreshInlineRows(UserData *bot, const InlineCacheEntry *entry, bool resultsDeleted) { - _inlineBot = bot; - refreshSwitchPmButton(entry); - auto clearResults = [this, entry]() { - if (!entry) { - return true; - } - if (entry->results.empty() && entry->switchPmText.isEmpty()) { - if (!_inlineBot) { - return true; - } - } - return false; - }; - auto clearResultsResult = clearResults(); // Clang segfault workaround. - if (clearResultsResult) { - if (resultsDeleted) { - clearInlineRows(true); - deleteUnusedInlineLayouts(); - } - emit emptyInlineRows(); - return 0; - } - - clearSelection(); - - t_assert(_inlineBot != 0); - _inlineBotTitle = lng_inline_bot_results(lt_inline_bot, _inlineBot->username.isEmpty() ? _inlineBot->name : ('@' + _inlineBot->username)); - - _section = Section::Inlines; - _settings->hide(); - - auto count = int(entry->results.size()); - auto from = validateExistingInlineRows(entry->results); - auto added = 0; - - if (count) { - _inlineRows.reserve(count); - auto row = InlineRow(); - row.items.reserve(kInlineItemsMaxPerRow); - auto sumWidth = 0; - for (auto i = from; i != count; ++i) { - if (inlineRowsAddItem(0, entry->results[i].get(), row, sumWidth)) { - ++added; - } - } - inlineRowFinalize(row, sumWidth, true); - } - - int32 h = countHeight(); - if (h != height()) resize(width(), h); - update(); - - emit refreshIcons(kRefreshIconsNoAnimation); - - _lastMousePos = QCursor::pos(); - updateSelected(); - - return added; -} - -int StickerPanInner::validateExistingInlineRows(const InlineResults &results) { - int count = results.size(), until = 0, untilrow = 0, untilcol = 0; - for (; until < count;) { - if (untilrow >= _inlineRows.size() || _inlineRows[untilrow].items[untilcol]->getResult() != results[until].get()) { - break; - } - ++until; - if (++untilcol == _inlineRows[untilrow].items.size()) { - ++untilrow; - untilcol = 0; - } - } - if (until == count) { // all items are layed out - if (untilrow == _inlineRows.size()) { // nothing changed - return until; - } - - for (int i = untilrow, l = _inlineRows.size(), skip = untilcol; i < l; ++i) { - for (int j = 0, s = _inlineRows[i].items.size(); j < s; ++j) { - if (skip) { - --skip; - } else { - _inlineRows[i].items[j]->setPosition(-1); - } - } - } - if (!untilcol) { // all good rows are filled - _inlineRows.resize(untilrow); - return until; - } - _inlineRows.resize(untilrow + 1); - _inlineRows[untilrow].items.resize(untilcol); - _inlineRows[untilrow] = layoutInlineRow(_inlineRows[untilrow]); - return until; - } - if (untilrow && !untilcol) { // remove last row, maybe it is not full - --untilrow; - untilcol = _inlineRows[untilrow].items.size(); - } - until -= untilcol; - - for (int i = untilrow, l = _inlineRows.size(); i < l; ++i) { - for (int j = 0, s = _inlineRows[i].items.size(); j < s; ++j) { - _inlineRows[i].items[j]->setPosition(-1); - } - } - _inlineRows.resize(untilrow); - - if (_inlineRows.isEmpty()) { - _inlineWithThumb = false; - for (int i = until; i < count; ++i) { - if (results.at(i)->hasThumbDisplay()) { - _inlineWithThumb = true; - break; - } - } - } - return until; -} - -void StickerPanInner::inlineItemLayoutChanged(const InlineItem *layout) { - if (_selected < 0 || !showingInlineItems() || !isVisible()) { - return; - } - - int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; - if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { - if (layout == _inlineRows.at(row).items.at(col)) { - updateSelected(); - } - } -} - -void StickerPanInner::inlineItemRepaint(const InlineItem *layout) { - auto ms = getms(); - if (_lastScrolled + 100 <= ms) { - update(); - } else { - _updateInlineItems.start(_lastScrolled + 100 - ms); - } -} - -bool StickerPanInner::inlineItemVisible(const InlineItem *layout) { - auto position = layout->position(); - if (!showingInlineItems() || position < 0 || !isVisible()) { - return false; - } - - auto row = position / MatrixRowShift; - auto col = position % MatrixRowShift; - t_assert((row < _inlineRows.size()) && (col < _inlineRows[row].items.size())); - - auto &inlineItems = _inlineRows[row].items; - auto top = 0; - for (auto i = 0; i != row; ++i) { - top += _inlineRows[i].height; - } - - return (top < getVisibleBottom()) && (top + _inlineRows[row].items[col]->height() > getVisibleTop()); -} - -void StickerPanInner::appendSet(Sets &to, uint64 setId, AppendSkip skip) { - auto &sets = Global::StickerSets(); - auto it = sets.constFind(setId); - if (it == sets.cend() || it->stickers.isEmpty()) return; - if ((skip == AppendSkip::Archived) && (it->flags & MTPDstickerSet::Flag::f_archived)) return; - if ((skip == AppendSkip::Installed) && (it->flags & MTPDstickerSet::Flag::f_installed) && !(it->flags & MTPDstickerSet::Flag::f_archived)) { - if (!_installedLocallySets.contains(setId)) { - return; - } - } - - to.push_back(Set(it->id, it->flags, it->title, it->stickers.size() + 1, it->stickers)); -} - -void StickerPanInner::refreshRecent() { - if (_section == Section::Gifs) { - refreshSavedGifs(); - } else if (_section == Section::Stickers) { - refreshRecentStickers(); - } -} - -void StickerPanInner::refreshRecentStickers(bool performResize) { - _custom.clear(); - clearSelection(); - auto &sets = Global::StickerSets(); - auto &recent = cGetRecentStickers(); - auto customIt = sets.constFind(Stickers::CustomSetId); - auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); - if (recent.isEmpty() - && (customIt == sets.cend() || customIt->stickers.isEmpty()) - && (cloudIt == sets.cend() || cloudIt->stickers.isEmpty())) { - if (!_mySets.isEmpty() && _mySets.at(0).id == Stickers::RecentSetId) { - _mySets.pop_front(); - } - } else { - StickerPack recentPack; - int customCnt = (customIt == sets.cend()) ? 0 : customIt->stickers.size(); - int cloudCnt = (cloudIt == sets.cend()) ? 0 : cloudIt->stickers.size(); - recentPack.reserve(cloudCnt + recent.size() + customCnt); - _custom.reserve(cloudCnt + recent.size() + customCnt); - if (cloudCnt > 0) { - for_const (auto sticker, cloudIt->stickers) { - recentPack.push_back(sticker); - _custom.push_back(false); - } - } - for_const (auto &recentSticker, recent) { - auto sticker = recentSticker.first; - recentPack.push_back(sticker); - _custom.push_back(false); - } - if (customCnt > 0) { - for_const (auto &sticker, customIt->stickers) { - auto index = recentPack.indexOf(sticker); - if (index >= cloudCnt) { - _custom[index] = true; // mark stickers from recent as custom - } else { - recentPack.push_back(sticker); - _custom.push_back(true); - } - } - } - if (_mySets.isEmpty() || _mySets.at(0).id != Stickers::RecentSetId) { - _mySets.push_back(Set(Stickers::RecentSetId, MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special, lang(lng_recent_stickers), recentPack.size() * 2, recentPack)); - } else { - _mySets[0].pack = recentPack; - } - } - - if (performResize && (_section == Section::Stickers || _section == Section::Featured)) { - int32 h = countHeight(); - if (h != height()) { - resize(width(), h); - update(); - } - - updateSelected(); - } -} - -void StickerPanInner::fillIcons(QList &icons) { - icons.clear(); - icons.reserve(_mySets.size() + 1); - if (Global::FeaturedStickerSetsUnreadCount() && !_featuredSets.isEmpty()) { - icons.push_back(StickerIcon(Stickers::FeaturedSetId)); - } - - if (!_mySets.isEmpty()) { - int i = 0; - if (_mySets[0].id == Stickers::RecentSetId) { - ++i; - icons.push_back(StickerIcon(Stickers::RecentSetId)); - } - for (int l = _mySets.size(); i < l; ++i) { - auto s = _mySets[i].pack[0]; - int32 availw = st::emojiCategory.width - 2 * st::stickerIconPadding, availh = st::emojiCategory.height - 2 * st::stickerIconPadding; - int32 thumbw = s->thumb->width(), thumbh = s->thumb->height(), pixw = 1, pixh = 1; - if (availw * thumbh > availh * thumbw) { - pixh = availh; - pixw = (pixh * thumbw) / thumbh; - } else { - pixw = availw; - pixh = thumbw ? ((pixw * thumbh) / thumbw) : 1; - } - if (pixw < 1) pixw = 1; - if (pixh < 1) pixh = 1; - icons.push_back(StickerIcon(_mySets[i].id, s, pixw, pixh)); - } - } - - if (!Global::FeaturedStickerSetsUnreadCount() && !_featuredSets.isEmpty()) { - icons.push_back(StickerIcon(Stickers::FeaturedSetId)); - } -} - -void StickerPanInner::updateSelected() { - if (_pressed >= 0 && !_previewShown) { - return; - } - - auto newSelected = -1; - auto p = mapFromGlobal(_lastMousePos); - - if (showingInlineItems()) { - int sx = (rtl() ? width() - p.x() : p.x()) - (st::inlineResultsLeft - st::buttonRadius); - int sy = p.y() - st::stickerPanPadding; - if (_switchPmButton) { - sy -= _switchPmButton->height() + st::inlineResultsSkip; - } - int row = -1, col = -1, sel = -1; - ClickHandlerPtr lnk; - ClickHandlerHost *lnkhost = nullptr; - HistoryCursorState cursor = HistoryDefaultCursorState; - if (sy >= 0) { - row = 0; - for (int rows = _inlineRows.size(); row < rows; ++row) { - if (sy < _inlineRows.at(row).height) { - break; - } - sy -= _inlineRows.at(row).height; - } - } - if (sx >= 0 && row >= 0 && row < _inlineRows.size()) { - auto &inlineItems = _inlineRows[row].items; - col = 0; - for (int cols = inlineItems.size(); col < cols; ++col) { - int width = inlineItems.at(col)->width(); - if (sx < width) { - break; - } - sx -= width; - if (inlineItems.at(col)->hasRightSkip()) { - sx -= st::inlineResultsSkip; - } - } - if (col < inlineItems.size()) { - sel = row * MatrixRowShift + col; - inlineItems.at(col)->getState(lnk, cursor, sx, sy); - lnkhost = inlineItems.at(col); - } else { - row = col = -1; - } - } else { - row = col = -1; - } - int srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; - int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; - if (_selected != sel) { - if (srow >= 0 && scol >= 0) { - t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); - _inlineRows[srow].items[scol]->update(); - } - _selected = sel; - if (row >= 0 && col >= 0) { - t_assert(row >= 0 && row < _inlineRows.size() && col >= 0 && col < _inlineRows.at(row).items.size()); - _inlineRows[row].items[col]->update(); - } - if (_previewShown && _selected >= 0 && _pressed != _selected) { - _pressed = _selected; - if (row >= 0 && col >= 0) { - auto layout = _inlineRows.at(row).items.at(col); - if (auto previewDocument = layout->getPreviewDocument()) { - Ui::showMediaPreview(previewDocument); - } else if (auto previewPhoto = layout->getPreviewPhoto()) { - Ui::showMediaPreview(previewPhoto); - } - } - } - } - if (ClickHandler::setActive(lnk, lnkhost)) { - setCursor(lnk ? style::cur_pointer : style::cur_default); - } - return; - } - - auto newSelectedFeaturedSet = -1; - auto newSelectedFeaturedSetAdd = -1; - auto &sets = shownSets(); - int sx = (rtl() ? width() - p.x() : p.x()) - stickersLeft(); - if (_section == Section::Featured) { - auto yOffset = p.y() - st::stickerPanPadding; - auto section = (yOffset >= 0) ? (yOffset / featuredRowHeight()) : -1; - if (section >= 0 && section < sets.size()) { - yOffset -= section * featuredRowHeight(); - - auto &set = sets[section]; - if (yOffset < st::stickersTrendingHeader) { - if (featuredHasAddButton(section) && myrtlrect(featuredAddRect(section)).contains(p.x(), p.y())) { - newSelectedFeaturedSetAdd = section; - } else { - newSelectedFeaturedSet = section; - } - } else if (yOffset >= st::stickersTrendingHeader && yOffset < st::stickersTrendingHeader + st::stickerPanSize.height()) { - if (sx >= 0 && sx < kStickersPanelPerRow * st::stickerPanSize.width()) { - newSelected = qFloor(sx / st::stickerPanSize.width()); - if (newSelected >= set.pack.size()) { - newSelected = -1; - } else { - newSelected += section * MatrixRowShift; - } - } - } - } - } else if (!_mySets.empty()) { - auto info = sectionInfoByOffset(p.y()); - if (p.y() >= info.top && p.y() < info.rowsTop) { - // TODO selected header / delete - } else if (p.y() >= info.rowsTop && p.y() < info.rowsBottom) { - auto yOffset = p.y() - info.rowsTop; - auto &set = sets[info.section]; - auto special = ((set.flags & MTPDstickerSet::Flag::f_official) != 0); - auto rowIndex = qFloor(yOffset / st::stickerPanSize.height()); - newSelected = rowIndex * kStickersPanelPerRow + qFloor(sx / st::stickerPanSize.width()); - if (newSelected >= set.pack.size()) { - newSelected = -1; - } else { - if (set.id == Stickers::RecentSetId && _custom[newSelected]) { - auto inx = sx - (newSelected % kStickersPanelPerRow) * st::stickerPanSize.width(); - auto iny = yOffset - ((newSelected / kStickersPanelPerRow) * st::stickerPanSize.height()); - if (inx >= st::stickerPanSize.width() - st::stickerPanDelete.width() && iny < st::stickerPanDelete.height()) { - newSelected += set.pack.size(); - } - } - newSelected += info.section * MatrixRowShift; - } - } - } - - setSelected(newSelected, newSelectedFeaturedSet, newSelectedFeaturedSetAdd); -} - -void StickerPanInner::setSelected(int newSelected, int newSelectedFeaturedSet, int newSelectedFeaturedSetAdd) { - if (_selected != newSelected || _selectedFeaturedSet != newSelectedFeaturedSet || _selectedFeaturedSetAdd != newSelectedFeaturedSetAdd) { - setCursor((newSelected >= 0 || newSelectedFeaturedSet >= 0 || newSelectedFeaturedSetAdd >= 0) ? style::cur_pointer : style::cur_default); - } - if (_selected != newSelected) { - auto &sets = shownSets(); - auto updateSelected = [this, &sets]() { - if (_selected < 0) return; - auto section = _selected / MatrixRowShift; - auto sel = _selected % MatrixRowShift; - if (section < sets.size() && sel >= sets[section].pack.size()) { - sel -= sets[section].pack.size(); - } - rtlupdate(stickerRect(section, sel)); - }; - updateSelected(); - _selected = newSelected; - updateSelected(); - - if (_previewShown && _selected >= 0 && _pressed != _selected) { - _pressed = _selected; - auto section = _selected / MatrixRowShift; - auto sel = _selected % MatrixRowShift; - if (section < sets.size() && sel < sets[section].pack.size()) { - Ui::showMediaPreview(sets[section].pack[sel]); - } - } - } - if (_selectedFeaturedSet != newSelectedFeaturedSet) { - _selectedFeaturedSet = newSelectedFeaturedSet; - } - if (_selectedFeaturedSetAdd != newSelectedFeaturedSetAdd) { - _selectedFeaturedSetAdd = newSelectedFeaturedSetAdd; - update(); - } -} - -void StickerPanInner::onSettings() { - Ui::show(Box(StickersBox::Section::Installed)); -} - -void StickerPanInner::onPreview() { - if (_pressed < 0) return; - if (showingInlineItems()) { - int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; - if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { - auto layout = _inlineRows.at(row).items.at(col); - if (auto previewDocument = layout->getPreviewDocument()) { - Ui::showMediaPreview(previewDocument); - _previewShown = true; - } else if (auto previewPhoto = layout->getPreviewPhoto()) { - Ui::showMediaPreview(previewPhoto); - _previewShown = true; - } - } - } else { - auto &sets = shownSets(); - if (_pressed < MatrixRowShift * sets.size()) { - auto section = (_pressed / MatrixRowShift); - auto sel = _pressed % MatrixRowShift; - if (sel < sets[section].pack.size()) { - Ui::showMediaPreview(sets[section].pack[sel]); - _previewShown = true; - } - } - } -} - -void StickerPanInner::onUpdateInlineItems() { - if (!showingInlineItems()) return; - - auto ms = getms(); - if (_lastScrolled + 100 <= ms) { - update(); - } else { - _updateInlineItems.start(_lastScrolled + 100 - ms); - } -} - -void StickerPanInner::onSwitchPm() { - if (_inlineBot && _inlineBot->botInfo) { - _inlineBot->botInfo->startToken = _switchPmStartToken; - Ui::showPeerHistory(_inlineBot, ShowAndStartBotMsgId); - } -} - -void StickerPanInner::showStickerSet(uint64 setId) { - clearSelection(); - - if (setId == Stickers::NoneSetId) { - refreshSavedGifs(); - emit scrollToY(0); - emit scrollUpdated(); - return; - } - - if (showingInlineItems()) { - Notify::clipStopperHidden(ClipStopperSavedGifsPanel); - } - - if (setId == Stickers::FeaturedSetId) { - if (_section != Section::Featured) { - _section = Section::Featured; - - refreshRecentStickers(true); - emit refreshIcons(kRefreshIconsScrollAnimation); - update(); - } - - emit scrollToY(0); - emit scrollUpdated(); - return; - } - - auto needRefresh = (_section != Section::Stickers); - if (needRefresh) { - _section = Section::Stickers; - refreshRecentStickers(true); - } - - auto y = 0; - enumerateSections([this, setId, &y](const SectionInfo &info) { - if (_mySets[info.section].id == setId) { - y = info.top; - return false; - } - return true; - }); - emit scrollToY(y); - emit scrollUpdated(); - - if (needRefresh) { - emit refreshIcons(kRefreshIconsScrollAnimation); - } - - _lastMousePos = QCursor::pos(); - - update(); -} - -} // namespace internal - -namespace { - -FORCE_INLINE uint32 oneImageOnBgWithAlpha( - const anim::Shifted shiftedBg, - const uint32 sourceAlpha, - const uint32 source, - const uint32 alpha) { - auto sourcePattern = anim::reshifted(anim::shifted(source) * sourceAlpha); - auto bgAlpha = 256 - anim::getAlpha(sourcePattern); - auto mixedPattern = anim::reshifted(shiftedBg * bgAlpha) + sourcePattern; - return anim::unshifted(mixedPattern * alpha); -}; } // namespace @@ -2312,7 +81,7 @@ private: }; void EmojiPanel::SlideAnimation::setFinalImages(Direction direction, QImage &&left, QImage &&right, QRect inner) { - t_assert(!started()); + Expects(!started()); _direction = direction; _leftImage = QPixmap::fromImage(std::move(left).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly); _rightImage = QPixmap::fromImage(std::move(right).convertToFormat(QImage::Format_ARGB32_Premultiplied), Qt::ColorOnly); @@ -2494,17 +263,17 @@ void EmojiPanel::SlideAnimation::paintFrame(QPainter &p, float64 dt, float64 opa p.drawImage(outerLeft / cIntRetinaFactor(), outerTop / cIntRetinaFactor(), _frame, outerLeft, outerTop, outerRight - outerLeft, outerBottom - outerTop); } -EmojiPanel::Tab::Tab(TabType type, object_ptr widget) +EmojiPanel::Tab::Tab(TabType type, object_ptr widget) : _type(type) , _widget(std::move(widget)) , _weak(_widget) { } -object_ptr EmojiPanel::Tab::takeWidget() { +object_ptr EmojiPanel::Tab::takeWidget() { return std::move(_widget); } -void EmojiPanel::Tab::returnWidget(object_ptr widget) { +void EmojiPanel::Tab::returnWidget(object_ptr widget) { _widget = std::move(widget); Ensures(_widget == _weak); } @@ -2528,9 +297,9 @@ EmojiPanel::EmojiPanel(QWidget *parent) : TWidget(parent) , _topShadow(this, st::shadowFg) , _bottomShadow(this, st::shadowFg) , _tabs { - Tab { TabType::Emoji, object_ptr(this) }, - Tab { TabType::Stickers, object_ptr(this, false) }, - Tab { TabType::Gifs, object_ptr(this, true) }, + Tab { TabType::Emoji, object_ptr(this) }, + Tab { TabType::Stickers, object_ptr(this) }, + Tab { TabType::Gifs, object_ptr(this) }, } , _currentTabType(AuthSession::Current().data().emojiPanelTab()) { resize(QRect(0, 0, st::emojiPanWidth, st::emojiPanMaxHeight).marginsAdded(innerPadding()).size()); @@ -2565,14 +334,14 @@ EmojiPanel::EmojiPanel(QWidget *parent) : TWidget(parent) connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideByTimerOrLeave())); for (auto &tab : _tabs) { - connect(tab.widget(), &internal::BasicPanInner::scrollToY, this, [this, tab = &tab](int y) { + connect(tab.widget(), &Inner::scrollToY, this, [this, tab = &tab](int y) { if (tab == currentTab()) { _scroll->scrollToY(y); } else { tab->saveScrollTop(y); } }); - connect(tab.widget(), &internal::BasicPanInner::disableScroll, this, [this, tab = &tab](bool disabled) { + connect(tab.widget(), &Inner::disableScroll, this, [this, tab = &tab](bool disabled) { if (tab == currentTab()) { _scroll->disableScroll(disabled); } @@ -3023,7 +792,7 @@ void EmojiPanel::hideFast() { void EmojiPanel::refreshStickers() { stickers()->refreshStickers(); - if (_currentTabType != TabType::Stickers) { + if (isHidden() || _currentTabType != TabType::Stickers) { stickers()->preloadImages(); } update(); @@ -3031,9 +800,10 @@ void EmojiPanel::refreshStickers() { void EmojiPanel::refreshSavedGifs() { gifs()->refreshSavedGifs(); - if (_currentTabType != TabType::Gifs) { + if (isHidden() || _currentTabType != TabType::Gifs) { gifs()->preloadImages(); } + update(); } void EmojiPanel::onRefreshIcons(bool scrollAnimation) { @@ -3428,15 +1198,17 @@ QRect EmojiPanel::verticalRect() const { } void EmojiPanel::createTabsSlider() { - _tabsSlider->setSectionActivatedCallback([this] { - switchTab(); - }); auto sections = QStringList(); sections.push_back(lang(lng_switch_emoji).toUpper()); sections.push_back(lang(lng_switch_stickers).toUpper()); sections.push_back(lang(lng_switch_gifs).toUpper()); _tabsSlider->setSections(sections); + _tabsSlider->setActiveSectionFast(static_cast(_currentTabType)); + _tabsSlider->setSectionActivatedCallback([this] { + switchTab(); + }); + _tabsSlider->resizeToWidth(innerRect().width()); _tabsSlider->moveToLeft(innerRect().x(), innerRect().y()); _topShadow->setGeometry(_tabsSlider->x(), _tabsSlider->bottomNoMargins() - st::lineWidth, _tabsSlider->width(), st::lineWidth); @@ -3449,16 +1221,13 @@ void EmojiPanel::switchTab() { if (_currentTabType == newTabType) { return; } - if (newTabType == TabType::Gifs) { - gifs()->showStickerSet(Stickers::NoneSetId); - } auto wasTab = _currentTabType; currentTab()->saveScrollTop(); auto wasCache = grabForComplexAnimation(GrabType::Slide); - auto widget = _scroll->takeWidget(); + auto widget = _scroll->takeWidget(); widget->setParent(this); widget->hide(); currentTab()->returnWidget(std::move(widget)); @@ -3498,7 +1267,19 @@ void EmojiPanel::switchTab() { update(); AuthSession::Current().data().setEmojiPanelTab(_currentTabType); - onSaveConfigDelayed(internal::kSaveChosenTabTimeout); + onSaveConfigDelayed(kSaveChosenTabTimeout); +} + +gsl::not_null EmojiPanel::emoji() const { + return static_cast(getTab(TabType::Emoji)->widget().get()); +} + +gsl::not_null EmojiPanel::stickers() const { + return static_cast(getTab(TabType::Stickers)->widget().get()); +} + +gsl::not_null EmojiPanel::gifs() const { + return static_cast(getTab(TabType::Gifs)->widget().get()); } void EmojiPanel::setWidgetToScrollArea() { @@ -3635,7 +1416,7 @@ void EmojiPanel::inlineResultsDone(const MTPmessages_BotResults &result) { auto queryId = d.vquery_id.v; if (it == _inlineCache.cend()) { - it = _inlineCache.emplace(_inlineQuery, std::make_unique()).first; + it = _inlineCache.emplace(_inlineQuery, std::make_unique()).first; } auto entry = it->second.get(); entry->nextOffset = qs(d.vnext_offset); @@ -3731,7 +1512,7 @@ void EmojiPanel::onEmptyInlineRows() { bool EmojiPanel::refreshInlineRows(int32 *added) { auto it = _inlineCache.find(_inlineQuery); - const internal::InlineCacheEntry *entry = nullptr; + const InlineCacheEntry *entry = nullptr; if (it != _inlineCache.cend()) { if (!it->second->results.empty() || !it->second->switchPmText.isEmpty()) { entry = it->second.get(); @@ -3765,3 +1546,18 @@ int32 EmojiPanel::showInlineRows(bool newResults) { return added; } + +EmojiPanel::Inner::Inner(QWidget *parent) : TWidget(parent) { +} + +void EmojiPanel::Inner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + auto oldVisibleHeight = getVisibleBottom() - getVisibleTop(); + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; + auto visibleHeight = getVisibleBottom() - getVisibleTop(); + if (visibleHeight != oldVisibleHeight) { + resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, countHeight()); + } +} + +} // namespace ChatHelpers diff --git a/Telegram/SourceFiles/stickers/emoji_panel.h b/Telegram/SourceFiles/stickers/emoji_panel.h index 79e892f2cb..65096e8302 100644 --- a/Telegram/SourceFiles/stickers/emoji_panel.h +++ b/Telegram/SourceFiles/stickers/emoji_panel.h @@ -44,14 +44,11 @@ class RippleAnimation; class SettingsSlider; } // namesapce Ui -namespace internal { +namespace ChatHelpers { -constexpr auto kInlineItemsMaxPerRow = 5; - -constexpr auto kEmojiSectionCount = 8; -inline DBIEmojiSection EmojiSectionAtIndex(int index) { - return (index < 0 || index >= kEmojiSectionCount) ? dbiesRecent : DBIEmojiSection(index - 1); -} +class EmojiListWidget; +class StickersListWidget; +class GifsListWidget; using InlineResult = InlineBots::Result; using InlineResults = std::vector>; @@ -63,173 +60,6 @@ struct InlineCacheEntry { InlineResults results; }; -class EmojiColorPicker : public TWidget { - Q_OBJECT - -public: - EmojiColorPicker(QWidget *parent); - - void showEmoji(EmojiPtr emoji); - - void clearSelection(); - void handleMouseMove(QPoint globalPos); - void handleMouseRelease(QPoint globalPos); - - void hideFast(); - -public slots: - void showAnimated(); - void hideAnimated(); - -signals: - void emojiSelected(EmojiPtr emoji); - void hidden(); - -protected: - void paintEvent(QPaintEvent *e) override; - void enterEventHook(QEvent *e) override; - void leaveEventHook(QEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - -private: - void animationCallback(); - - void drawVariant(Painter &p, int variant); - - void updateSelected(); - void setSelected(int newSelected); - - bool _ignoreShow = false; - - QVector _variants; - - int _selected = -1; - int _pressedSel = -1; - QPoint _lastMousePos; - - bool _hiding = false; - QPixmap _cache; - Animation _a_opacity; - - QTimer _hideTimer; - -}; - -class BasicPanInner : public TWidget { - Q_OBJECT - -public: - BasicPanInner(QWidget *parent); - - void setVisibleTopBottom(int visibleTop, int visibleBottom) override; - - int getVisibleTop() const { - return _visibleTop; - } - int getVisibleBottom() const { - return _visibleBottom; - } - - virtual void refreshRecent() = 0; - virtual void preloadImages() { - } - virtual void hideFinish(bool completely) = 0; - virtual void clearSelection() = 0; - - virtual object_ptr createController() = 0; - -signals: - void scrollToY(int y); - void disableScroll(bool disabled); - void saveConfigDelayed(int delay); - -protected: - virtual int countHeight() = 0; - -private: - int _visibleTop = 0; - int _visibleBottom = 0; - -}; - -class EmojiPanInner : public BasicPanInner { - Q_OBJECT - -public: - EmojiPanInner(QWidget *parent); - - void refreshRecent() override; - void hideFinish(bool completely) override; - void clearSelection() override; - object_ptr createController() override; - - void showEmojiSection(DBIEmojiSection section); - DBIEmojiSection currentSection(int yOffset) const; - -public slots: - void onShowPicker(); - void onPickerHidden(); - void onColorSelected(EmojiPtr emoji); - - bool checkPickerHide(); - -signals: - void selected(EmojiPtr emoji); - void switchToStickers(); - -protected: - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void leaveEventHook(QEvent *e) override; - void leaveToChildEvent(QEvent *e, QWidget *child) override; - void enterFromChildEvent(QEvent *e, QWidget *child) override; - bool event(QEvent *e) override; - int countHeight() override; - -private: - class Controller; - - struct SectionInfo { - int section = 0; - int count = 0; - int top = 0; - int rowsCount = 0; - int rowsTop = 0; - int rowsBottom = 0; - }; - template - bool enumerateSections(Callback callback) const; - SectionInfo sectionInfo(int section) const; - SectionInfo sectionInfoByOffset(int yOffset) const; - - void ensureLoaded(int section); - int countSectionTop(int section) const; - void updateSelected(); - void setSelected(int newSelected); - - void selectEmoji(EmojiPtr emoji); - - QRect emojiRect(int section, int sel); - - int _counts[kEmojiSectionCount]; - QVector _emoji[kEmojiSectionCount]; - - int32 _esize; - - int _selected = -1; - int _pressedSel = -1; - int _pickerSel = -1; - QPoint _lastMousePos; - - object_ptr _picker; - QTimer _showPickerTimer; - -}; - struct StickerIcon { StickerIcon(uint64 setId) : setId(setId) { } @@ -242,209 +72,6 @@ struct StickerIcon { }; -class StickerPanInner : public BasicPanInner, public InlineBots::Layout::Context, private base::Subscriber { - Q_OBJECT - -public: - StickerPanInner(QWidget *parent, bool gifs); - - void refreshRecent() override; - void preloadImages() override; - void hideFinish(bool completely) override; - void clearSelection() override; - object_ptr createController() override; - - void showStickerSet(uint64 setId); - - void refreshStickers(); - void refreshRecentStickers(bool resize = true); - void refreshSavedGifs(); - int refreshInlineRows(UserData *bot, const InlineCacheEntry *results, bool resultsDeleted); - void inlineBotChanged(); - void hideInlineRowsPanel(); - void clearInlineRowsPanel(); - - void fillIcons(QList &icons); - - void setVisibleTopBottom(int visibleTop, int visibleBottom) override; - - uint64 currentSet(int yOffset) const; - - void inlineItemLayoutChanged(const InlineItem *layout) override; - void inlineItemRepaint(const InlineItem *layout) override; - bool inlineItemVisible(const InlineItem *layout) override; - - void installedLocally(uint64 setId); - void notInstalledLocally(uint64 setId); - void clearInstalledLocally(); - - ~StickerPanInner(); - -protected: - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void leaveEventHook(QEvent *e) override; - void leaveToChildEvent(QEvent *e, QWidget *child) override; - void enterFromChildEvent(QEvent *e, QWidget *child) override; - int countHeight() override; - -private slots: - void onSettings(); - void onPreview(); - void onUpdateInlineItems(); - void onSwitchPm(); - -signals: - void selected(DocumentData *sticker); - void selected(PhotoData *photo); - void selected(InlineBots::Result *result, UserData *bot); - - void displaySet(quint64 setId); - void installSet(quint64 setId); - void removeSet(quint64 setId); - - void refreshIcons(bool scrollAnimation); - void emptyInlineRows(); - void scrollUpdated(); - -private: - enum class Section { - Inlines, - Gifs, - Featured, - Stickers, - }; - class Controller; - - static constexpr auto kRefreshIconsScrollAnimation = true; - static constexpr auto kRefreshIconsNoAnimation = false; - - struct SectionInfo { - int section = 0; - int count = 0; - int top = 0; - int rowsCount = 0; - int rowsTop = 0; - int rowsBottom = 0; - }; - template - bool enumerateSections(Callback callback) const; - SectionInfo sectionInfo(int section) const; - SectionInfo sectionInfoByOffset(int yOffset) const; - - void updateSelected(); - void setSelected(int newSelected, int newSelectedFeaturedSet, int newSelectedFeaturedSetAdd); - - void setPressedFeaturedSetAdd(int newPressedFeaturedSetAdd); - - struct Set { - Set(uint64 id, MTPDstickerSet::Flags flags, const QString &title, int32 hoversSize, const StickerPack &pack = StickerPack()) : id(id), flags(flags), title(title), pack(pack) { - } - uint64 id; - MTPDstickerSet::Flags flags; - QString title; - StickerPack pack; - QSharedPointer ripple; - }; - using Sets = QList; - Sets &shownSets() { - return (_section == Section::Featured) ? _featuredSets : _mySets; - } - const Sets &shownSets() const { - return const_cast(this)->shownSets(); - } - int featuredRowHeight() const; - void readVisibleSets(); - - bool showingInlineItems() const { // Gifs or Inline results - return (_section == Section::Inlines) || (_section == Section::Gifs); - } - - void paintInlineItems(Painter &p, QRect clip); - void paintFeaturedStickers(Painter &p, QRect clip); - void paintStickers(Painter &p, QRect clip); - void paintSticker(Painter &p, Set &set, int y, int index, bool selected, bool deleteSelected); - bool featuredHasAddButton(int index) const; - int featuredContentWidth() const; - QRect featuredAddRect(int y) const; - - void refreshSwitchPmButton(const InlineCacheEntry *entry); - - enum class AppendSkip { - Archived, - Installed, - }; - void appendSet(Sets &to, uint64 setId, AppendSkip skip); - - void selectEmoji(EmojiPtr emoji); - int stickersLeft() const; - QRect stickerRect(int section, int sel); - - Sets _mySets; - Sets _featuredSets; - OrderedSet _installedLocallySets; - QList _custom; - - Section _section = Section::Stickers; - UserData *_inlineBot; - QString _inlineBotTitle; - TimeMs _lastScrolled = 0; - QTimer _updateInlineItems; - bool _inlineWithThumb = false; - - object_ptr _switchPmButton = { nullptr }; - QString _switchPmStartToken; - - typedef QVector InlineItems; - struct InlineRow { - int height = 0; - InlineItems items; - }; - typedef QVector InlineRows; - InlineRows _inlineRows; - void clearInlineRows(bool resultsDeleted); - - std::map> _gifLayouts; - InlineItem *layoutPrepareSavedGif(DocumentData *doc, int32 position); - - std::map> _inlineLayouts; - InlineItem *layoutPrepareInlineResult(InlineResult *result, int32 position); - - bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth); - bool inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force = false); - - InlineRow &layoutInlineRow(InlineRow &row, int32 sumWidth = 0); - void deleteUnusedGifLayouts(); - - void deleteUnusedInlineLayouts(); - - int validateExistingInlineRows(const InlineResults &results); - void selectInlineResult(int row, int column); - void removeRecentSticker(int section, int index); - - int _selected = -1; - int _pressed = -1; - int _selectedFeaturedSet = -1; - int _pressedFeaturedSet = -1; - int _selectedFeaturedSetAdd = -1; - int _pressedFeaturedSetAdd = -1; - QPoint _lastMousePos; - - QString _addText; - int _addWidth; - - object_ptr _settings; - - QTimer _previewTimer; - bool _previewShown = false; - -}; - -} // namespace internal - class EmojiPanel : public TWidget, private MTP::Sender { Q_OBJECT @@ -478,6 +105,8 @@ public: ~EmojiPanel(); + class Inner; + protected: void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; @@ -495,7 +124,7 @@ protected: public slots: void refreshStickers(); -private slots: + private slots: void hideByTimerOrLeave(); void refreshSavedGifs(); @@ -529,15 +158,15 @@ private: public: static constexpr auto kCount = 3; - Tab(TabType type, object_ptr widget); + Tab(TabType type, object_ptr widget); - object_ptr takeWidget(); - void returnWidget(object_ptr widget); + object_ptr takeWidget(); + void returnWidget(object_ptr widget); TabType type() const { return _type; } - gsl::not_null widget() const { + gsl::not_null widget() const { return _weak; } @@ -551,8 +180,8 @@ private: private: TabType _type = TabType::Emoji; - object_ptr _widget = { nullptr }; - QPointer _weak; + object_ptr _widget = { nullptr }; + QPointer _weak; int _scrollTop = 0; }; @@ -629,15 +258,9 @@ private: gsl::not_null currentTab() const { return getTab(_currentTabType); } - gsl::not_null emoji() const { - return static_cast(getTab(TabType::Emoji)->widget().get()); - } - gsl::not_null stickers() const { - return static_cast(getTab(TabType::Stickers)->widget().get()); - } - gsl::not_null gifs() const { - return static_cast(getTab(TabType::Gifs)->widget().get()); - } + gsl::not_null emoji() const; + gsl::not_null stickers() const; + gsl::not_null gifs() const; int _minTop = 0; int _minBottom = 0; @@ -672,7 +295,7 @@ private: object_ptr _objects; object_ptr _symbols; - QList _icons; + QList _icons; int _iconOver = -1; int _iconSel = 0; int _iconDown = -1; @@ -700,7 +323,7 @@ private: QTimer _saveConfigTimer; // inline bots - std::map> _inlineCache; + std::map> _inlineCache; QTimer _inlineRequestTimer; void inlineBotChanged(); @@ -713,3 +336,42 @@ private: void inlineResultsDone(const MTPmessages_BotResults &result); }; + +class EmojiPanel::Inner : public TWidget { + Q_OBJECT + +public: + Inner(QWidget *parent); + + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + + int getVisibleTop() const { + return _visibleTop; + } + int getVisibleBottom() const { + return _visibleBottom; + } + + virtual void refreshRecent() = 0; + virtual void preloadImages() { + } + virtual void hideFinish(bool completely) = 0; + virtual void clearSelection() = 0; + + virtual object_ptr createController() = 0; + +signals: + void scrollToY(int y); + void disableScroll(bool disabled); + void saveConfigDelayed(int delay); + +protected: + virtual int countHeight() = 0; + +private: + int _visibleTop = 0; + int _visibleBottom = 0; + +}; + +} // namespace ChatHelpers diff --git a/Telegram/SourceFiles/stickers/gifs_list_widget.cpp b/Telegram/SourceFiles/stickers/gifs_list_widget.cpp new file mode 100644 index 0000000000..28bab930df --- /dev/null +++ b/Telegram/SourceFiles/stickers/gifs_list_widget.cpp @@ -0,0 +1,764 @@ +/* +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/gifs_list_widget.h" + +#include "styles/style_stickers.h" +#include "ui/widgets/buttons.h" +#include "ui/effects/ripple_animation.h" +#include "boxes/stickers_box.h" +#include "inline_bots/inline_bot_result.h" +#include "stickers/stickers.h" +#include "storage/localstorage.h" +#include "lang.h" +#include "mainwindow.h" + +namespace ChatHelpers { +namespace { + +constexpr auto kSaveChosenTabTimeout = 1000; +constexpr auto kStickersPanelPerRow = Stickers::kPanelPerRow; +constexpr auto kInlineItemsMaxPerRow = 5; + +} // namespace + +class GifsListWidget::Controller : public TWidget { +public: + Controller(gsl::not_null parent); + +private: + gsl::not_null _pan; + +}; + +GifsListWidget::Controller::Controller(gsl::not_null parent) : TWidget(parent) +, _pan(parent) { +} + +GifsListWidget::GifsListWidget(QWidget *parent) : Inner(parent) +, _section(Section::Gifs) { + resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, countHeight()); + + setMouseTracking(true); + setAttribute(Qt::WA_OpaquePaintEvent); + + _previewTimer.setSingleShot(true); + connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); + + _updateInlineItems.setSingleShot(true); + connect(&_updateInlineItems, SIGNAL(timeout()), this, SLOT(onUpdateInlineItems())); + + subscribe(AuthSession::CurrentDownloaderTaskFinished(), [this] { + update(); + }); +} + +object_ptr GifsListWidget::createController() { + return object_ptr(this); +} + +void GifsListWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { + auto top = getVisibleTop(); + Inner::setVisibleTopBottom(visibleTop, visibleBottom); + if (top != getVisibleTop()) { + _lastScrolled = getms(); + } +} + +int GifsListWidget::countHeight() { + auto visibleHeight = getVisibleBottom() - getVisibleTop(); + if (visibleHeight <= 0) { + visibleHeight = st::emojiPanMaxHeight - st::emojiCategory.height; + } + auto minimalLastHeight = (visibleHeight - st::stickerPanPadding); + auto result = st::stickerPanPadding; + if (_switchPmButton) { + result += _switchPmButton->height() + st::inlineResultsSkip; + } + for (int i = 0, l = _inlineRows.count(); i < l; ++i) { + result += _inlineRows[i].height; + } + return qMax(minimalLastHeight, result) + st::stickerPanPadding; +} + +GifsListWidget::~GifsListWidget() { + clearInlineRows(true); + deleteUnusedGifLayouts(); + deleteUnusedInlineLayouts(); +} + +void GifsListWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + auto clip = e->rect(); + p.fillRect(clip, st::emojiPanBg); + + paintInlineItems(p, clip); +} + +void GifsListWidget::paintInlineItems(Painter &p, QRect clip) { + if (_inlineRows.isEmpty() && !_switchPmButton) { + p.setFont(st::normalFont); + p.setPen(st::noContactsColor); + p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), lang(lng_inline_bot_no_results), style::al_center); + return; + } + auto gifPaused = Ui::isLayerShown() || Ui::isMediaViewShown() || _previewShown || !App::wnd()->isActive(); + InlineBots::Layout::PaintContext context(getms(), false, gifPaused, false); + + auto top = st::stickerPanPadding; + if (_switchPmButton) { + top += _switchPmButton->height() + st::inlineResultsSkip; + } + + auto fromx = rtl() ? (width() - clip.x() - clip.width()) : clip.x(); + auto tox = rtl() ? (width() - clip.x()) : (clip.x() + clip.width()); + for (auto row = 0, rows = _inlineRows.size(); row != rows; ++row) { + auto &inlineRow = _inlineRows[row]; + if (top >= clip.top() + clip.height()) { + break; + } + if (top + inlineRow.height > clip.top()) { + auto left = st::inlineResultsLeft - st::buttonRadius; + if (row == rows - 1) context.lastRow = true; + for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { + if (left >= tox) break; + + auto item = inlineRow.items.at(col); + auto w = item->width(); + if (left + w > fromx) { + p.translate(left, top); + item->paint(p, clip.translated(-left, -top), &context); + p.translate(-left, -top); + } + left += w; + if (item->hasRightSkip()) { + left += st::inlineResultsSkip; + } + } + } + top += inlineRow.height; + } +} + +void GifsListWidget::mousePressEvent(QMouseEvent *e) { + if (e->button() != Qt::LeftButton) { + return; + } + _lastMousePos = e->globalPos(); + updateSelected(); + + _pressed = _selected; + ClickHandler::pressed(); + _previewTimer.start(QApplication::startDragTime()); +} + +void GifsListWidget::mouseReleaseEvent(QMouseEvent *e) { + _previewTimer.stop(); + + auto pressed = std::exchange(_pressed, -1); + auto activated = ClickHandler::unpressed(); + + if (_previewShown) { + _previewShown = false; + return; + } + + _lastMousePos = e->globalPos(); + updateSelected(); + + if (_selected < 0 || _selected != pressed || !activated) { + return; + } + + if (dynamic_cast(activated.data())) { + int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift; + selectInlineResult(row, column); + } else { + App::activateClickHandler(activated, e->button()); + } +} + +void GifsListWidget::selectInlineResult(int row, int column) { + if (row >= _inlineRows.size() || column >= _inlineRows.at(row).items.size()) { + return; + } + + auto item = _inlineRows[row].items[column]; + if (auto photo = item->getPhoto()) { + if (photo->medium->loaded() || photo->thumb->loaded()) { + emit selected(photo); + } else if (!photo->medium->loading()) { + photo->thumb->loadEvenCancelled(); + photo->medium->loadEvenCancelled(); + } + } else if (auto document = item->getDocument()) { + if (document->loaded()) { + emit selected(document); + } else if (document->loading()) { + document->cancel(); + } else { + DocumentOpenClickHandler::doOpen(document, nullptr, ActionOnLoadNone); + } + } else if (auto inlineResult = item->getResult()) { + if (inlineResult->onChoose(item)) { + emit selected(inlineResult, _inlineBot); + } + } +} + +void GifsListWidget::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); +} + +void GifsListWidget::leaveEventHook(QEvent *e) { + clearSelection(); +} + +void GifsListWidget::leaveToChildEvent(QEvent *e, QWidget *child) { + clearSelection(); +} + +void GifsListWidget::enterFromChildEvent(QEvent *e, QWidget *child) { + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +void GifsListWidget::clearSelection() { + if (_selected >= 0) { + int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; + t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); + ClickHandler::clearActive(_inlineRows.at(srow).items.at(scol)); + setCursor(style::cur_default); + } + _selected = _pressed = -1; + update(); +} + +void GifsListWidget::hideFinish(bool completely) { + clearSelection(); + if (completely) { + auto itemForget = [](auto &item) { + if (auto document = item->getDocument()) { + document->forget(); + } + if (auto photo = item->getPhoto()) { + photo->forget(); + } + if (auto result = item->getResult()) { + result->forget(); + } + }; + clearInlineRows(false); + for_const (auto &item, _gifLayouts) { + itemForget(item.second); + } + for_const (auto &item, _inlineLayouts) { + itemForget(item.second); + } + } +} + +bool GifsListWidget::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth) { + InlineItem *layout = nullptr; + if (savedGif) { + layout = layoutPrepareSavedGif(savedGif, (_inlineRows.size() * MatrixRowShift) + row.items.size()); + } else if (result) { + layout = layoutPrepareInlineResult(result, (_inlineRows.size() * MatrixRowShift) + row.items.size()); + } + if (!layout) return false; + + layout->preload(); + if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { + layout->setPosition(_inlineRows.size() * MatrixRowShift); + } + + sumWidth += layout->maxWidth(); + if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { + sumWidth += st::inlineResultsSkip; + } + + row.items.push_back(layout); + return true; +} + +bool GifsListWidget::inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force) { + if (row.items.isEmpty()) return false; + + auto full = (row.items.size() >= kInlineItemsMaxPerRow); + auto big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); + if (full || big || force) { + _inlineRows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); + row = InlineRow(); + row.items.reserve(kInlineItemsMaxPerRow); + sumWidth = 0; + return true; + } + return false; +} + +void GifsListWidget::refreshSavedGifs() { + if (_section == Section::Gifs) { + clearInlineRows(false); + + auto &saved = cSavedGifs(); + if (!saved.isEmpty()) { + _inlineRows.reserve(saved.size()); + auto row = InlineRow(); + row.items.reserve(kInlineItemsMaxPerRow); + auto sumWidth = 0; + for_const (auto &gif, saved) { + inlineRowsAddItem(gif, 0, row, sumWidth); + } + inlineRowFinalize(row, sumWidth, true); + } + deleteUnusedGifLayouts(); + + int32 h = countHeight(); + if (h != height()) resize(width(), h); + + update(); + } + updateSelected(); +} + +void GifsListWidget::inlineBotChanged() { + refreshInlineRows(nullptr, nullptr, true); +} + +void GifsListWidget::clearInlineRows(bool resultsDeleted) { + if (resultsDeleted) { + _selected = _pressed = -1; + } else { + clearSelection(); + for_const (auto &row, _inlineRows) { + for_const (auto &item, row.items) { + item->setPosition(-1); + } + } + } + _inlineRows.clear(); +} + +InlineItem *GifsListWidget::layoutPrepareSavedGif(DocumentData *doc, int32 position) { + auto it = _gifLayouts.find(doc); + if (it == _gifLayouts.cend()) { + if (auto layout = InlineItem::createLayoutGif(this, doc)) { + it = _gifLayouts.emplace(doc, std::move(layout)).first; + it->second->initDimensions(); + } else { + return nullptr; + } + } + if (!it->second->maxWidth()) return nullptr; + + it->second->setPosition(position); + return it->second.get(); +} + +InlineItem *GifsListWidget::layoutPrepareInlineResult(InlineResult *result, int32 position) { + auto it = _inlineLayouts.find(result); + if (it == _inlineLayouts.cend()) { + if (auto layout = InlineItem::createLayout(this, result, _inlineWithThumb)) { + it = _inlineLayouts.emplace(result, std::move(layout)).first; + it->second->initDimensions(); + } else { + return nullptr; + } + } + if (!it->second->maxWidth()) return nullptr; + + it->second->setPosition(position); + return it->second.get(); +} + +void GifsListWidget::deleteUnusedGifLayouts() { + if (_inlineRows.isEmpty() || _section != Section::Gifs) { // delete all + _gifLayouts.clear(); + } else { + for (auto i = _gifLayouts.begin(); i != _gifLayouts.cend();) { + if (i->second->position() < 0) { + i = _gifLayouts.erase(i); + } else { + ++i; + } + } + } +} + +void GifsListWidget::deleteUnusedInlineLayouts() { + if (_inlineRows.isEmpty() || _section == Section::Gifs) { // delete all + _inlineLayouts.clear(); + } else { + for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { + if (i->second->position() < 0) { + i = _inlineLayouts.erase(i); + } else { + ++i; + } + } + } +} + +GifsListWidget::InlineRow &GifsListWidget::layoutInlineRow(InlineRow &row, int32 sumWidth) { + auto count = int(row.items.size()); + t_assert(count <= kInlineItemsMaxPerRow); + + // enumerate items in the order of growing maxWidth() + // for that sort item indices by maxWidth() + int indices[kInlineItemsMaxPerRow]; + for (auto i = 0; i != count; ++i) { + indices[i] = i; + } + std::sort(indices, indices + count, [&row](int a, int b) -> bool { + return row.items.at(a)->maxWidth() < row.items.at(b)->maxWidth(); + }); + + row.height = 0; + int availw = width() - (st::inlineResultsLeft - st::buttonRadius); + for (int i = 0; i < count; ++i) { + int index = indices[i]; + int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth(); + int actualw = qMax(w, int(st::inlineResultsMinWidth)); + row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw)); + if (sumWidth) { + availw -= actualw; + sumWidth -= row.items.at(index)->maxWidth(); + if (index > 0 && row.items.at(index - 1)->hasRightSkip()) { + availw -= st::inlineResultsSkip; + sumWidth -= st::inlineResultsSkip; + } + } + } + return row; +} + +void GifsListWidget::preloadImages() { + for (auto row = 0, rows = _inlineRows.size(); row != rows; ++row) { + for (auto col = 0, cols = _inlineRows[row].items.size(); col != cols; ++col) { + _inlineRows[row].items[col]->preload(); + } + } +} + +void GifsListWidget::hideInlineRowsPanel() { + clearInlineRows(false); + _section = Section::Gifs; + refreshSavedGifs(); + emit scrollToY(0); + emit scrollUpdated(); +} + +void GifsListWidget::clearInlineRowsPanel() { + clearInlineRows(false); +} + +void GifsListWidget::refreshSwitchPmButton(const InlineCacheEntry *entry) { + if (!entry || entry->switchPmText.isEmpty()) { + _switchPmButton.destroy(); + _switchPmStartToken.clear(); + } else { + if (!_switchPmButton) { + _switchPmButton.create(this, QString(), st::switchPmButton); + _switchPmButton->show(); + _switchPmButton->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + connect(_switchPmButton, SIGNAL(clicked()), this, SLOT(onSwitchPm())); + } + _switchPmButton->setText(entry->switchPmText); // doesn't perform text.toUpper() + _switchPmStartToken = entry->switchPmStartToken; + auto buttonTop = st::stickerPanPadding; + _switchPmButton->move(st::inlineResultsLeft - st::buttonRadius, buttonTop); + } + update(); +} + +int GifsListWidget::refreshInlineRows(UserData *bot, const InlineCacheEntry *entry, bool resultsDeleted) { + _inlineBot = bot; + refreshSwitchPmButton(entry); + auto clearResults = [this, entry]() { + if (!entry) { + return true; + } + if (entry->results.empty() && entry->switchPmText.isEmpty()) { + if (!_inlineBot) { + return true; + } + } + return false; + }; + if (clearResults()) { + if (resultsDeleted) { + clearInlineRows(true); + deleteUnusedInlineLayouts(); + } + emit emptyInlineRows(); + return 0; + } + + clearSelection(); + + t_assert(_inlineBot != 0); + _inlineBotTitle = lng_inline_bot_results(lt_inline_bot, _inlineBot->username.isEmpty() ? _inlineBot->name : ('@' + _inlineBot->username)); + + _section = Section::Inlines; + auto count = int(entry->results.size()); + auto from = validateExistingInlineRows(entry->results); + auto added = 0; + + if (count) { + _inlineRows.reserve(count); + auto row = InlineRow(); + row.items.reserve(kInlineItemsMaxPerRow); + auto sumWidth = 0; + for (auto i = from; i != count; ++i) { + if (inlineRowsAddItem(0, entry->results[i].get(), row, sumWidth)) { + ++added; + } + } + inlineRowFinalize(row, sumWidth, true); + } + + int32 h = countHeight(); + if (h != height()) resize(width(), h); + update(); + + _lastMousePos = QCursor::pos(); + updateSelected(); + + return added; +} + +int GifsListWidget::validateExistingInlineRows(const InlineResults &results) { + int count = results.size(), until = 0, untilrow = 0, untilcol = 0; + for (; until < count;) { + if (untilrow >= _inlineRows.size() || _inlineRows[untilrow].items[untilcol]->getResult() != results[until].get()) { + break; + } + ++until; + if (++untilcol == _inlineRows[untilrow].items.size()) { + ++untilrow; + untilcol = 0; + } + } + if (until == count) { // all items are layed out + if (untilrow == _inlineRows.size()) { // nothing changed + return until; + } + + for (int i = untilrow, l = _inlineRows.size(), skip = untilcol; i < l; ++i) { + for (int j = 0, s = _inlineRows[i].items.size(); j < s; ++j) { + if (skip) { + --skip; + } else { + _inlineRows[i].items[j]->setPosition(-1); + } + } + } + if (!untilcol) { // all good rows are filled + _inlineRows.resize(untilrow); + return until; + } + _inlineRows.resize(untilrow + 1); + _inlineRows[untilrow].items.resize(untilcol); + _inlineRows[untilrow] = layoutInlineRow(_inlineRows[untilrow]); + return until; + } + if (untilrow && !untilcol) { // remove last row, maybe it is not full + --untilrow; + untilcol = _inlineRows[untilrow].items.size(); + } + until -= untilcol; + + for (int i = untilrow, l = _inlineRows.size(); i < l; ++i) { + for (int j = 0, s = _inlineRows[i].items.size(); j < s; ++j) { + _inlineRows[i].items[j]->setPosition(-1); + } + } + _inlineRows.resize(untilrow); + + if (_inlineRows.isEmpty()) { + _inlineWithThumb = false; + for (int i = until; i < count; ++i) { + if (results.at(i)->hasThumbDisplay()) { + _inlineWithThumb = true; + break; + } + } + } + return until; +} + +void GifsListWidget::inlineItemLayoutChanged(const InlineItem *layout) { + if (_selected < 0 || !isVisible()) { + return; + } + + int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; + if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { + if (layout == _inlineRows.at(row).items.at(col)) { + updateSelected(); + } + } +} + +void GifsListWidget::inlineItemRepaint(const InlineItem *layout) { + auto ms = getms(); + if (_lastScrolled + 100 <= ms) { + update(); + } else { + _updateInlineItems.start(_lastScrolled + 100 - ms); + } +} + +bool GifsListWidget::inlineItemVisible(const InlineItem *layout) { + auto position = layout->position(); + if (position < 0 || !isVisible()) { + return false; + } + + auto row = position / MatrixRowShift; + auto col = position % MatrixRowShift; + t_assert((row < _inlineRows.size()) && (col < _inlineRows[row].items.size())); + + auto &inlineItems = _inlineRows[row].items; + auto top = 0; + for (auto i = 0; i != row; ++i) { + top += _inlineRows[i].height; + } + + return (top < getVisibleBottom()) && (top + _inlineRows[row].items[col]->height() > getVisibleTop()); +} + +void GifsListWidget::refreshRecent() { + if (_section == Section::Gifs) { + refreshSavedGifs(); + } +} + +void GifsListWidget::updateSelected() { + if (_pressed >= 0 && !_previewShown) { + return; + } + + auto newSelected = -1; + auto p = mapFromGlobal(_lastMousePos); + + int sx = (rtl() ? width() - p.x() : p.x()) - (st::inlineResultsLeft - st::buttonRadius); + int sy = p.y() - st::stickerPanPadding; + if (_switchPmButton) { + sy -= _switchPmButton->height() + st::inlineResultsSkip; + } + int row = -1, col = -1, sel = -1; + ClickHandlerPtr lnk; + ClickHandlerHost *lnkhost = nullptr; + HistoryCursorState cursor = HistoryDefaultCursorState; + if (sy >= 0) { + row = 0; + for (int rows = _inlineRows.size(); row < rows; ++row) { + if (sy < _inlineRows.at(row).height) { + break; + } + sy -= _inlineRows.at(row).height; + } + } + if (sx >= 0 && row >= 0 && row < _inlineRows.size()) { + auto &inlineItems = _inlineRows[row].items; + col = 0; + for (int cols = inlineItems.size(); col < cols; ++col) { + int width = inlineItems.at(col)->width(); + if (sx < width) { + break; + } + sx -= width; + if (inlineItems.at(col)->hasRightSkip()) { + sx -= st::inlineResultsSkip; + } + } + if (col < inlineItems.size()) { + sel = row * MatrixRowShift + col; + inlineItems.at(col)->getState(lnk, cursor, sx, sy); + lnkhost = inlineItems.at(col); + } else { + row = col = -1; + } + } else { + row = col = -1; + } + int srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; + int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; + if (_selected != sel) { + if (srow >= 0 && scol >= 0) { + t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); + _inlineRows[srow].items[scol]->update(); + } + _selected = sel; + if (row >= 0 && col >= 0) { + t_assert(row >= 0 && row < _inlineRows.size() && col >= 0 && col < _inlineRows.at(row).items.size()); + _inlineRows[row].items[col]->update(); + } + if (_previewShown && _selected >= 0 && _pressed != _selected) { + _pressed = _selected; + if (row >= 0 && col >= 0) { + auto layout = _inlineRows.at(row).items.at(col); + if (auto previewDocument = layout->getPreviewDocument()) { + Ui::showMediaPreview(previewDocument); + } else if (auto previewPhoto = layout->getPreviewPhoto()) { + Ui::showMediaPreview(previewPhoto); + } + } + } + } + if (ClickHandler::setActive(lnk, lnkhost)) { + setCursor(lnk ? style::cur_pointer : style::cur_default); + } +} + +void GifsListWidget::onPreview() { + if (_pressed < 0) return; + int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; + if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { + auto layout = _inlineRows.at(row).items.at(col); + if (auto previewDocument = layout->getPreviewDocument()) { + Ui::showMediaPreview(previewDocument); + _previewShown = true; + } else if (auto previewPhoto = layout->getPreviewPhoto()) { + Ui::showMediaPreview(previewPhoto); + _previewShown = true; + } + } +} + +void GifsListWidget::onUpdateInlineItems() { + auto ms = getms(); + if (_lastScrolled + 100 <= ms) { + update(); + } else { + _updateInlineItems.start(_lastScrolled + 100 - ms); + } +} + +void GifsListWidget::onSwitchPm() { + if (_inlineBot && _inlineBot->botInfo) { + _inlineBot->botInfo->startToken = _switchPmStartToken; + Ui::showPeerHistory(_inlineBot, ShowAndStartBotMsgId); + } +} + +} // namespace ChatHelpers diff --git a/Telegram/SourceFiles/stickers/gifs_list_widget.h b/Telegram/SourceFiles/stickers/gifs_list_widget.h new file mode 100644 index 0000000000..75d236a53e --- /dev/null +++ b/Telegram/SourceFiles/stickers/gifs_list_widget.h @@ -0,0 +1,132 @@ +/* +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 +*/ +#pragma once + +#include "stickers/emoji_panel.h" + +namespace ChatHelpers { + +class GifsListWidget : public EmojiPanel::Inner, public InlineBots::Layout::Context, private base::Subscriber { + Q_OBJECT + +public: + GifsListWidget(QWidget *parent); + + void refreshRecent() override; + void preloadImages() override; + void hideFinish(bool completely) override; + void clearSelection() override; + object_ptr createController() override; + + void refreshSavedGifs(); + int refreshInlineRows(UserData *bot, const InlineCacheEntry *results, bool resultsDeleted); + void inlineBotChanged(); + void hideInlineRowsPanel(); + void clearInlineRowsPanel(); + + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + + void inlineItemLayoutChanged(const InlineItem *layout) override; + void inlineItemRepaint(const InlineItem *layout) override; + bool inlineItemVisible(const InlineItem *layout) override; + + ~GifsListWidget(); + +protected: + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void leaveEventHook(QEvent *e) override; + void leaveToChildEvent(QEvent *e, QWidget *child) override; + void enterFromChildEvent(QEvent *e, QWidget *child) override; + int countHeight() override; + +private slots: + void onPreview(); + void onUpdateInlineItems(); + void onSwitchPm(); + +signals: + void selected(DocumentData *sticker); + void selected(PhotoData *photo); + void selected(InlineBots::Result *result, UserData *bot); + + void emptyInlineRows(); + void scrollUpdated(); + +private: + enum class Section { + Inlines, + Gifs, + }; + class Controller; + + void updateSelected(); + void paintInlineItems(Painter &p, QRect clip); + void refreshSwitchPmButton(const InlineCacheEntry *entry); + + Section _section = Section::Gifs; + UserData *_inlineBot; + QString _inlineBotTitle; + TimeMs _lastScrolled = 0; + QTimer _updateInlineItems; + bool _inlineWithThumb = false; + + object_ptr _switchPmButton = { nullptr }; + QString _switchPmStartToken; + + typedef QVector InlineItems; + struct InlineRow { + int height = 0; + InlineItems items; + }; + typedef QVector InlineRows; + InlineRows _inlineRows; + void clearInlineRows(bool resultsDeleted); + + std::map> _gifLayouts; + InlineItem *layoutPrepareSavedGif(DocumentData *doc, int32 position); + + std::map> _inlineLayouts; + InlineItem *layoutPrepareInlineResult(InlineResult *result, int32 position); + + bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth); + bool inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force = false); + + InlineRow &layoutInlineRow(InlineRow &row, int32 sumWidth = 0); + void deleteUnusedGifLayouts(); + + void deleteUnusedInlineLayouts(); + + int validateExistingInlineRows(const InlineResults &results); + void selectInlineResult(int row, int column); + + int _selected = -1; + int _pressed = -1; + QPoint _lastMousePos; + + QTimer _previewTimer; + bool _previewShown = false; + +}; + +} // namespace ChatHelpers diff --git a/Telegram/SourceFiles/stickers/stickers_list_widget.cpp b/Telegram/SourceFiles/stickers/stickers_list_widget.cpp new file mode 100644 index 0000000000..cb0dac1812 --- /dev/null +++ b/Telegram/SourceFiles/stickers/stickers_list_widget.cpp @@ -0,0 +1,931 @@ +/* +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/stickers_list_widget.h" + +#include "styles/style_stickers.h" +#include "ui/widgets/buttons.h" +#include "ui/effects/ripple_animation.h" +#include "boxes/stickers_box.h" +#include "inline_bots/inline_bot_result.h" +#include "stickers/stickers.h" +#include "storage/localstorage.h" +#include "lang.h" +#include "mainwindow.h" + +namespace ChatHelpers { +namespace { + +constexpr auto kStickersPanelPerRow = Stickers::kPanelPerRow; +constexpr auto kInlineItemsMaxPerRow = 5; + +} // namespace + +class StickersListWidget::Controller : public TWidget { +public: + Controller(gsl::not_null parent); + +private: + gsl::not_null _pan; + +}; + +StickersListWidget::Controller::Controller(gsl::not_null parent) : TWidget(parent) +, _pan(parent) { +} + +StickersListWidget::StickersListWidget(QWidget *parent) : Inner(parent) +, _section(Section::Stickers) +, _addText(lang(lng_stickers_featured_add).toUpper()) +, _addWidth(st::stickersTrendingAdd.font->width(_addText)) +, _settings(this, lang(lng_stickers_you_have)) { + resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, countHeight()); + + setMouseTracking(true); + setAttribute(Qt::WA_OpaquePaintEvent); + + connect(_settings, SIGNAL(clicked()), this, SLOT(onSettings())); + + _previewTimer.setSingleShot(true); + connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); + + subscribe(AuthSession::CurrentDownloaderTaskFinished(), [this] { + update(); + readVisibleSets(); + }); +} + +object_ptr StickersListWidget::createController() { + return object_ptr(this); +} + +void StickersListWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { + auto top = getVisibleTop(); + Inner::setVisibleTopBottom(visibleTop, visibleBottom); + if (_section == Section::Featured) { + readVisibleSets(); + } +} + +void StickersListWidget::readVisibleSets() { + auto itemsVisibleTop = getVisibleTop(); + auto itemsVisibleBottom = getVisibleBottom(); + auto rowHeight = featuredRowHeight(); + int rowFrom = floorclamp(itemsVisibleTop, rowHeight, 0, _featuredSets.size()); + int rowTo = ceilclamp(itemsVisibleBottom, rowHeight, 0, _featuredSets.size()); + for (int i = rowFrom; i < rowTo; ++i) { + auto &set = _featuredSets[i]; + if (!(set.flags & MTPDstickerSet_ClientFlag::f_unread)) { + continue; + } + if (i * rowHeight < itemsVisibleTop || (i + 1) * rowHeight > itemsVisibleBottom) { + continue; + } + int count = qMin(set.pack.size(), static_cast(kStickersPanelPerRow)); + int loaded = 0; + for (int j = 0; j < count; ++j) { + if (set.pack[j]->thumb->loaded() || set.pack[j]->loaded()) { + ++loaded; + } + } + if (loaded == count) { + Stickers::markFeaturedAsRead(set.id); + } + } +} + +int StickersListWidget::featuredRowHeight() const { + return st::stickersTrendingHeader + st::stickerPanSize.height() + st::stickersTrendingSkip; +} + +template +bool StickersListWidget::enumerateSections(Callback callback) const { + Expects(_section == Section::Stickers); + auto info = SectionInfo(); + for (auto i = 0; i != _mySets.size(); ++i) { + info.section = i; + info.count = _mySets[i].pack.size(); + info.rowsCount = (info.count / kStickersPanelPerRow) + ((info.count % kStickersPanelPerRow) ? 1 : 0); + info.rowsTop = info.top + (i == 0 ? st::stickerPanPadding : st::emojiPanHeader); + info.rowsBottom = info.rowsTop + info.rowsCount * st::stickerPanSize.height(); + if (!callback(info)) { + return false; + } + info.top = info.rowsBottom; + } + return true; +} + +StickersListWidget::SectionInfo StickersListWidget::sectionInfo(int section) const { + Expects(section >= 0 && section < _mySets.size()); + auto result = SectionInfo(); + enumerateSections([searchForSection = section, &result](const SectionInfo &info) { + if (info.section == searchForSection) { + result = info; + return false; + } + return true; + }); + return result; +} + +StickersListWidget::SectionInfo StickersListWidget::sectionInfoByOffset(int yOffset) const { + auto result = SectionInfo(); + enumerateSections([this, &result, yOffset](const SectionInfo &info) { + if (yOffset < info.rowsBottom || info.section == _mySets.size() - 1) { + result = info; + return false; + } + return true; + }); + return result; +} + +int StickersListWidget::countHeight() { + auto visibleHeight = getVisibleBottom() - getVisibleTop(); + if (visibleHeight <= 0) { + visibleHeight = st::emojiPanMaxHeight - st::emojiCategory.height; + } + auto minimalLastHeight = (visibleHeight - st::stickerPanPadding); + auto countResult = [this, minimalLastHeight] { + if (_section == Section::Featured) { + return st::stickerPanPadding + shownSets().size() * featuredRowHeight(); + } else if (!shownSets().empty()) { + auto info = sectionInfo(shownSets().size() - 1); + return info.top + qMax(info.rowsBottom - info.top, minimalLastHeight); + } + return 0; + }; + return qMax(minimalLastHeight, countResult()) + st::stickerPanPadding; +} + +void StickersListWidget::installedLocally(uint64 setId) { + _installedLocallySets.insert(setId); +} + +void StickersListWidget::notInstalledLocally(uint64 setId) { + _installedLocallySets.remove(setId); +} + +void StickersListWidget::clearInstalledLocally() { + if (!_installedLocallySets.empty()) { + _installedLocallySets.clear(); + refreshStickers(); + } +} + +int StickersListWidget::stickersLeft() const { + return (st::stickerPanPadding - st::buttonRadius); +} + +QRect StickersListWidget::stickerRect(int section, int sel) { + int x = 0, y = 0; + if (_section == Section::Featured) { + x = stickersLeft() + (sel * st::stickerPanSize.width()); + y = st::stickerPanPadding + (section * featuredRowHeight()) + st::stickersTrendingHeader; + } else if (_section == Section::Stickers) { + auto info = sectionInfo(section); + if (sel >= _mySets[section].pack.size()) { + sel -= _mySets[section].pack.size(); + } + auto countTillItem = (sel - (sel % kStickersPanelPerRow)); + auto rowsToSkip = (countTillItem / kStickersPanelPerRow) + ((countTillItem % kStickersPanelPerRow) ? 1 : 0); + x = stickersLeft() + ((sel % kStickersPanelPerRow) * st::stickerPanSize.width()); + y = info.rowsTop + rowsToSkip * st::stickerPanSize.height(); + } + return QRect(x, y, st::stickerPanSize.width(), st::stickerPanSize.height()); +} + +void StickersListWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + auto clip = e->rect(); + p.fillRect(clip, st::emojiPanBg); + + if (_section == Section::Featured) { + paintFeaturedStickers(p, clip); + } else { + paintStickers(p, clip); + } +} + +void StickersListWidget::paintFeaturedStickers(Painter &p, QRect clip) { + auto fromColumn = floorclamp(clip.x() - stickersLeft(), st::stickerPanSize.width(), 0, kStickersPanelPerRow); + auto toColumn = ceilclamp(clip.x() + clip.width() - stickersLeft(), st::stickerPanSize.width(), 0, kStickersPanelPerRow); + if (rtl()) { + qSwap(fromColumn, toColumn); + fromColumn = kStickersPanelPerRow - fromColumn; + toColumn = kStickersPanelPerRow - toColumn; + } + + auto &sets = shownSets(); + auto selsection = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; + auto selindex = (selsection >= 0) ? (_selected % MatrixRowShift) : -1; + auto seldelete = false; + if (selsection >= sets.size()) { + selsection = -1; + } else if (selsection >= 0 && selindex >= sets[selsection].pack.size()) { + selindex -= sets[selsection].pack.size(); + seldelete = true; + } + + auto tilly = st::stickerPanPadding; + auto ms = getms(); + for (auto c = 0, l = sets.size(); c != l; ++c) { + auto y = tilly; + auto &set = sets[c]; + tilly = y + featuredRowHeight(); + if (clip.top() >= tilly) continue; + if (y >= clip.y() + clip.height()) break; + + int size = set.pack.size(); + + int widthForTitle = featuredContentWidth() - (st::emojiPanHeaderLeft - st::buttonRadius); + if (featuredHasAddButton(c)) { + auto add = featuredAddRect(c); + auto selected = (_selectedFeaturedSetAdd == c) || (_pressedFeaturedSetAdd == c); + auto &textBg = selected ? st::stickersTrendingAdd.textBgOver : st::stickersTrendingAdd.textBg; + + App::roundRect(p, myrtlrect(add), textBg, ImageRoundRadius::Small); + if (set.ripple) { + set.ripple->paint(p, add.x(), add.y(), width(), ms); + if (set.ripple->empty()) { + set.ripple.reset(); + } + } + p.setFont(st::stickersTrendingAdd.font); + p.setPen(selected ? st::stickersTrendingAdd.textFgOver : st::stickersTrendingAdd.textFg); + p.drawTextLeft(add.x() - (st::stickersTrendingAdd.width / 2), add.y() + st::stickersTrendingAdd.textTop, width(), _addText, _addWidth); + + widthForTitle -= add.width() - (st::stickersTrendingAdd.width / 2); + } else { + auto add = featuredAddRect(c); + int checkx = add.left() + (add.width() - st::stickersFeaturedInstalled.width()) / 2; + int checky = add.top() + (add.height() - st::stickersFeaturedInstalled.height()) / 2; + st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width()); + } + if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { + widthForTitle -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; + } + + auto titleText = set.title; + auto titleWidth = st::stickersTrendingHeaderFont->width(titleText); + if (titleWidth > widthForTitle) { + titleText = st::stickersTrendingHeaderFont->elided(titleText, widthForTitle); + titleWidth = st::stickersTrendingHeaderFont->width(titleText); + } + p.setFont(st::stickersTrendingHeaderFont); + p.setPen(st::stickersTrendingHeaderFg); + p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, y + st::stickersTrendingHeaderTop, width(), titleText, titleWidth); + + if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { + p.setPen(Qt::NoPen); + p.setBrush(st::stickersFeaturedUnreadBg); + + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(rtlrect(st::emojiPanHeaderLeft - st::buttonRadius + titleWidth + st::stickersFeaturedUnreadSkip, y + st::stickersTrendingHeaderTop + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); + } + } + + p.setFont(st::stickersTrendingSubheaderFont); + p.setPen(st::stickersTrendingSubheaderFg); + p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, y + st::stickersTrendingSubheaderTop, width(), lng_stickers_count(lt_count, size)); + + y += st::stickersTrendingHeader; + if (y >= clip.y() + clip.height()) break; + + for (int j = fromColumn; j < toColumn; ++j) { + int index = j; + if (index >= size) break; + + auto selected = (selsection == c && selindex == index); + auto deleteSelected = selected && seldelete; + paintSticker(p, set, y, index, selected, deleteSelected); + } + } +} + +void StickersListWidget::paintStickers(Painter &p, QRect clip) { + auto fromColumn = floorclamp(clip.x() - stickersLeft(), st::stickerPanSize.width(), 0, kStickersPanelPerRow); + auto toColumn = ceilclamp(clip.x() + clip.width() - stickersLeft(), st::stickerPanSize.width(), 0, kStickersPanelPerRow); + if (rtl()) { + qSwap(fromColumn, toColumn); + fromColumn = kStickersPanelPerRow - fromColumn; + toColumn = kStickersPanelPerRow - toColumn; + } + + auto &sets = shownSets(); + auto selsection = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; + auto selindex = (selsection >= 0) ? (_selected % MatrixRowShift) : -1; + auto seldelete = false; + if (selsection >= sets.size()) { + selsection = -1; + } else if (selsection >= 0 && selindex >= sets[selsection].pack.size()) { + selindex -= sets[selsection].pack.size(); + seldelete = true; + } + enumerateSections([this, &p, clip, fromColumn, toColumn, selsection, selindex, seldelete](const SectionInfo &info) { + if (clip.top() >= info.rowsBottom) { + return true; + } else if (clip.top() + clip.height() <= info.top) { + return false; + } + auto &set = _mySets[info.section]; + if (info.section > 0 && clip.top() < info.rowsTop) { + // TODO delete button, elided text + p.setFont(st::emojiPanHeaderFont); + p.setPen(st::emojiPanHeaderFg); + p.drawTextLeft(st::emojiPanHeaderLeft - st::buttonRadius, info.top + st::emojiPanHeaderTop, width(), set.title); + } + if (clip.top() + clip.height() > info.rowsTop) { + auto special = (set.flags & MTPDstickerSet::Flag::f_official) != 0; + auto fromRow = floorclamp(clip.y() - info.rowsTop, st::stickerPanSize.height(), 0, info.rowsCount); + auto toRow = ceilclamp(clip.y() + clip.height() - info.rowsTop, st::stickerPanSize.height(), 0, info.rowsCount); + for (int i = fromRow; i < toRow; ++i) { + for (int j = fromColumn; j < toColumn; ++j) { + int index = i * kStickersPanelPerRow + j; + if (index >= info.count) break; + + auto selected = (selsection == info.section && selindex == index); + auto deleteSelected = selected && seldelete; + paintSticker(p, set, info.rowsTop, index, selected, deleteSelected); + } + } + } + return true; + }); +} + +void StickersListWidget::paintSticker(Painter &p, Set &set, int y, int index, bool selected, bool deleteSelected) { + auto sticker = set.pack[index]; + if (!sticker->sticker()) return; + + int row = (index / kStickersPanelPerRow), col = (index % kStickersPanelPerRow); + + auto pos = QPoint(stickersLeft() + col * st::stickerPanSize.width(), y + row * st::stickerPanSize.height()); + if (selected) { + auto tl = pos; + if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width()); + App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners); + } + + auto goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); + if (goodThumb) { + sticker->thumb->load(); + } else { + sticker->checkSticker(); + } + + auto coef = qMin((st::stickerPanSize.width() - st::buttonRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::buttonRadius * 2) / float64(sticker->dimensions.height())); + if (coef > 1) coef = 1; + auto w = qMax(qRound(coef * sticker->dimensions.width()), 1); + auto h = qMax(qRound(coef * sticker->dimensions.height()), 1); + auto ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); + if (goodThumb) { + p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h)); + } else if (!sticker->sticker()->img->isNull()) { + p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h)); + } + + if (selected && set.id == Stickers::RecentSetId && _custom.at(index)) { + auto xPos = pos + QPoint(st::stickerPanSize.width() - st::stickerPanDelete.width(), 0); + if (!deleteSelected) p.setOpacity(st::stickerPanDeleteOpacity); + st::stickerPanDelete.paint(p, xPos, width()); + if (!deleteSelected) p.setOpacity(1.); + } +} + +bool StickersListWidget::featuredHasAddButton(int index) const { + if (index < 0 || index >= _featuredSets.size()) { + return false; + } + auto flags = _featuredSets[index].flags; + return !(flags & MTPDstickerSet::Flag::f_installed) || (flags & MTPDstickerSet::Flag::f_archived); +} + +int StickersListWidget::featuredContentWidth() const { + return stickersLeft() + (kStickersPanelPerRow * st::stickerPanSize.width()); +} + +QRect StickersListWidget::featuredAddRect(int index) const { + int addw = _addWidth - st::stickersTrendingAdd.width; + int addh = st::stickersTrendingAdd.height; + int addx = featuredContentWidth() - addw; + int addy = st::stickerPanPadding + index * featuredRowHeight() + st::stickersTrendingAddTop; + return QRect(addx, addy, addw, addh); +} + +void StickersListWidget::mousePressEvent(QMouseEvent *e) { + if (e->button() != Qt::LeftButton) { + return; + } + _lastMousePos = e->globalPos(); + updateSelected(); + + _pressed = _selected; + _pressedFeaturedSet = _selectedFeaturedSet; + setPressedFeaturedSetAdd(_selectedFeaturedSetAdd); + ClickHandler::pressed(); + _previewTimer.start(QApplication::startDragTime()); +} + +void StickersListWidget::setPressedFeaturedSetAdd(int newPressedFeaturedSetAdd) { + if (_pressedFeaturedSetAdd >= 0 && _pressedFeaturedSetAdd < _featuredSets.size()) { + auto &set = _featuredSets[_pressedFeaturedSetAdd]; + if (set.ripple) { + set.ripple->lastStop(); + } + } + _pressedFeaturedSetAdd = newPressedFeaturedSetAdd; + if (_pressedFeaturedSetAdd >= 0 && _pressedFeaturedSetAdd < _featuredSets.size()) { + auto &set = _featuredSets[_pressedFeaturedSetAdd]; + if (!set.ripple) { + auto maskSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height); + auto mask = Ui::RippleAnimation::roundRectMask(maskSize, st::buttonRadius); + set.ripple = MakeShared(st::stickersTrendingAdd.ripple, std::move(mask), [this, index = _pressedFeaturedSetAdd] { + update(myrtlrect(featuredAddRect(index))); + }); + } + auto rect = myrtlrect(featuredAddRect(_pressedFeaturedSetAdd)); + set.ripple->add(mapFromGlobal(QCursor::pos()) - rect.topLeft()); + } +} + +void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) { + _previewTimer.stop(); + + auto pressed = std::exchange(_pressed, -1); + auto pressedFeaturedSet = std::exchange(_pressedFeaturedSet, -1); + auto pressedFeaturedSetAdd = _pressedFeaturedSetAdd; + setPressedFeaturedSetAdd(-1); + if (pressedFeaturedSetAdd != _selectedFeaturedSetAdd) { + update(); + } + + auto activated = ClickHandler::unpressed(); + + if (_previewShown) { + _previewShown = false; + return; + } + + _lastMousePos = e->globalPos(); + updateSelected(); + + auto &sets = shownSets(); + if (_selected >= 0 && _selected < MatrixRowShift * sets.size() && _selected == pressed) { + auto section = (_selected / MatrixRowShift); + auto sel = _selected % MatrixRowShift; + if (sets[section].id == Stickers::RecentSetId && sel >= sets[section].pack.size() && sel < sets[section].pack.size() * 2 && _custom.at(sel - sets[section].pack.size())) { + removeRecentSticker(section, sel - sets[section].pack.size()); + return; + } + if (sel < sets[section].pack.size()) { + emit selected(sets[section].pack[sel]); + } + } else if (_selectedFeaturedSet >= 0 && _selectedFeaturedSet < sets.size() && _selectedFeaturedSet == pressedFeaturedSet) { + emit displaySet(sets[_selectedFeaturedSet].id); + } else if (_selectedFeaturedSetAdd >= 0 && _selectedFeaturedSetAdd < sets.size() && _selectedFeaturedSetAdd == pressedFeaturedSetAdd) { + emit installSet(sets[_selectedFeaturedSetAdd].id); + } +} + +void StickersListWidget::removeRecentSticker(int section, int index) { + if (_section != Section::Stickers || section >= _mySets.size() || _mySets[section].id != Stickers::RecentSetId) { + return; + } + + clearSelection(); + bool refresh = false; + auto sticker = _mySets[section].pack[index]; + auto &recent = cGetRecentStickers(); + for (int32 i = 0, l = recent.size(); i < l; ++i) { + if (recent.at(i).first == sticker) { + recent.removeAt(i); + Local::writeUserSettings(); + refresh = true; + break; + } + } + auto &sets = Global::RefStickerSets(); + auto it = sets.find(Stickers::CustomSetId); + if (it != sets.cend()) { + for (int i = 0, l = it->stickers.size(); i < l; ++i) { + if (it->stickers.at(i) == sticker) { + it->stickers.removeAt(i); + if (it->stickers.isEmpty()) { + sets.erase(it); + } + Local::writeInstalledStickers(); + refresh = true; + break; + } + } + } + if (refresh) { + refreshRecentStickers(); + updateSelected(); + update(); + } +} + +void StickersListWidget::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); +} + +void StickersListWidget::resizeEvent(QResizeEvent *e) { + _settings->moveToLeft((st::emojiPanWidth - _settings->width()) / 2, height() / 3); +} + +void StickersListWidget::leaveEventHook(QEvent *e) { + clearSelection(); +} + +void StickersListWidget::leaveToChildEvent(QEvent *e, QWidget *child) { + clearSelection(); +} + +void StickersListWidget::enterFromChildEvent(QEvent *e, QWidget *child) { + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +void StickersListWidget::clearSelection() { + _pressed = -1; + _pressedFeaturedSet = -1; + setSelected(-1, -1, -1); + setPressedFeaturedSetAdd(-1); + update(); +} + +void StickersListWidget::hideFinish(bool completely) { + clearSelection(); + if (completely) { + clearInstalledLocally(); + } + + // Reset to the recent stickers section. + if (_section == Section::Featured) { + _section = Section::Stickers; + } +} + +void StickersListWidget::refreshStickers() { + auto stickersShown = (_section == Section::Stickers || _section == Section::Featured); + if (stickersShown) { + clearSelection(); + } + + _mySets.clear(); + _mySets.reserve(Global::StickerSetsOrder().size() + 1); + + refreshRecentStickers(false); + for_const (auto setId, Global::StickerSetsOrder()) { + appendSet(_mySets, setId, AppendSkip::Archived); + } + + _featuredSets.clear(); + _featuredSets.reserve(Global::FeaturedStickerSetsOrder().size()); + + for_const (auto setId, Global::FeaturedStickerSetsOrder()) { + appendSet(_featuredSets, setId, AppendSkip::Installed); + } + + if (stickersShown) { + int h = countHeight(); + if (h != height()) resize(width(), h); + + _settings->setVisible(_section == Section::Stickers && _mySets.isEmpty()); + } else { + _settings->hide(); + } + + emit refreshIcons(kRefreshIconsNoAnimation); + + if (stickersShown) updateSelected(); +} + +void StickersListWidget::preloadImages() { + auto &sets = shownSets(); + for (int i = 0, l = sets.size(), k = 0; i < l; ++i) { + int count = sets[i].pack.size(); + if (_section == Section::Featured) { + accumulate_min(count, kStickersPanelPerRow); + } + for (int j = 0; j != count; ++j) { + if (++k > kStickersPanelPerRow * (kStickersPanelPerRow + 1)) break; + + auto sticker = sets.at(i).pack.at(j); + if (!sticker || !sticker->sticker()) continue; + + bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); + if (goodThumb) { + sticker->thumb->load(); + } else { + sticker->automaticLoad(0); + } + } + if (k > kStickersPanelPerRow * (kStickersPanelPerRow + 1)) break; + } +} + +uint64 StickersListWidget::currentSet(int yOffset) const { + if (_section == Section::Featured) { + return Stickers::FeaturedSetId; + } + return _mySets.isEmpty() ? Stickers::RecentSetId : _mySets[sectionInfoByOffset(yOffset).section].id; +} + +void StickersListWidget::appendSet(Sets &to, uint64 setId, AppendSkip skip) { + auto &sets = Global::StickerSets(); + auto it = sets.constFind(setId); + if (it == sets.cend() || it->stickers.isEmpty()) return; + if ((skip == AppendSkip::Archived) && (it->flags & MTPDstickerSet::Flag::f_archived)) return; + if ((skip == AppendSkip::Installed) && (it->flags & MTPDstickerSet::Flag::f_installed) && !(it->flags & MTPDstickerSet::Flag::f_archived)) { + if (!_installedLocallySets.contains(setId)) { + return; + } + } + + to.push_back(Set(it->id, it->flags, it->title, it->stickers.size() + 1, it->stickers)); +} + +void StickersListWidget::refreshRecent() { + if (_section == Section::Stickers) { + refreshRecentStickers(); + } +} + +void StickersListWidget::refreshRecentStickers(bool performResize) { + _custom.clear(); + clearSelection(); + auto &sets = Global::StickerSets(); + auto &recent = cGetRecentStickers(); + auto customIt = sets.constFind(Stickers::CustomSetId); + auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); + if (recent.isEmpty() + && (customIt == sets.cend() || customIt->stickers.isEmpty()) + && (cloudIt == sets.cend() || cloudIt->stickers.isEmpty())) { + if (!_mySets.isEmpty() && _mySets.at(0).id == Stickers::RecentSetId) { + _mySets.pop_front(); + } + } else { + StickerPack recentPack; + int customCnt = (customIt == sets.cend()) ? 0 : customIt->stickers.size(); + int cloudCnt = (cloudIt == sets.cend()) ? 0 : cloudIt->stickers.size(); + recentPack.reserve(cloudCnt + recent.size() + customCnt); + _custom.reserve(cloudCnt + recent.size() + customCnt); + if (cloudCnt > 0) { + for_const (auto sticker, cloudIt->stickers) { + recentPack.push_back(sticker); + _custom.push_back(false); + } + } + for_const (auto &recentSticker, recent) { + auto sticker = recentSticker.first; + recentPack.push_back(sticker); + _custom.push_back(false); + } + if (customCnt > 0) { + for_const (auto &sticker, customIt->stickers) { + auto index = recentPack.indexOf(sticker); + if (index >= cloudCnt) { + _custom[index] = true; // mark stickers from recent as custom + } else { + recentPack.push_back(sticker); + _custom.push_back(true); + } + } + } + if (_mySets.isEmpty() || _mySets.at(0).id != Stickers::RecentSetId) { + _mySets.push_back(Set(Stickers::RecentSetId, MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special, lang(lng_recent_stickers), recentPack.size() * 2, recentPack)); + } else { + _mySets[0].pack = recentPack; + } + } + + if (performResize && (_section == Section::Stickers || _section == Section::Featured)) { + int32 h = countHeight(); + if (h != height()) { + resize(width(), h); + update(); + } + + updateSelected(); + } +} + +void StickersListWidget::fillIcons(QList &icons) { + icons.clear(); + icons.reserve(_mySets.size() + 1); + if (Global::FeaturedStickerSetsUnreadCount() && !_featuredSets.isEmpty()) { + icons.push_back(StickerIcon(Stickers::FeaturedSetId)); + } + + if (!_mySets.isEmpty()) { + int i = 0; + if (_mySets[0].id == Stickers::RecentSetId) { + ++i; + icons.push_back(StickerIcon(Stickers::RecentSetId)); + } + for (int l = _mySets.size(); i < l; ++i) { + auto s = _mySets[i].pack[0]; + int32 availw = st::emojiCategory.width - 2 * st::stickerIconPadding, availh = st::emojiCategory.height - 2 * st::stickerIconPadding; + int32 thumbw = s->thumb->width(), thumbh = s->thumb->height(), pixw = 1, pixh = 1; + if (availw * thumbh > availh * thumbw) { + pixh = availh; + pixw = (pixh * thumbw) / thumbh; + } else { + pixw = availw; + pixh = thumbw ? ((pixw * thumbh) / thumbw) : 1; + } + if (pixw < 1) pixw = 1; + if (pixh < 1) pixh = 1; + icons.push_back(StickerIcon(_mySets[i].id, s, pixw, pixh)); + } + } + + if (!Global::FeaturedStickerSetsUnreadCount() && !_featuredSets.isEmpty()) { + icons.push_back(StickerIcon(Stickers::FeaturedSetId)); + } +} + +void StickersListWidget::updateSelected() { + if (_pressed >= 0 && !_previewShown) { + return; + } + + auto newSelected = -1; + auto p = mapFromGlobal(_lastMousePos); + auto newSelectedFeaturedSet = -1; + auto newSelectedFeaturedSetAdd = -1; + auto &sets = shownSets(); + auto sx = (rtl() ? width() - p.x() : p.x()) - stickersLeft(); + if (_section == Section::Featured) { + auto yOffset = p.y() - st::stickerPanPadding; + auto section = (yOffset >= 0) ? (yOffset / featuredRowHeight()) : -1; + if (section >= 0 && section < sets.size()) { + yOffset -= section * featuredRowHeight(); + + auto &set = sets[section]; + if (yOffset < st::stickersTrendingHeader) { + if (featuredHasAddButton(section) && myrtlrect(featuredAddRect(section)).contains(p.x(), p.y())) { + newSelectedFeaturedSetAdd = section; + } else { + newSelectedFeaturedSet = section; + } + } else if (yOffset >= st::stickersTrendingHeader && yOffset < st::stickersTrendingHeader + st::stickerPanSize.height()) { + if (sx >= 0 && sx < kStickersPanelPerRow * st::stickerPanSize.width()) { + newSelected = qFloor(sx / st::stickerPanSize.width()); + if (newSelected >= set.pack.size()) { + newSelected = -1; + } else { + newSelected += section * MatrixRowShift; + } + } + } + } + } else if (!_mySets.empty()) { + auto info = sectionInfoByOffset(p.y()); + if (p.y() >= info.top && p.y() < info.rowsTop) { + // TODO selected header / delete + } else if (p.y() >= info.rowsTop && p.y() < info.rowsBottom && sx >= 0) { + auto yOffset = p.y() - info.rowsTop; + auto &set = sets[info.section]; + auto special = ((set.flags & MTPDstickerSet::Flag::f_official) != 0); + auto rowIndex = qFloor(yOffset / st::stickerPanSize.height()); + newSelected = rowIndex * kStickersPanelPerRow + qFloor(sx / st::stickerPanSize.width()); + if (newSelected >= set.pack.size()) { + newSelected = -1; + } else { + if (set.id == Stickers::RecentSetId && _custom[newSelected]) { + auto inx = sx - (newSelected % kStickersPanelPerRow) * st::stickerPanSize.width(); + auto iny = yOffset - ((newSelected / kStickersPanelPerRow) * st::stickerPanSize.height()); + if (inx >= st::stickerPanSize.width() - st::stickerPanDelete.width() && iny < st::stickerPanDelete.height()) { + newSelected += set.pack.size(); + } + } + newSelected += info.section * MatrixRowShift; + } + } + } + + setSelected(newSelected, newSelectedFeaturedSet, newSelectedFeaturedSetAdd); +} + +void StickersListWidget::setSelected(int newSelected, int newSelectedFeaturedSet, int newSelectedFeaturedSetAdd) { + if (_selected != newSelected || _selectedFeaturedSet != newSelectedFeaturedSet || _selectedFeaturedSetAdd != newSelectedFeaturedSetAdd) { + setCursor((newSelected >= 0 || newSelectedFeaturedSet >= 0 || newSelectedFeaturedSetAdd >= 0) ? style::cur_pointer : style::cur_default); + } + if (_selected != newSelected) { + auto &sets = shownSets(); + auto updateSelected = [this, &sets]() { + if (_selected < 0) return; + auto section = _selected / MatrixRowShift; + auto sel = _selected % MatrixRowShift; + if (section < sets.size() && sel >= sets[section].pack.size()) { + sel -= sets[section].pack.size(); + } + rtlupdate(stickerRect(section, sel)); + }; + updateSelected(); + _selected = newSelected; + updateSelected(); + + if (_previewShown && _selected >= 0 && _pressed != _selected) { + _pressed = _selected; + auto section = _selected / MatrixRowShift; + auto sel = _selected % MatrixRowShift; + if (section < sets.size() && sel < sets[section].pack.size()) { + Ui::showMediaPreview(sets[section].pack[sel]); + } + } + } + if (_selectedFeaturedSet != newSelectedFeaturedSet) { + _selectedFeaturedSet = newSelectedFeaturedSet; + } + if (_selectedFeaturedSetAdd != newSelectedFeaturedSetAdd) { + _selectedFeaturedSetAdd = newSelectedFeaturedSetAdd; + update(); + } +} + +void StickersListWidget::onSettings() { + Ui::show(Box(StickersBox::Section::Installed)); +} + +void StickersListWidget::onPreview() { + if (_pressed < 0) return; + auto &sets = shownSets(); + if (_pressed < MatrixRowShift * sets.size()) { + auto section = (_pressed / MatrixRowShift); + auto sel = _pressed % MatrixRowShift; + if (sel < sets[section].pack.size()) { + Ui::showMediaPreview(sets[section].pack[sel]); + _previewShown = true; + } + } +} + +void StickersListWidget::showStickerSet(uint64 setId) { + clearSelection(); + + if (setId == Stickers::FeaturedSetId) { + if (_section != Section::Featured) { + _section = Section::Featured; + + refreshRecentStickers(true); + emit refreshIcons(kRefreshIconsScrollAnimation); + update(); + } + + emit scrollToY(0); + emit scrollUpdated(); + return; + } + + auto needRefresh = (_section != Section::Stickers); + if (needRefresh) { + _section = Section::Stickers; + refreshRecentStickers(true); + } + + auto y = 0; + enumerateSections([this, setId, &y](const SectionInfo &info) { + if (_mySets[info.section].id == setId) { + y = info.top; + return false; + } + return true; + }); + emit scrollToY(y); + emit scrollUpdated(); + + if (needRefresh) { + emit refreshIcons(kRefreshIconsScrollAnimation); + } + + _lastMousePos = QCursor::pos(); + + update(); +} + +} // namespace ChatHelpers diff --git a/Telegram/SourceFiles/stickers/stickers_list_widget.h b/Telegram/SourceFiles/stickers/stickers_list_widget.h new file mode 100644 index 0000000000..b5c4428891 --- /dev/null +++ b/Telegram/SourceFiles/stickers/stickers_list_widget.h @@ -0,0 +1,170 @@ +/* +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 +*/ +#pragma once + +#include "stickers/emoji_panel.h" + +namespace ChatHelpers { + +class StickersListWidget : public EmojiPanel::Inner, private base::Subscriber { + Q_OBJECT + +public: + StickersListWidget(QWidget *parent); + + void refreshRecent() override; + void preloadImages() override; + void hideFinish(bool completely) override; + void clearSelection() override; + object_ptr createController() override; + + void showStickerSet(uint64 setId); + + void refreshStickers(); + void refreshRecentStickers(bool resize = true); + + void fillIcons(QList &icons); + + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + + uint64 currentSet(int yOffset) const; + + void installedLocally(uint64 setId); + void notInstalledLocally(uint64 setId); + void clearInstalledLocally(); + +protected: + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void leaveEventHook(QEvent *e) override; + void leaveToChildEvent(QEvent *e, QWidget *child) override; + void enterFromChildEvent(QEvent *e, QWidget *child) override; + int countHeight() override; + +private slots: + void onSettings(); + void onPreview(); + +signals: + void selected(DocumentData *sticker); + + void displaySet(quint64 setId); + void installSet(quint64 setId); + void removeSet(quint64 setId); + + void refreshIcons(bool scrollAnimation); + void scrollUpdated(); + +private: + enum class Section { + Featured, + Stickers, + }; + class Controller; + + static constexpr auto kRefreshIconsScrollAnimation = true; + static constexpr auto kRefreshIconsNoAnimation = false; + + struct SectionInfo { + int section = 0; + int count = 0; + int top = 0; + int rowsCount = 0; + int rowsTop = 0; + int rowsBottom = 0; + }; + template + bool enumerateSections(Callback callback) const; + SectionInfo sectionInfo(int section) const; + SectionInfo sectionInfoByOffset(int yOffset) const; + + void updateSelected(); + void setSelected(int newSelected, int newSelectedFeaturedSet, int newSelectedFeaturedSetAdd); + + void setPressedFeaturedSetAdd(int newPressedFeaturedSetAdd); + + struct Set { + Set(uint64 id, MTPDstickerSet::Flags flags, const QString &title, int32 hoversSize, const StickerPack &pack = StickerPack()) : id(id), flags(flags), title(title), pack(pack) { + } + uint64 id; + MTPDstickerSet::Flags flags; + QString title; + StickerPack pack; + QSharedPointer ripple; + }; + using Sets = QList; + Sets &shownSets() { + return (_section == Section::Featured) ? _featuredSets : _mySets; + } + const Sets &shownSets() const { + return (_section == Section::Featured) ? _featuredSets : _mySets; + } + int featuredRowHeight() const; + void readVisibleSets(); + + void paintFeaturedStickers(Painter &p, QRect clip); + void paintStickers(Painter &p, QRect clip); + void paintSticker(Painter &p, Set &set, int y, int index, bool selected, bool deleteSelected); + bool featuredHasAddButton(int index) const; + int featuredContentWidth() const; + QRect featuredAddRect(int y) const; + + enum class AppendSkip { + Archived, + Installed, + }; + void appendSet(Sets &to, uint64 setId, AppendSkip skip); + + void selectEmoji(EmojiPtr emoji); + int stickersLeft() const; + QRect stickerRect(int section, int sel); + + Sets _mySets; + Sets _featuredSets; + OrderedSet _installedLocallySets; + QList _custom; + + Section _section = Section::Stickers; + + void removeRecentSticker(int section, int index); + + int _selected = -1; + int _pressed = -1; + int _selectedFeaturedSet = -1; + int _pressedFeaturedSet = -1; + int _selectedFeaturedSetAdd = -1; + int _pressedFeaturedSetAdd = -1; + QPoint _lastMousePos; + + QString _addText; + int _addWidth; + + object_ptr _settings; + + QTimer _previewTimer; + bool _previewShown = false; + +}; + +} // namespace ChatHelpers diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 805a1ce3bb..d2d2832303 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -332,8 +332,14 @@ <(src_loc)/settings/settings_widget.h <(src_loc)/stickers/emoji_panel.cpp <(src_loc)/stickers/emoji_panel.h +<(src_loc)/stickers/emoji_list_widget.cpp +<(src_loc)/stickers/emoji_list_widget.h +<(src_loc)/stickers/gifs_list_widget.cpp +<(src_loc)/stickers/gifs_list_widget.h <(src_loc)/stickers/stickers.cpp <(src_loc)/stickers/stickers.h +<(src_loc)/stickers/stickers_list_widget.cpp +<(src_loc)/stickers/stickers_list_widget.h <(src_loc)/storage/file_download.cpp <(src_loc)/storage/file_download.h <(src_loc)/storage/file_upload.cpp