From 64bd4f0926e7c283c9185fe352a5fbf4c3f52084 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 11 Aug 2022 13:10:40 +0300 Subject: [PATCH] Make nice emoji status selector in profile. --- .../chat_helpers/emoji_list_widget.cpp | 156 ++++++++++-------- .../chat_helpers/emoji_list_widget.h | 12 +- .../chat_helpers/stickers_list_footer.cpp | 69 ++++---- .../chat_helpers/stickers_list_footer.h | 17 +- .../SourceFiles/chat_helpers/tabbed_panel.cpp | 38 ++++- .../SourceFiles/chat_helpers/tabbed_panel.h | 6 +- .../chat_helpers/tabbed_selector.cpp | 95 ++++++----- .../chat_helpers/tabbed_selector.h | 13 +- .../info/profile/info_profile_cover.cpp | 147 +++++++++-------- .../info/profile/info_profile_cover.h | 3 +- 10 files changed, 327 insertions(+), 229 deletions(-) diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index d269de423..978bcad4f 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -364,8 +364,14 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) { EmojiListWidget::EmojiListWidget( QWidget *parent, not_null controller, - Window::GifPauseReason level) + Window::GifPauseReason level, + Mode mode) : Inner(parent, controller, level) +, _mode(mode) +, _staticCount(_mode == Mode::Full ? kEmojiSectionCount : 1) +, _premiumIcon(_mode == Mode::EmojiStatus + ? std::make_unique() + : nullptr) , _localSetsManager( std::make_unique(&controller->session())) , _collapsedBg(st::emojiPanExpand.height / 2, st::emojiPanHeaderFg) @@ -376,7 +382,7 @@ EmojiListWidget::EmojiListWidget( _picker->hide(); - for (auto i = 1; i != kEmojiSectionCount; ++i) { + for (auto i = 1; i != _staticCount; ++i) { const auto section = static_cast
(i); _counts[i] = Ui::Emoji::GetSectionCount(section); } @@ -430,8 +436,8 @@ void EmojiListWidget::repaintCustom(uint64 setId) { const auto repaint1 = repaintRecent && (info.section == int(Section::Recent)); const auto repaint2 = !repaint1 - && (info.section >= kEmojiSectionCount) - && (setId == _custom[info.section - kEmojiSectionCount].id); + && (info.section >= _staticCount) + && (setId == _custom[info.section - _staticCount].id); if (repaint1 || repaint2) { update( 0, @@ -496,10 +502,10 @@ void EmojiListWidget::unloadCustomIn(const SectionInfo &info) { } } return; - } else if (info.section < kEmojiSectionCount) { + } else if (info.section < _staticCount) { return; } - auto &custom = _custom[info.section - kEmojiSectionCount]; + auto &custom = _custom[info.section - _staticCount]; if (!custom.painted) { return; } @@ -549,7 +555,7 @@ bool EmojiListWidget::enumerateSections(Callback callback) const { info.top = info.rowsBottom; return true; }; - for (; i != kEmojiSectionCount; ++i) { + for (; i != _staticCount; ++i) { info.section = i; info.count = i ? _counts[i] : _recent.size(); if (!next()) { @@ -599,7 +605,7 @@ EmojiListWidget::SectionInfo EmojiListWidget::sectionInfoByOffset( } int EmojiListWidget::sectionsCount() const { - return kEmojiSectionCount + int(_custom.size()); + return _staticCount + int(_custom.size()); } void EmojiListWidget::setSingleSize(QSize size) { @@ -612,6 +618,9 @@ void EmojiListWidget::setSingleSize(QSize size) { _innerPosition = QPoint( (area.width() - esize) / 2, (area.height() - esize) / 2); + const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize); + const auto customSkip = (esize - customSize) / 2; + _customPosition = QPoint(customSkip, customSkip); _picker->setSingleSize(_singleSize); } @@ -649,7 +658,7 @@ void EmojiListWidget::ensureLoaded(int section) { fillRecent(); } return; - } else if (section >= kEmojiSectionCount || !_emoji[section].empty()) { + } else if (section >= _staticCount || !_emoji[section].empty()) { return; } _emoji[section] = Ui::Emoji::GetSection(static_cast
(section)); @@ -671,11 +680,17 @@ void EmojiListWidget::fillRecent() { _recentCustomIds.clear(); const auto &list = Core::App().settings().recentEmoji(); - _recent.reserve(std::min(int(list.size()), Core::kRecentEmojiLimit)); + _recent.reserve(std::min(int(list.size()), Core::kRecentEmojiLimit) + 1); + if (_mode == Mode::EmojiStatus) { + const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f"); + _recent.push_back({ .id = { Ui::Emoji::Find(star) } }); + } const auto test = controller()->session().isTestMode(); for (const auto &one : list) { const auto document = std::get_if(&one.id.data); - if (document && document->test != test) { + if (_mode == Mode::EmojiStatus && !document) { + continue; + } else if (document && document->test != test) { continue; } _recent.push_back({ @@ -730,9 +745,9 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) { if (info.section > 0 && r.top() < info.rowsTop) { p.setFont(st::emojiPanHeaderFont); p.setPen(st::emojiPanHeaderFg); - auto titleText = (info.section < kEmojiSectionCount) + auto titleText = (info.section < _staticCount) ? ChatHelpers::EmojiCategoryTitle(info.section)(tr::now) - : _custom[info.section - kEmojiSectionCount].title; + : _custom[info.section - _staticCount].title; auto titleWidth = st::emojiPanHeaderFont->width(titleText); if (titleWidth > widthForTitle) { titleText = st::emojiPanHeaderFont->elided(titleText, widthForTitle); @@ -793,10 +808,10 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) { } if (info.section == int(Section::Recent)) { drawRecent(p, w, now, paused, index); - } else if (info.section < kEmojiSectionCount) { + } else if (info.section < _staticCount) { drawEmoji(p, w, _emoji[info.section][index]); } else { - const auto set = info.section - kEmojiSectionCount; + const auto set = info.section - _staticCount; drawCustom(p, w, now, paused, set, index); } } @@ -834,10 +849,18 @@ void EmojiListWidget::drawRecent( int index) { _recentPainted = true; if (const auto emoji = std::get_if(&_recent[index].id.data)) { - drawEmoji(p, position, *emoji); + if (_mode == Mode::EmojiStatus) { + position += QPoint( + (_singleSize.width() - st::stickersPremium.width()) / 2, + (_singleSize.height() - st::stickersPremium.height()) / 2 + ) - _areaPosition; + p.drawImage(position, _premiumIcon->image()); + } else { + drawEmoji(p, position, *emoji); + } } else { Assert(_recent[index].custom != nullptr); - position += _innerPosition; + position += _innerPosition + _customPosition; _recent[index].custom->paint( p, position.x(), @@ -868,7 +891,7 @@ void EmojiListWidget::drawCustom( bool paused, int set, int index) { - position += _innerPosition; + position += _innerPosition + _customPosition; _custom[set].painted = true; _custom[set].list[index].custom->paint( p, @@ -897,7 +920,7 @@ EmojiPtr EmojiListWidget::lookupOverEmoji(const OverEmoji *over) const { && v::is(_recent[index].id.data)) ? v::get(_recent[index].id.data) : (section > int(Section::Recent) - && section < kEmojiSectionCount + && section < _staticCount && index < _emoji[section].size()) ? _emoji[section][index] : nullptr; @@ -958,10 +981,10 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { if (const auto over = std::get_if(&_selected)) { const auto section = over->section; const auto index = over->index; - if (section >= kEmojiSectionCount + if (section >= _staticCount && sectionInfo(section).collapsed && index + 1 == _columnCount * kCollapsedRows) { - _custom[section - kEmojiSectionCount].expanded = true; + _custom[section - _staticCount].expanded = true; resizeToWidth(width()); update(); return; @@ -980,19 +1003,19 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { if (custom && custom->sticker()) { selectCustom(custom); } - } else if (section >= kEmojiSectionCount - && index < _custom[section - kEmojiSectionCount].list.size()) { - auto &set = _custom[section - kEmojiSectionCount]; + } else if (section >= _staticCount + && index < _custom[section - _staticCount].list.size()) { + auto &set = _custom[section - _staticCount]; selectCustom(set.list[index].document); } } else if (const auto set = std::get_if(&pressed)) { - Assert(set->section >= kEmojiSectionCount - && set->section < kEmojiSectionCount + _custom.size()); - displaySet(_custom[set->section - kEmojiSectionCount].id); + Assert(set->section >= _staticCount + && set->section < _staticCount + _custom.size()); + displaySet(_custom[set->section - _staticCount].id); } else if (auto button = std::get_if(&pressed)) { - Assert(button->section >= kEmojiSectionCount - && button->section < kEmojiSectionCount + _custom.size()); - const auto id = _custom[button->section - kEmojiSectionCount].id; + Assert(button->section >= _staticCount + && button->section < _staticCount + _custom.size()); + const auto id = _custom[button->section - _staticCount].id; if (hasRemoveButton(button->section)) { removeSet(id); } else if (hasAddButton(button->section)) { @@ -1074,11 +1097,11 @@ void EmojiListWidget::pickerHidden() { } bool EmojiListWidget::hasRemoveButton(int index) const { - if (index < kEmojiSectionCount - || index >= kEmojiSectionCount + _custom.size()) { + if (index < _staticCount + || index >= _staticCount + _custom.size()) { return false; } - const auto &set = _custom[index - kEmojiSectionCount]; + const auto &set = _custom[index - _staticCount]; return set.canRemove && !set.premiumRequired; } @@ -1096,11 +1119,11 @@ QRect EmojiListWidget::removeButtonRect(const SectionInfo &info) const { } bool EmojiListWidget::hasAddButton(int index) const { - if (index < kEmojiSectionCount - || index >= kEmojiSectionCount + _custom.size()) { + if (index < _staticCount + || index >= _staticCount + _custom.size()) { return false; } - const auto &set = _custom[index - kEmojiSectionCount]; + const auto &set = _custom[index - _staticCount]; return !set.canRemove && !set.premiumRequired; } @@ -1109,24 +1132,24 @@ QRect EmojiListWidget::addButtonRect(int index) const { } bool EmojiListWidget::hasUnlockButton(int index) const { - if (index < kEmojiSectionCount - || index >= kEmojiSectionCount + _custom.size()) { + if (index < _staticCount + || index >= _staticCount + _custom.size()) { return false; } - const auto &set = _custom[index - kEmojiSectionCount]; + const auto &set = _custom[index - _staticCount]; return set.premiumRequired; } QRect EmojiListWidget::unlockButtonRect(int index) const { - Expects(index >= kEmojiSectionCount - && index < kEmojiSectionCount + _custom.size()); + Expects(index >= _staticCount + && index < _staticCount + _custom.size()); return buttonRect(sectionInfo(index), rightButton(index)); } bool EmojiListWidget::hasButton(int index) const { - if (index < kEmojiSectionCount - || index >= kEmojiSectionCount + _custom.size()) { + if (index < _staticCount + || index >= _staticCount + _custom.size()) { return false; } return true; @@ -1151,11 +1174,11 @@ QRect EmojiListWidget::buttonRect( } auto EmojiListWidget::rightButton(int index) const -> const RightButton & { - Expects(index >= kEmojiSectionCount - && index < kEmojiSectionCount + _custom.size()); + Expects(index >= _staticCount + && index < _staticCount + _custom.size()); return hasAddButton(index) ? _add - : _custom[index - kEmojiSectionCount].canRemove + : _custom[index - _staticCount].canRemove ? _restore : _unlock; } @@ -1187,7 +1210,7 @@ void EmojiListWidget::colorChosen(EmojiPtr emoji) { const auto over = std::get_if(&_pickerSelected); if (over && over->section > int(Section::Recent) - && over->section < kEmojiSectionCount + && over->section < _staticCount && over->index < _emoji[over->section].size()) { _emoji[over->section][over->index] = emoji; rtlupdate(emojiRect(over->section, over->index)); @@ -1466,7 +1489,8 @@ std::vector EmojiListWidget::fillIcons() { result.reserve(2 + _custom.size()); result.emplace_back(RecentEmojiSectionSetId()); - if (_custom.empty()) { + if (_mode == Mode::EmojiStatus) { + } else if (_custom.empty()) { using Section = Ui::Emoji::Section; for (auto i = int(Section::People); i <= int(Section::Symbols); ++i) { result.emplace_back(EmojiSectionSetId(Section(i))); @@ -1489,11 +1513,11 @@ int EmojiListWidget::paintButtonGetWidth( const SectionInfo &info, bool selected, QRect clip) const { - if (info.section < kEmojiSectionCount - || info.section >= kEmojiSectionCount + _custom.size()) { + if (info.section < _staticCount + || info.section >= _staticCount + _custom.size()) { return 0; } - auto &custom = _custom[info.section - kEmojiSectionCount]; + auto &custom = _custom[info.section - _staticCount]; if (hasRemoveButton(info.section)) { const auto remove = removeButtonRect(info); if (remove.intersects(clip)) { @@ -1565,7 +1589,7 @@ void EmojiListWidget::updateSelected() { if (hasButton(section) && myrtlrect(buttonRect(section)).contains(p.x(), p.y())) { newSelected = OverButton{ section }; - } else if (section >= kEmojiSectionCount) { + } else if (section >= _staticCount) { newSelected = OverSet{ section }; } } else if (p.y() >= info.rowsTop && p.y() < info.rowsBottom) { @@ -1616,18 +1640,18 @@ void EmojiListWidget::setSelected(OverState newSelected) { void EmojiListWidget::setPressed(OverState newPressed) { if (auto button = std::get_if(&_pressed)) { - Assert(button->section >= kEmojiSectionCount - && button->section < kEmojiSectionCount + _custom.size()); - auto &set = _custom[button->section - kEmojiSectionCount]; + Assert(button->section >= _staticCount + && button->section < _staticCount + _custom.size()); + auto &set = _custom[button->section - _staticCount]; if (set.ripple) { set.ripple->lastStop(); } } _pressed = newPressed; if (auto button = std::get_if(&_pressed)) { - Assert(button->section >= kEmojiSectionCount - && button->section < kEmojiSectionCount + _custom.size()); - auto &set = _custom[button->section - kEmojiSectionCount]; + Assert(button->section >= _staticCount + && button->section < _staticCount + _custom.size()); + auto &set = _custom[button->section - _staticCount]; if (!set.ripple) { set.ripple = createButtonRipple(button->section); } @@ -1675,8 +1699,8 @@ void EmojiListWidget::initButton( std::unique_ptr EmojiListWidget::createButtonRipple( int section) { - Expects(section >= kEmojiSectionCount - && section < kEmojiSectionCount + _custom.size()); + Expects(section >= _staticCount + && section < _staticCount + _custom.size()); const auto remove = hasRemoveButton(section); const auto &st = remove @@ -1694,8 +1718,8 @@ std::unique_ptr EmojiListWidget::createButtonRipple( } QPoint EmojiListWidget::buttonRippleTopLeft(int section) const { - Expects(section >= kEmojiSectionCount - && section < kEmojiSectionCount + _custom.size()); + Expects(section >= _staticCount + && section < _staticCount + _custom.size()); return myrtlrect(buttonRect(section)).topLeft() + (hasRemoveButton(section) @@ -1727,12 +1751,12 @@ void EmojiListWidget::showSet(uint64 setId) { } uint64 EmojiListWidget::sectionSetId(int section) const { - Expects(section < kEmojiSectionCount - || (section - kEmojiSectionCount) < _custom.size()); + Expects(section < _staticCount + || (section - _staticCount) < _custom.size()); - return (section < kEmojiSectionCount) + return (section < _staticCount) ? EmojiSectionSetId(static_cast
(section)) - : _custom[section - kEmojiSectionCount].id; + : _custom[section - _staticCount].id; } tr::phrase<> EmojiCategoryTitle(int index) { diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 0cf343edb..fb7df5665 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -50,16 +50,22 @@ inline constexpr auto kEmojiSectionCount = 8; struct StickerIcon; class EmojiColorPicker; class StickersListFooter; +class GradientPremiumStar; class LocalStickersManager; class EmojiListWidget : public TabbedSelector::Inner , public Ui::AbstractTooltipShower { public: + enum class Mode { + Full, + EmojiStatus, + }; EmojiListWidget( QWidget *parent, not_null controller, - Window::GifPauseReason level); + Window::GifPauseReason level, + Mode mode); ~EmojiListWidget(); using Section = Ui::Emoji::Section; @@ -266,7 +272,10 @@ private: DocumentId documentId, uint64 setId); + Mode _mode = Mode::Full; + const int _staticCount = 0; StickersListFooter *_footer = nullptr; + std::unique_ptr _premiumIcon; std::unique_ptr _localSetsManager; int _counts[kEmojiSectionCount]; @@ -284,6 +293,7 @@ private: QSize _singleSize; QPoint _areaPosition; QPoint _innerPosition; + QPoint _customPosition; RightButton _add; RightButton _unlock; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index c3e4c425c..da9e1f700 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -145,6 +145,42 @@ bool StickersListFooter::ScrollState::animationCallback(crl::time now) { return true; } +GradientPremiumStar::GradientPremiumStar() { + style::PaletteChanged( + ) | rpl::start_with_next([=] { + _image = QImage(); + }, _lifetime); +} + +QImage GradientPremiumStar::image() const { + if (_image.isNull()) { + renderOnDemand(); + } + return _image; +} + +void GradientPremiumStar::renderOnDemand() const { + const auto size = st::stickersPremium.size(); + const auto mask = st::stickersPremium.instance(Qt::white); + const auto factor = style::DevicePixelRatio(); + _image = QImage( + size * factor, + QImage::Format_ARGB32_Premultiplied); + _image.setDevicePixelRatio(factor); + + QPainter p(&_image); + auto gradient = QLinearGradient( + QPoint(0, size.height()), + QPoint(size.width(), 0)); + gradient.setStops({ + { 0., st::stickerPanPremium1->c }, + { 1., st::stickerPanPremium2->c }, + }); + p.fillRect(QRect(QPoint(), size), gradient); + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + p.drawImage(QRect(QPoint(), size), mask); +} + StickersListFooter::StickersListFooter(Descriptor &&descriptor) : InnerFooter(descriptor.parent) , _controller(descriptor.controller) @@ -169,36 +205,6 @@ StickersListFooter::StickersListFooter(Descriptor &&descriptor) ) | rpl::start_with_next([=] { update(); }, lifetime()); - - style::PaletteChanged( - ) | rpl::start_with_next([=] { - _premiumIcon = QImage(); - }, lifetime()); -} - -void StickersListFooter::validatePremiumIcon() const { - if (!_premiumIcon.isNull()) { - return; - } - const auto size = st::stickersPremium.size(); - const auto mask = st::stickersPremium.instance(Qt::white); - const auto factor = style::DevicePixelRatio(); - _premiumIcon = QImage( - size * factor, - QImage::Format_ARGB32_Premultiplied); - _premiumIcon.setDevicePixelRatio(factor); - - QPainter p(&_premiumIcon); - auto gradient = QLinearGradient( - QPoint(0, size.height()), - QPoint(size.width(), 0)); - gradient.setStops({ - { 0., st::stickerPanPremium1->c }, - { 1., st::stickerPanPremium2->c }, - }); - p.fillRect(QRect(QPoint(), size), gradient); - p.setCompositionMode(QPainter::CompositionMode_DestinationIn); - p.drawImage(QRect(QPoint(), size), mask); } void StickersListFooter::clearHeavyData() { @@ -1211,12 +1217,11 @@ void StickersListFooter::paintSetIcon( width(), st::stickerGroupCategorySize); } else if (icon.setId == Data::Stickers::PremiumSetId) { - validatePremiumIcon(); const auto size = st::stickersPremium.size(); p.drawImage( info.adjustedLeft + (_singleWidth - size.width()) / 2, _iconsTop + (st::emojiFooterHeight - size.height()) / 2, - _premiumIcon); + _premiumIcon.image()); } else { using Section = Ui::Emoji::Section; const auto sectionIcon = [&](Section section, bool active) { diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h index 0a9b842f6..449716750 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h @@ -75,6 +75,20 @@ struct StickerIcon { mutable rpl::lifetime lifetime; }; +class GradientPremiumStar { +public: + GradientPremiumStar(); + + [[nodiscard]] QImage image() const; + +private: + void renderOnDemand() const; + + mutable QImage _image; + rpl::lifetime _lifetime; + +}; + class StickersListFooter final : public TabbedSelector::InnerFooter { public: struct Descriptor { @@ -203,7 +217,6 @@ private: void paintSelectionBg(Painter &p) const; void paintSelectionBar(Painter &p) const; void paintLeftRightFading(Painter &p) const; - void validatePremiumIcon() const; void updateEmojiSectionWidth(); void updateEmojiWidthCallback(); @@ -230,7 +243,7 @@ private: OverState _pressed = SpecialOver::None; QPoint _iconsMousePos, _iconsMouseDown; - mutable QImage _premiumIcon; + GradientPremiumStar _premiumIcon; int _iconsLeft = 0; int _iconsRight = 0; int _iconsTop = 0; diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp index 82648c8f9..6b26c494e 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp @@ -132,7 +132,19 @@ void TabbedPanel::moveBottomRight(int bottom, int right) { _right = right; // If the panel is already shown, update the position. if (!isHidden() && isNew) { - moveByBottom(); + moveHorizontally(); + } else { + updateContentHeight(); + } +} + +void TabbedPanel::moveTopRight(int top, int right) { + const auto isNew = (_top != top || _right != right); + _top = top; + _right = right; + // If the panel is already shown, update the position. + if (!isHidden() && isNew) { + moveHorizontally(); } else { updateContentHeight(); } @@ -148,16 +160,26 @@ void TabbedPanel::setDesiredHeightValues( updateContentHeight(); } +void TabbedPanel::setDropDown(bool dropDown) { + selector()->setDropDown(dropDown); + _dropDown = dropDown; +} + void TabbedPanel::updateContentHeight() { auto addedHeight = innerPadding().top() + innerPadding().bottom(); auto marginsHeight = _selector->marginTop() + _selector->marginBottom(); - auto availableHeight = _bottom - marginsHeight; - auto wantedContentHeight = qRound(_heightRatio * availableHeight) - addedHeight; + auto availableHeight = _dropDown + ? (parentWidget()->height() - _top - marginsHeight) + : (_bottom - marginsHeight); + auto wantedContentHeight = qRound(_heightRatio * availableHeight) + - addedHeight; auto contentHeight = marginsHeight + std::clamp( wantedContentHeight, _minContentHeight, _maxContentHeight); - auto resultTop = _bottom - addedHeight - contentHeight; + auto resultTop = _dropDown + ? _top + : (_bottom - addedHeight - contentHeight); if (contentHeight == _contentHeight) { move(x(), resultTop); return; @@ -204,7 +226,7 @@ void TabbedPanel::paintEvent(QPaintEvent *e) { } } -void TabbedPanel::moveByBottom() { +void TabbedPanel::moveHorizontally() { const auto right = std::max(parentWidget()->width() - _right, 0); moveToRight(right, y()); updateContentHeight(); @@ -318,7 +340,7 @@ void TabbedPanel::startShowAnimation() { if (!_a_show.animating()) { auto image = grabForAnimation(); - _showAnimation = std::make_unique(st::emojiPanAnimation, Ui::PanelAnimation::Origin::BottomRight); + _showAnimation = std::make_unique(st::emojiPanAnimation, _dropDown ? Ui::PanelAnimation::Origin::TopRight : Ui::PanelAnimation::Origin::BottomRight); auto inner = rect().marginsRemoved(st::emojiPanMargins); _showAnimation->setFinalImage(std::move(image), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor())); _showAnimation->setCornerMasks(Images::CornersMask(ImageRoundRadius::Small)); @@ -402,7 +424,7 @@ void TabbedPanel::showStarted() { } if (isHidden()) { _selector->showStarted(); - moveByBottom(); + moveHorizontally(); raise(); show(); startShowAnimation(); @@ -424,7 +446,7 @@ bool TabbedPanel::eventFilter(QObject *obj, QEvent *e) { void TabbedPanel::showFromSelector() { if (isHidden()) { - moveByBottom(); + moveHorizontally(); startShowAnimation(); show(); } diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_panel.h b/Telegram/SourceFiles/chat_helpers/tabbed_panel.h index 1e1fd6bf3..c592c4a48 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_panel.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_panel.h @@ -41,10 +41,12 @@ public: [[nodiscard]] not_null selector() const; void moveBottomRight(int bottom, int right); + void moveTopRight(int top, int right); void setDesiredHeightValues( float64 ratio, int minHeight, int maxHeight); + void setDropDown(bool dropDown); void hideFast(); bool hiding() const { @@ -76,7 +78,7 @@ private: TabbedSelector *nonOwnedSelector); void hideByTimerOrLeave(); - void moveByBottom(); + void moveHorizontally(); void showFromSelector(); style::margins innerPadding() const; @@ -103,6 +105,7 @@ private: int _contentMaxHeight = 0; int _contentHeight = 0; + int _top = 0; int _bottom = 0; int _right = 0; float64 _heightRatio = 1.; @@ -113,6 +116,7 @@ private: Ui::Animations::Simple _a_show; bool _shouldFinishHide = false; + bool _dropDown = false; bool _hiding = false; bool _hideAfterSlide = false; diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 3e5e841a0..f9e3d6242 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -439,7 +439,14 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) { auto createWidget = [&]() -> object_ptr { switch (type) { case SelectorTab::Emoji: - return object_ptr(this, _controller, _level); + using EmojiMode = EmojiListWidget::Mode; + return object_ptr( + this, + _controller, + _level, + (_mode == Mode::EmojiStatus + ? EmojiMode::EmojiStatus + : EmojiMode::Full)); case SelectorTab::Stickers: return object_ptr(this, _controller, _level); case SelectorTab::Gifs: @@ -561,7 +568,7 @@ void TabbedSelector::resizeEvent(QResizeEvent *e) { } auto scrollWidth = width() - st::roundRadiusSmall; - auto scrollHeight = height() - scrollTop() - marginBottom(); + auto scrollHeight = height() - scrollTop() - scrollBottom(); auto inner = currentTab()->widget(); auto innerWidth = scrollWidth - st::emojiScroll.width; auto updateScrollGeometry = [&] { @@ -591,7 +598,7 @@ void TabbedSelector::resizeEvent(QResizeEvent *e) { st::lineWidth); updateRestrictedLabelGeometry(); - _footerTop = height() - st::emojiFooterHeight; + _footerTop = _dropDown ? 0 : (height() - st::emojiFooterHeight); for (auto &tab : _tabs) { tab.footer()->resizeToWidth(width()); tab.footer()->moveToLeft(0, _footerTop); @@ -630,21 +637,7 @@ void TabbedSelector::paintEvent(QPaintEvent *e) { void TabbedSelector::paintSlideFrame(Painter &p) { if (_roundRadius > 0) { - const auto topPart = QRect( - 0, - 0, - width(), - _tabsSlider - ? _tabsSlider->height() + _roundRadius - : 3 * _roundRadius); - Ui::FillRoundRect( - p, - topPart, - st::emojiPanBg, - ImageRoundRadius::Small, - tabbed() - ? RectPart::FullTop | RectPart::NoTopBottom - : RectPart::FullTop); + paintBgRoundedPart(p); } else if (_tabsSlider) { p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg); } @@ -652,43 +645,53 @@ void TabbedSelector::paintSlideFrame(Painter &p) { _slideAnimation->paintFrame(p, slideDt, 1.); } -void TabbedSelector::paintContent(Painter &p) { - auto &bottomBg = hasSectionIcons() - ? st::emojiPanCategories - : st::emojiPanBg; - if (_roundRadius > 0) { - const auto topPart = QRect( +void TabbedSelector::paintBgRoundedPart(Painter &p) { + const auto threeRadius = 3 * _roundRadius; + const auto topOrBottomPart = _dropDown + ? QRect(0, height() - threeRadius, width(), threeRadius) + : QRect( 0, 0, width(), - _tabsSlider + (_tabsSlider ? _tabsSlider->height() + _roundRadius - : 3 * _roundRadius); - Ui::FillRoundRect( - p, - topPart, - st::emojiPanBg, - ImageRoundRadius::Small, - tabbed() - ? RectPart::FullTop | RectPart::NoTopBottom - : RectPart::FullTop); + : threeRadius)); + Ui::FillRoundRect( + p, + topOrBottomPart, + st::emojiPanBg, + ImageRoundRadius::Small, + (_dropDown + ? RectPart::FullBottom + : tabbed() + ? (RectPart::FullTop | RectPart::NoTopBottom) + : RectPart::FullTop)); +} - const auto bottomPart = QRect( +void TabbedSelector::paintContent(Painter &p) { + auto &footerBg = hasSectionIcons() + ? st::emojiPanCategories + : st::emojiPanBg; + if (_roundRadius > 0) { + paintBgRoundedPart(p); + + const auto footerPart = QRect( 0, - _footerTop - _roundRadius, + _footerTop - (_dropDown ? 0 : _roundRadius), width(), st::emojiFooterHeight + _roundRadius); Ui::FillRoundRect( p, - bottomPart, - bottomBg, + footerPart, + footerBg, ImageRoundRadius::Small, - RectPart::NoTopBottom | RectPart::FullBottom); + (RectPart::NoTopBottom + | (_dropDown ? RectPart::FullTop : RectPart::FullBottom))); } else { if (_tabsSlider) { p.fillRect(0, 0, width(), _tabsSlider->height(), st::emojiPanBg); } - p.fillRect(0, _footerTop, width(), st::emojiFooterHeight, bottomBg); + p.fillRect(0, _footerTop, width(), st::emojiFooterHeight, footerBg); } auto sidesTop = marginTop(); @@ -710,17 +713,23 @@ void TabbedSelector::paintContent(Painter &p) { } int TabbedSelector::marginTop() const { - return _tabsSlider + return _dropDown + ? st::emojiFooterHeight + : _tabsSlider ? (_tabsSlider->height() - st::lineWidth) : _roundRadius; } int TabbedSelector::scrollTop() const { - return tabbed() ? marginTop() : 0; + return tabbed() ? marginTop() : _dropDown ? st::emojiFooterHeight : 0; } int TabbedSelector::marginBottom() const { - return st::emojiFooterHeight; + return _dropDown ? _roundRadius : st::emojiFooterHeight; +} + +int TabbedSelector::scrollBottom() const { + return _dropDown ? 0 : marginBottom(); } void TabbedSelector::refreshStickers() { diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index dfb7addd6..2b1e10acc 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -70,6 +70,7 @@ public: Full, EmojiOnly, MediaEditor, + EmojiStatus, }; enum class Action { Update, @@ -110,9 +111,10 @@ public: void beforeHiding(); void afterShown(); - int marginTop() const; - int marginBottom() const; - int scrollTop() const; + [[nodiscard]] int marginTop() const; + [[nodiscard]] int marginBottom() const; + [[nodiscard]] int scrollTop() const; + [[nodiscard]] int scrollBottom() const; bool preventAutoHide() const; bool isSliding() const { @@ -128,6 +130,9 @@ public: } void showMenuWithType(SendMenu::Type type); + void setDropDown(bool dropDown) { + _dropDown = dropDown; + } // Float player interface. bool floatPlayerHandleWheelEvent(QEvent *e); @@ -193,6 +198,7 @@ private: Tab createTab(SelectorTab type, int index); void paintSlideFrame(Painter &p); + void paintBgRoundedPart(Painter &p); void paintContent(Painter &p); void checkRestrictedPeer(); @@ -252,6 +258,7 @@ private: const bool _hasGifsTab; const bool _hasMasksTab; const bool _tabbed; + bool _dropDown = false; base::unique_qptr _menu; diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 38ed694b6..b8c861171 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "ui/effects/ripple_animation.h" +#include "ui/text/text_block.h" #include "ui/text/text_utilities.h" #include "ui/special_buttons.h" #include "ui/unread_badge.h" @@ -215,29 +216,25 @@ void Cover::setBadge(Badge badge, DocumentId emojiStatusId) { _badge = badge; _emojiStatusId = emojiStatusId; _emojiStatus = nullptr; - _verifiedCheck.destroy(); - _scamFakeBadge.destroy(); + _badgeView.destroy(); switch (_badge) { case Badge::Verified: case Badge::Premium: { - const auto icon = (_badge == Badge::Verified) - ? &st::infoVerifiedCheck - : &st::infoPremiumStar; - _verifiedCheck.create(this); - _verifiedCheck->show(); - _verifiedCheck->resize(icon->size()); + _badgeView.create(this); + _badgeView->show(); if (_emojiStatusId) { auto &owner = _controller->session().data(); _emojiStatus = owner.customEmojiManager().create( _emojiStatusId, - [raw = _verifiedCheck.data()]{ raw->update(); }, - Data::CustomEmojiManager::SizeTag::Normal); - } - - _verifiedCheck->paintRequest( - ) | rpl::start_with_next([=, check = _verifiedCheck.data()] { - Painter p(check); - if (_emojiStatus) { + [raw = _badgeView.data()]{ raw->update(); }, + Data::CustomEmojiManager::SizeTag::Large); + const auto size = Ui::Emoji::GetSizeLarge() + / style::DevicePixelRatio(); + const auto emoji = Ui::Text::AdjustCustomEmojiSize(size); + _badgeView->resize(emoji, emoji); + _badgeView->paintRequest( + ) | rpl::start_with_next([=, check = _badgeView.data()]{ + Painter p(check); _emojiStatus->paint( p, 0, @@ -246,24 +243,17 @@ void Cover::setBadge(Badge badge, DocumentId emojiStatusId) { st::windowBgOver->c, _controller->isGifPausedAtLeastFor( Window::GifPauseReason::Layer)); - } else { - icon->paint(p, 0, 0, check->width()); - } - }, _verifiedCheck->lifetime()); - - if (_badge == Badge::Premium) { - const auto userId = peerToUser(_peer->id).bare; - _verifiedCheck->setClickedCallback([=] { - if (_peer->isSelf()) { - showEmojiStatusSelector(); - } else { - ::Settings::ShowPremium( - _controller, - u"profile__%1"_q.arg(userId)); - } - }); + }, _badgeView->lifetime()); } else { - _verifiedCheck->setAttribute(Qt::WA_TransparentForMouseEvents); + const auto icon = (_badge == Badge::Verified) + ? &st::infoVerifiedCheck + : &st::infoPremiumStar; + _badgeView->resize(icon->size()); + _badgeView->paintRequest( + ) | rpl::start_with_next([=, check = _badgeView.data()]{ + Painter p(check); + icon->paint(p, 0, 0, check->width()); + }, _badgeView->lifetime()); } } break; case Badge::Scam: @@ -271,13 +261,13 @@ void Cover::setBadge(Badge badge, DocumentId emojiStatusId) { const auto fake = (_badge == Badge::Fake); const auto size = Ui::ScamBadgeSize(fake); const auto skip = st::infoVerifiedCheckPosition.x(); - _scamFakeBadge.create(this); - _scamFakeBadge->show(); - _scamFakeBadge->resize( + _badgeView.create(this); + _badgeView->show(); + _badgeView->resize( size.width() + 2 * skip, size.height() + 2 * skip); - _scamFakeBadge->paintRequest( - ) | rpl::start_with_next([=, badge = _scamFakeBadge.data()]{ + _badgeView->paintRequest( + ) | rpl::start_with_next([=, badge = _badgeView.data()]{ Painter p(badge); Ui::DrawScamBadge( fake, @@ -285,28 +275,51 @@ void Cover::setBadge(Badge badge, DocumentId emojiStatusId) { badge->rect().marginsRemoved({ skip, skip, skip, skip }), badge->width(), st::attentionButtonFg); - }, _scamFakeBadge->lifetime()); + }, _badgeView->lifetime()); } break; } + + if (_badge == Badge::Premium) { + const auto userId = peerToUser(_peer->id).bare; + _badgeView->setClickedCallback([=] { + if (_peer->isSelf()) { + showEmojiStatusSelector(); + } else { + ::Settings::ShowPremium( + _controller, + u"profile__%1"_q.arg(userId)); + } + }); + } else { + _badgeView->setAttribute(Qt::WA_TransparentForMouseEvents); + } + refreshNameGeometry(width()); } void Cover::showEmojiStatusSelector() { - Expects(_verifiedCheck != nullptr); + Expects(_badgeView != nullptr); if (!_emojiStatusPanel) { createEmojiStatusSelector(); } const auto parent = _emojiStatusPanel->parentWidget(); - const auto global = _verifiedCheck->mapToGlobal({ 0, 0 }); + const auto global = _badgeView->mapToGlobal({ 0, 0 }); const auto local = parent->mapFromGlobal(global); - _emojiStatusPanel->moveBottomRight( - local.y(), - local.x() + _verifiedCheck->width() * 3); + _emojiStatusPanel->moveTopRight( + local.y() + _badgeView->height(), + local.x() + _badgeView->width() * 3); _emojiStatusPanel->toggleAnimated(); } void Cover::createEmojiStatusSelector() { + const auto set = [=](DocumentId id) { + _controller->session().user()->setEmojiStatus(id); + _controller->session().api().request(MTPaccount_UpdateEmojiStatus( + id ? MTP_emojiStatus(MTP_long(id)) : MTP_emojiStatusEmpty() + )).send(); + _emojiStatusPanel->hideAnimated(); + }; const auto container = _controller->window().widget()->bodyWidget(); using Selector = ChatHelpers::TabbedSelector; _emojiStatusPanel = base::make_unique_q( @@ -316,20 +329,21 @@ void Cover::createEmojiStatusSelector() { nullptr, _controller, Window::GifPauseReason::Layer, - ChatHelpers::TabbedSelector::Mode::EmojiOnly)); + ChatHelpers::TabbedSelector::Mode::EmojiStatus)); + _emojiStatusPanel->setDropDown(true); _emojiStatusPanel->setDesiredHeightValues( 1., st::emojiPanMinHeight / 2, st::emojiPanMinHeight); _emojiStatusPanel->hide(); _emojiStatusPanel->selector()->setAllowEmojiWithoutPremium(false); + _emojiStatusPanel->selector()->emojiChosen( + ) | rpl::start_with_next([=] { + set(0); + }, _emojiStatusPanel->lifetime()); _emojiStatusPanel->selector()->customEmojiChosen( ) | rpl::start_with_next([=](Selector::FileChosen data) { - _controller->session().user()->setEmojiStatus(data.document->id); - _controller->session().api().request(MTPaccount_UpdateEmojiStatus( - MTP_emojiStatus(MTP_long(data.document->id)) - )).send(); - _emojiStatusPanel->hideAnimated(); + set(data.document->id); }, _emojiStatusPanel->lifetime()); _emojiStatusPanel->selector()->showPromoForPremiumEmoji(); } @@ -392,31 +406,22 @@ void Cover::refreshNameGeometry(int newWidth) { auto nameWidth = newWidth - nameLeft - st::infoProfileNameRight; - if (_verifiedCheck) { - nameWidth -= st::infoVerifiedCheckPosition.x() - + _verifiedCheck->width(); - } else if (_scamFakeBadge) { - nameWidth -= st::infoVerifiedCheckPosition.x() - + _scamFakeBadge->width(); + if (_badgeView) { + nameWidth -= st::infoVerifiedCheckPosition.x() + _badgeView->width(); } _name->resizeToNaturalWidth(nameWidth); _name->moveToLeft(nameLeft, nameTop, newWidth); - if (_verifiedCheck) { - const auto checkLeft = nameLeft - + _name->width() - + st::infoVerifiedCheckPosition.x(); - const auto checkTop = nameTop - + st::infoVerifiedCheckPosition.y(); - _verifiedCheck->moveToLeft(checkLeft, checkTop, newWidth); - } else if (_scamFakeBadge) { - const auto skip = st::infoVerifiedCheckPosition.x(); - const auto badgeLeft = nameLeft - + _name->width() - + st::infoVerifiedCheckPosition.x() - - skip; + if (_badgeView) { + const auto star = !_emojiStatus + && (_badge == Badge::Premium || _badge == Badge::Verified); + const auto fake = !_emojiStatus && !star; + const auto skip = fake ? 0 : st::infoVerifiedCheckPosition.x(); + const auto badgeLeft = nameLeft + _name->width() + skip; const auto badgeTop = nameTop - + (_name->height() - _scamFakeBadge->height()) / 2; - _scamFakeBadge->moveToLeft(badgeLeft, badgeTop, newWidth); + + (star + ? st::infoVerifiedCheckPosition.y() + : (_name->height() - _badgeView->height()) / 2); + _badgeView->moveToLeft(badgeLeft, badgeTop, newWidth); } } diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.h b/Telegram/SourceFiles/info/profile/info_profile_cover.h index eaa00d934..3a18c0674 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.h +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.h @@ -82,8 +82,7 @@ private: object_ptr _userpic; object_ptr _name = { nullptr }; - object_ptr _verifiedCheck = { nullptr }; - object_ptr _scamFakeBadge = { nullptr }; + object_ptr _badgeView = { nullptr }; object_ptr _status = { nullptr }; //object_ptr _dropArea = { nullptr }; base::Timer _refreshStatusTimer;