diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 9335ad14a..7426a1cc5 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -114,21 +114,21 @@ emojiTabs: SettingsSlider(defaultTabsSlider) { } emojiScroll: defaultSolidScroll; emojiRecent: icon {{ "emoji/emoji_recent", emojiIconFg }}; -emojiRecentActive: icon {{ "emoji/emoji_recent", emojiIconFgActive }}; +emojiRecentActive: icon {{ "emoji/emoji_recent", windowBoldFg }}; emojiPeople: icon {{ "emoji/emoji_people", emojiIconFg }}; -emojiPeopleActive: icon {{ "emoji/emoji_people", emojiIconFgActive }}; +emojiPeopleActive: icon {{ "emoji/emoji_people", windowBoldFg }}; emojiNature: icon {{ "emoji/emoji_nature", emojiIconFg }}; -emojiNatureActive: icon {{ "emoji/emoji_nature", emojiIconFgActive }}; +emojiNatureActive: icon {{ "emoji/emoji_nature", windowBoldFg }}; emojiFood: icon {{ "emoji/emoji_food", emojiIconFg }}; -emojiFoodActive: icon {{ "emoji/emoji_food", emojiIconFgActive }}; +emojiFoodActive: icon {{ "emoji/emoji_food", windowBoldFg }}; emojiActivity: icon {{ "emoji/emoji_activity", emojiIconFg }}; -emojiActivityActive: icon {{ "emoji/emoji_activity", emojiIconFgActive }}; +emojiActivityActive: icon {{ "emoji/emoji_activity", windowBoldFg }}; emojiTravel: icon {{ "emoji/emoji_travel", emojiIconFg }}; -emojiTravelActive: icon {{ "emoji/emoji_travel", emojiIconFgActive }}; +emojiTravelActive: icon {{ "emoji/emoji_travel", windowBoldFg }}; emojiObjects: icon {{ "emoji/emoji_objects", emojiIconFg }}; -emojiObjectsActive: icon {{ "emoji/emoji_objects", emojiIconFgActive }}; +emojiObjectsActive: icon {{ "emoji/emoji_objects", windowBoldFg }}; emojiSymbols: icon {{ "emoji/emoji_symbols", emojiIconFg }}; -emojiSymbolsActive: icon {{ "emoji/emoji_symbols", emojiIconFgActive }}; +emojiSymbolsActive: icon {{ "emoji/emoji_symbols", windowBoldFg }}; emojiFooterHeight: 46px; emojiCategorySkip: 4px; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 08d9ab093..ace1b4b0b 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -1129,7 +1129,6 @@ void EmojiListWidget::refreshRecent() { clearSelection(); _emoji[0] = Core::App().settings().recentEmojiSection(); _counts[0] = _emoji[0].size(); - refreshCustom(); resizeToWidth(width()); } @@ -1371,10 +1370,15 @@ void EmojiListWidget::showEmojiSection(Section section) { showSet(EmojiSectionSetId(section)); } +void EmojiListWidget::refreshEmoji() { + refreshRecent(); + refreshCustom(); +} + void EmojiListWidget::showSet(uint64 setId) { clearSelection(); - refreshRecent(); + refreshEmoji(); auto y = 0; enumerateSections([&](const SectionInfo &info) { diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 44f696a2f..668225161 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -69,6 +69,8 @@ public: QPoint tooltipPos() const override; bool tooltipWindowActive() const override; + void refreshEmoji(); + [[nodiscard]] rpl::producer chosen() const; [[nodiscard]] auto customChosen() const -> rpl::producer; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index 37ab346da..32c7e71bf 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -33,6 +33,9 @@ constexpr auto kEmojiSectionSetIdBase = uint64(0x77FF'FFFF'FFFF'FFF0ULL); using EmojiSection = Ui::Emoji::Section; void UpdateAnimated(anim::value &value, int to) { + if (int(base::SafeRound(value.to())) == to) { + return; + } value = anim::value( (value.from() != value.to()) ? value.from() : to, to); @@ -118,13 +121,12 @@ StickersListFooter::StickersListFooter(Descriptor &&descriptor) , _searchButtonVisible(descriptor.searchButtonVisible) , _settingsButtonVisible(descriptor.settingsButtonVisible) , _iconsAnimation([=](crl::time now) { - return iconsAnimationCallback(now); + return iconsAnimationCallback(_iconState, now); }) , _subiconsAnimation([=](crl::time now) { - return iconsAnimationCallback(now); + return iconsAnimationCallback(_subiconState, now); }) , _selectionBg(st::roundRadiusSmall, st::windowBgRipple) -, _emojiIconWidth(st::stickerIconWidth) , _barSelection(descriptor.barSelection) { setMouseTracking(true); @@ -273,11 +275,14 @@ void StickersListFooter::enumerateIcons( auto left = _iconsLeft - iconsX; const auto emojiId = AllEmojiSectionSetId(); const auto right = width(); + const auto single = st::stickerIconWidth; for (auto i = 0, count = int(_icons.size()); i != count; ++i) { auto &icon = _icons[i]; const auto width = (icon.setId == emojiId) - ? _emojiIconWidthAnimation.value(_emojiIconWidth) - : st::stickerIconWidth; + ? _subiconsWidthAnimation.value(_subiconsExpanded + ? _subiconsWidth + : single) + : single; const auto visible = (left + width > 0 && left < right); const auto result = callback({ .index = i, @@ -296,13 +301,13 @@ void StickersListFooter::enumerateSubicons( Fn callback) const { auto iconsX = int(base::SafeRound(_subiconState.x.current())); auto left = -iconsX; - const auto right = _emojiIconWidth; + const auto right = _subiconsWidth; using Section = Ui::Emoji::Section; for (auto i = int(Section::People); i <= int(Section::Symbols); ++i) { const auto width = st::stickerIconWidth; const auto visible = (left + width > 0 && left < right); const auto result = callback({ - .index = i, + .index = i - int(Section::People), .left = left, .width = int(base::SafeRound(width)), .visible = visible, @@ -353,8 +358,8 @@ void StickersListFooter::preloadImages() { } void StickersListFooter::validateSelectedIcon( - uint64 setId, - ValidateIconAnimations animations) { + uint64 setId, + ValidateIconAnimations animations) { _activeByScrollId = setId; using EmojiSection = Ui::Emoji::Section; @@ -389,21 +394,18 @@ void StickersListFooter::validateSelectedIcon( } void StickersListFooter::updateEmojiSectionWidth() { - _emojiIconExpanded = (_iconState.selected >= 0) + const auto expanded = (_iconState.selected >= 0) && (_iconState.selected < _icons.size()) && (_icons[_iconState.selected].setId == AllEmojiSectionSetId()); - const auto desired = _emojiIconExpanded - ? (9 * st::stickerIconWidth / 2) - : st::stickerIconWidth; - if (_emojiIconWidth == desired) { + if (_subiconsExpanded == expanded) { return; } - _emojiIconWidthAnimation.start( + _subiconsExpanded = expanded; + _subiconsWidthAnimation.start( [=] { updateEmojiWidthCallback(); }, - _emojiIconWidth, - desired, - st::stickerIconMove); - _emojiIconWidth = desired; + expanded ? st::stickerIconWidth : _subiconsWidth, + expanded ? _subiconsWidth : st::stickerIconWidth, + st::slideDuration); } void StickersListFooter::updateEmojiWidthCallback() { @@ -411,6 +413,9 @@ void StickersListFooter::updateEmojiWidthCallback() { const auto info = iconInfo(_iconState.selected); UpdateAnimated(_iconState.selectionX, info.left); UpdateAnimated(_iconState.selectionWidth, info.width); + if (_iconsAnimation.animating()) { + iconsAnimationCallback(_iconState, crl::now()); + } } void StickersListFooter::setSelectedIcon( @@ -421,10 +426,11 @@ void StickersListFooter::setSelectedIcon( } _iconState.selected = newSelected; updateEmojiSectionWidth(); + const auto shift = int(base::SafeRound(_iconState.x.current())); const auto info = iconInfo(_iconState.selected); - UpdateAnimated(_iconState.selectionX, info.left, animations); + UpdateAnimated(_iconState.selectionX, shift + info.left, animations); UpdateAnimated(_iconState.selectionWidth, info.width, animations); - const auto relativeLeft = info.left - _iconsLeft; + const auto relativeLeft = shift + info.left - _iconsLeft; const auto iconsWidthForCentering = 2 * relativeLeft + info.width; const auto iconsXFinal = std::clamp( (_iconsLeft + iconsWidthForCentering + _iconsRight - width()) / 2, @@ -449,23 +455,21 @@ void StickersListFooter::setSelectedSubicon( return; } _subiconState.selected = newSelected; + const auto shift = int(base::SafeRound(_subiconState.x.current())); const auto info = subiconInfo(_subiconState.selected); - updateEmojiSectionWidth(); - UpdateAnimated(_subiconState.selectionX, info.left, animations); + UpdateAnimated(_subiconState.selectionX, shift + info.left, animations); UpdateAnimated(_subiconState.selectionWidth, info.width, animations); - const auto relativeLeft = info.left; + const auto relativeLeft = shift + info.left; const auto subiconsWidthForCentering = 2 * relativeLeft + info.width; const auto subiconsXFinal = std::clamp( - (subiconsWidthForCentering - width()) / 2, + (subiconsWidthForCentering - _subiconsWidth) / 2, 0, _subiconState.max); if (animations == ValidateIconAnimations::None) { - _subiconState.selectionX = anim::value( - subiconsXFinal, - subiconsXFinal); + _subiconState.x = anim::value(subiconsXFinal, subiconsXFinal); _subiconsAnimation.stop(); } else { - _subiconState.selectionX.start(subiconsXFinal); + _subiconState.x.start(subiconsXFinal); _subiconState.animationStart = crl::now(); _subiconsAnimation.start(); } @@ -474,7 +478,7 @@ void StickersListFooter::setSelectedSubicon( } void StickersListFooter::processHideFinished() { - _iconOver = _iconDown = SpecialOver::None; + _selected = _pressed = SpecialOver::None; _iconsAnimation.stop(); _iconState.animationStart = 0; _iconState.x.finish(); @@ -603,6 +607,7 @@ void StickersListFooter::resizeEvent(QResizeEvent *e) { resizeSearchControls(); } refreshIconsGeometry(ValidateIconAnimations::None); + refreshSubiconsGeometry(); } void StickersListFooter::resizeSearchControls() { @@ -637,12 +642,12 @@ void StickersListFooter::mousePressEvent(QMouseEvent *e) { _iconsMousePos = e ? e->globalPos() : QCursor::pos(); updateSelected(); - if (_iconOver == SpecialOver::Settings) { + if (_selected == SpecialOver::Settings) { _openSettingsRequests.fire({}); - } else if (_iconOver == SpecialOver::Search) { + } else if (_selected == SpecialOver::Search) { toggleSearch(true); } else { - _iconDown = _iconOver; + _pressed = _selected; _iconsMouseDown = _iconsMousePos; _iconsStartX = qRound(_iconState.x.current()); } @@ -654,7 +659,7 @@ void StickersListFooter::mouseMoveEvent(QMouseEvent *e) { if (!_iconsDragging && !_icons.empty() - && v::is(_iconDown)) { + && v::is(_pressed)) { if ((_iconsMousePos - _iconsMouseDown).manhattanLength() >= QApplication::startDragDistance()) { _iconsDragging = true; } @@ -679,7 +684,7 @@ void StickersListFooter::mouseReleaseEvent(QMouseEvent *e) { return; } - const auto wasDown = std::exchange(_iconDown, SpecialOver::None); + const auto wasDown = std::exchange(_pressed, SpecialOver::None); _iconsMousePos = e ? e->globalPos() : QCursor::pos(); if (_iconsDragging) { @@ -688,12 +693,23 @@ void StickersListFooter::mouseReleaseEvent(QMouseEvent *e) { } updateSelected(); - if (wasDown == _iconOver) { - if (const auto index = std::get_if(&_iconOver)) { - const auto info = iconInfo(*index); + if (wasDown == _selected) { + if (const auto icon = std::get_if(&_selected)) { + const auto info = iconInfo(icon->index); _iconState.selectionX = anim::value(info.left, info.left); _iconState.selectionWidth = anim::value(info.width, info.width); - _setChosen.fire_copy(_icons[*index].setId); + const auto subinfo = subiconInfo(icon->subindex); + _subiconState.selectionX = anim::value( + subinfo.left, + subinfo.left); + _subiconState.selectionWidth = anim::value( + subinfo.width, + subinfo.width); + const auto setId = _icons[icon->index].setId; + _setChosen.fire_copy((setId == AllEmojiSectionSetId()) + ? EmojiSectionSetId( + EmojiSection(int(EmojiSection::People) + icon->subindex)) + : setId); } } } @@ -717,8 +733,8 @@ bool StickersListFooter::eventHook(QEvent *e) { if (e->type() == QEvent::TouchBegin) { } else if (e->type() == QEvent::Wheel) { if (!_icons.empty() - && v::is(_iconOver) - && (_iconDown == SpecialOver::None)) { + && v::is(_selected) + && (_pressed == SpecialOver::None)) { scrollByWheelEvent(static_cast(e)); } } @@ -789,7 +805,7 @@ void StickersListFooter::clipCallback( } void StickersListFooter::updateSelected() { - if (_iconDown != SpecialOver::None) { + if (_pressed != SpecialOver::None) { return; } @@ -821,20 +837,30 @@ void StickersListFooter::updateSelected() { x += qRound(_iconState.x.current()); enumerateIcons([&](const IconInfo &info) { if (x >= info.left && x < info.left + info.width) { - newOver = info.index; + newOver = IconId{ .index = info.index }; + if (_icons[info.index].setId == AllEmojiSectionSetId()) { + const auto subx = (x - info.left); + enumerateSubicons([&](const IconInfo &info) { + if (subx >= info.left && subx < info.left + info.width) { + v::get(newOver).subindex = info.index; + return false; + } + return true; + }); + } return false; } return true; }); } } - if (newOver != _iconOver) { + if (newOver != _selected) { if (newOver == SpecialOver::None) { setCursor(style::cur_default); - } else if (_iconOver == SpecialOver::None) { + } else if (_selected == SpecialOver::None) { setCursor(style::cur_pointer); } - _iconOver = newOver; + _selected = newOver; } } @@ -881,7 +907,7 @@ void StickersListFooter::refreshIcons( void StickersListFooter::refreshIconsGeometry( ValidateIconAnimations animations) { - _iconOver = _iconDown = SpecialOver::None; + _selected = _pressed = SpecialOver::None; _iconState.x.finish(); _iconState.selectionX.finish(); _iconState.selectionWidth.finish(); @@ -896,9 +922,39 @@ void StickersListFooter::refreshIconsGeometry( } updateSelected(); validateSelectedIcon(_activeByScrollId, animations); + refreshSubiconsGeometry(); update(); } +void StickersListFooter::refreshSubiconsGeometry() { + using Section = Ui::Emoji::Section; + _subiconState.x.finish(); + _subiconState.selectionX.finish(); + _subiconState.selectionWidth.finish(); + _subiconState.animationStart = 0; + _subiconsAnimation.stop(); + const auto single = st::stickerIconWidth; + const auto half = single / 2; + const auto count = int(Section::Symbols) - int(Section::Recent); + const auto widthMax = count * single; + const auto widthMin = 4 * single + half; + const auto collapsedWidth = int(_icons.size()) * single; + _subiconsWidth = std::clamp( + width() + single - collapsedWidth, + widthMin, + widthMax); + if (_subiconsWidth < widthMax) { + _subiconsWidth = ((_subiconsWidth - half) / single) * single + half; + } + const auto &last = subiconInfo(int(Section::Symbols)); + _subiconState.max = std::max( + widthMax - _subiconsWidth, + 0); + if (_subiconState.x.current() > _subiconState.max) { + _subiconState.x = anim::value(_subiconState.max, _subiconState.max); + } +} + bool StickersListFooter::hasOnlyFeaturedSets() const { return (_icons.size() == 1) && (_icons[0].setId == Data::Stickers::FeaturedSetId); @@ -1069,21 +1125,32 @@ void StickersListFooter::paintSetIcon( _premiumIcon); } else { using Section = Ui::Emoji::Section; - const auto sectionIcon = [&](Section section) { - switch (section) { - case Section::Recent: return &st::emojiRecent; - case Section::People: return &st::emojiPeople; - case Section::Nature: return &st::emojiNature; - case Section::Food: return &st::emojiFood; - case Section::Activity: return &st::emojiActivity; - case Section::Travel: return &st::emojiTravel; - case Section::Objects: return &st::emojiObjects; - case Section::Symbols: return &st::emojiSymbols; - } - Unexpected("Section in SetIdEmojiSection result."); + const auto sectionIcon = [&](Section section, bool active) { + const auto icons = std::array{ + &st::emojiRecent, + &st::emojiRecentActive, + &st::emojiPeople, + &st::emojiPeopleActive, + &st::emojiNature, + &st::emojiNatureActive, + &st::emojiFood, + &st::emojiFoodActive, + &st::emojiActivity, + &st::emojiActivityActive, + &st::emojiTravel, + &st::emojiTravelActive, + &st::emojiObjects, + &st::emojiObjectsActive, + &st::emojiSymbols, + &st::emojiSymbolsActive, + }; + const auto index = int(section) * 2 + (active ? 1 : 0); + + Assert(index >= 0 && index < icons.size()); + return icons[index]; }; - auto left = info.left; - const auto paintOne = [&](const style::icon *icon) { + const auto left = info.left; + const auto paintOne = [&](int left, const style::icon *icon) { icon->paint( p, left + (st::stickerIconWidth - icon->width()) / 2, @@ -1100,13 +1167,19 @@ void StickersListFooter::paintSetIcon( info.width - 2 * skip, st::emojiFooterHeight, Qt::IntersectClip); - for (auto i = int(Section::People); i <= int(Section::Symbols); ++i) { - paintOne(sectionIcon(Section(i))); - left += st::stickerIconWidth; - } + enumerateSubicons([&](const IconInfo &info) { + if (info.visible) { + paintOne( + info.left + left, + sectionIcon( + Section(int(Section::People) + info.index), + (_subiconState.selected == info.index))); + } + return true; + }); p.restore(); } else { - paintOne([&] { + paintOne(left, [&] { if (icon.setId == Data::Stickers::FeaturedSetId) { const auto session = &_controller->session(); return session->data().stickers().featuredSetsUnreadCount() @@ -1114,8 +1187,10 @@ void StickersListFooter::paintSetIcon( : &st::stickersTrending; //} else if (setId == Stickers::FavedSetId) { // return &st::stickersFaved; + } else if (icon.setId == AllEmojiSectionSetId()) { + return &st::emojiPeople; } else if (const auto section = SetIdEmojiSection(icon.setId)) { - return sectionIcon(*section); + return sectionIcon(*section, false); } return &st::emojiRecent; }()); @@ -1123,28 +1198,30 @@ void StickersListFooter::paintSetIcon( } } -bool StickersListFooter::iconsAnimationCallback(crl::time now) { +bool StickersListFooter::iconsAnimationCallback( + ScrollState &state, + crl::time now) { if (anim::Disabled()) { now += st::stickerIconMove; } - if (_iconState.animationStart) { - const auto dt = (now - _iconState.animationStart) + if (state.animationStart) { + const auto dt = (now - state.animationStart) / float64(st::stickerIconMove); if (dt >= 1.) { - _iconState.animationStart = 0; - _iconState.x.finish(); - _iconState.selectionX.finish(); - _iconState.selectionWidth.finish(); + state.animationStart = 0; + state.x.finish(); + state.selectionX.finish(); + state.selectionWidth.finish(); } else { - _iconState.x.update(dt, anim::linear); - _iconState.selectionX.update(dt, anim::linear); - _iconState.selectionWidth.update(dt, anim::linear); + state.x.update(dt, anim::linear); + state.selectionX.update(dt, anim::linear); + state.selectionWidth.update(dt, anim::linear); } } update(); - return (_iconState.animationStart != 0); + return (state.animationStart != 0); } } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h index 7f5ec4378..28a2b342d 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h @@ -127,7 +127,15 @@ private: Search, Settings, }; - using OverState = std::variant; + struct IconId { + int index = 0; + int subindex = 0; + + friend inline bool operator==(IconId a, IconId b) { + return (a.index == b.index) && (a.subindex == b.subindex); + } + }; + using OverState = std::variant; struct IconInfo { int index = 0; int left = 0; @@ -150,7 +158,7 @@ private: [[nodiscard]] IconInfo subiconInfo(int index) const; [[nodiscard]] std::shared_ptr getLottieRenderer(); - bool iconsAnimationCallback(crl::time now); + bool iconsAnimationCallback(ScrollState &state, crl::time now); void setSelectedIcon( int newSelected, ValidateIconAnimations animations); @@ -162,6 +170,7 @@ private: void validateIconAnimation(const StickerIcon &icon); void refreshIconsGeometry(ValidateIconAnimations animations); + void refreshSubiconsGeometry(); void updateSelected(); void updateSetIcon(uint64 setId); void updateSetIconAt(int left); @@ -198,8 +207,8 @@ private: std::vector _icons; Fn()> _renderer; uint64 _activeByScrollId = 0; - OverState _iconOver = SpecialOver::None; - OverState _iconDown = SpecialOver::None; + OverState _selected = SpecialOver::None; + OverState _pressed = SpecialOver::None; QPoint _iconsMousePos, _iconsMouseDown; mutable QImage _premiumIcon; @@ -217,9 +226,9 @@ private: Ui::Animations::Basic _subiconsAnimation; Ui::RoundRect _selectionBg; - Ui::Animations::Simple _emojiIconWidthAnimation; - int _emojiIconWidth = 0; - bool _emojiIconExpanded = false; + Ui::Animations::Simple _subiconsWidthAnimation; + int _subiconsWidth = 0; + bool _subiconsExpanded = false; bool _barSelection = false; bool _horizontal = false; diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index d4e59c173..f9fee0874 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -410,6 +410,8 @@ TabbedSelector::TabbedSelector( emoji()->showSet(setId); _showRequests.fire({}); }, lifetime()); + + emoji()->refreshEmoji(); } //setAttribute(Qt::WA_AcceptTouchEvents); setAttribute(Qt::WA_OpaquePaintEvent, false);