diff --git a/Telegram/SourceFiles/history/view/reactions/_history_view_reactions_button.cpp b/Telegram/SourceFiles/history/view/reactions/_history_view_reactions_button.cpp deleted file mode 100644 index e4578c7b5..000000000 --- a/Telegram/SourceFiles/history/view/reactions/_history_view_reactions_button.cpp +++ /dev/null @@ -1,1422 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop application for the Telegram messaging service. - -For license and copyright information please follow this link: -https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL -*/ -#include "history/view/history_view_react_button.h" - -#include "history/view/history_view_cursor_state.h" -#include "history/history_item.h" -#include "ui/chat/chat_style.h" -#include "ui/chat/message_bubble.h" -#include "ui/widgets/popup_menu.h" -#include "data/data_message_reactions.h" -#include "data/data_session.h" -#include "data/data_document.h" -#include "data/data_document_media.h" -#include "data/data_peer_values.h" -#include "lang/lang_keys.h" -#include "core/click_handler_types.h" -#include "lottie/lottie_icon.h" -#include "main/main_session.h" -#include "base/event_filter.h" -#include "styles/style_chat.h" -#include "styles/style_menu_icons.h" - -namespace HistoryView::Reactions { -namespace { - -constexpr auto kDivider = 4; -constexpr auto kToggleDuration = crl::time(120); -constexpr auto kActivateDuration = crl::time(150); -constexpr auto kExpandDuration = crl::time(300); -constexpr auto kCollapseDuration = crl::time(250); -constexpr auto kEmojiCacheIndex = 0; -constexpr auto kButtonShowDelay = crl::time(300); -constexpr auto kButtonExpandDelay = crl::time(25); -constexpr auto kButtonHideDelay = crl::time(300); -constexpr auto kButtonExpandedHideDelay = crl::time(0); -constexpr auto kSizeForDownscale = 96; -constexpr auto kHoverScaleDuration = crl::time(200); -constexpr auto kHoverScale = 1.24; -constexpr auto kMaxReactionsScrollAtOnce = 2; - -[[nodiscard]] QPoint LocalPosition(not_null e) { -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - return e->position().toPoint(); -#else // Qt >= 6.0 - return e->pos(); -#endif // Qt >= 6.0 -} - -[[nodiscard]] QSize CountMaxSizeWithMargins(style::margins margins) { - return QRect( - QPoint(), - st::reactionCornerSize - ).marginsAdded(margins).size(); -} - -[[nodiscard]] QSize CountOuterSize() { - return CountMaxSizeWithMargins(st::reactionCornerShadow); -} - -[[nodiscard]] int CornerImageSize(float64 scale) { - return int(base::SafeRound(st::reactionCornerImage * scale)); -} - -[[nodiscard]] int MainReactionSize() { - return style::ConvertScale(kSizeForDownscale); -} - -[[nodiscard]] std::shared_ptr CreateIcon( - not_null media, - int size, - int frame) { - Expects(media->loaded()); - - return std::make_shared(Lottie::IconDescriptor{ - .path = media->owner()->filepath(true), - .json = media->bytes(), - .sizeOverride = QSize(size, size), - .frame = frame, - }); -} - -} // namespace - -Button::Button( - Fn update, - ButtonParameters parameters, - Fn toggleExpanded, - Fn hide) -: _update(std::move(update)) -, _toggleExpanded(std::move(toggleExpanded)) -, _finalScale(ScaleForState(_state)) -, _collapsed(QPoint(), CountOuterSize()) -, _finalHeight(_collapsed.height()) -, _expandTimer([=] { _toggleExpanded(true); }) -, _hideTimer(hide) { - applyParameters(parameters, nullptr); -} - -Button::~Button() = default; - -void Button::expandWithoutCustom() { - applyState(State::Inside, _update); -} - -bool Button::isHidden() const { - return (_state == State::Hidden) && !_opacityAnimation.animating(); -} - -QRect Button::geometry() const { - return _geometry; -} - -int Button::expandedHeight() const { - return _expandedHeight; -} - -int Button::scroll() const { - return _scroll; -} - -int Button::scrollMax() const { - return _expandedInnerHeight - _expandedHeight; -} - -float64 Button::expandAnimationOpacity(float64 expandRatio) const { - return (_collapseType == CollapseType::Fade) - ? expandRatio - : 1.; -} - -int Button::expandAnimationScroll(float64 expandRatio) const { - return (_collapseType == CollapseType::Scroll && expandRatio < 1.) - ? std::clamp(int(base::SafeRound(expandRatio * _scroll)), 0, _scroll) - : _scroll; -} - -bool Button::expandUp() const { - return (_expandDirection == ExpandDirection::Up); -} - -bool Button::consumeWheelEvent(not_null e) { - const auto scrollMax = (_expandedInnerHeight - _expandedHeight); - if (_state != State::Inside - || scrollMax <= 0 - || !_geometry.contains(LocalPosition(e))) { - return false; - } - const auto delta = e->angleDelta(); - const auto horizontal = std::abs(delta.x()) > std::abs(delta.y()); - if (horizontal) { - return false; - } - const auto between = st::reactionCornerSkip; - const auto oneHeight = (st::reactionCornerSize.height() + between); - const auto max = oneHeight * kMaxReactionsScrollAtOnce; - const auto shift = std::clamp( - delta.y() * (expandUp() ? 1 : -1), - -max, - max); - _scroll = std::clamp(_scroll + shift, 0, scrollMax); - _update(_geometry); - e->accept(); - return true; -} - -void Button::applyParameters(ButtonParameters parameters) { - applyParameters(std::move(parameters), _update); -} - -void Button::applyParameters( - ButtonParameters parameters, - Fn update) { - const auto shift = parameters.center - _collapsed.center(); - _collapsed = _collapsed.translated(shift); - updateGeometry(update); - const auto inner = _geometry.marginsRemoved(st::reactionCornerShadow); - const auto active = inner.marginsAdded( - st::reactionCornerActiveAreaPadding - ).contains(parameters.pointer); - const auto inside = inner.contains(parameters.pointer) - || (active && (_state == State::Inside)); - if (_state != State::Inside && !_heightAnimation.animating()) { - updateExpandDirection(parameters); - } - const auto delayInside = inside && (_state != State::Inside); - if (!delayInside) { - _expandTimer.cancel(); - _lastGlobalPosition = std::nullopt; - } else { - const auto globalPositionChanged = _lastGlobalPosition - && (*_lastGlobalPosition != parameters.globalPointer); - if (globalPositionChanged || _state == State::Hidden) { - _expandTimer.callOnce(kButtonExpandDelay); - } - _lastGlobalPosition = parameters.globalPointer; - } - const auto wasInside = (_state == State::Inside); - const auto state = (inside && !delayInside) - ? State::Inside - : active - ? State::Active - : State::Shown; - applyState(state, update); - if (parameters.outside && _state == State::Shown) { - _hideTimer.callOnce(wasInside - ? kButtonExpandedHideDelay - : kButtonHideDelay); - } else { - _hideTimer.cancel(); - } -} - -void Button::updateExpandDirection(const ButtonParameters ¶meters) { - const auto maxAddedHeight = (parameters.reactionsCount - 1) - * (st::reactionCornerSize.height() + st::reactionCornerSkip) - + (parameters.reactionsCount > 1 ? 2 * st::reactionExpandedSkip : 0); - _expandedInnerHeight = _collapsed.height() + maxAddedHeight; - const auto addedHeight = std::min( - maxAddedHeight, - st::reactionCornerAddedHeightMax); - _expandedHeight = _collapsed.height() + addedHeight; - _scroll = std::clamp(_scroll, 0, scrollMax()); - if (parameters.reactionsCount < 2) { - return; - } - const auto up = (_collapsed.y() - addedHeight >= parameters.visibleTop) - || (_collapsed.y() + _collapsed.height() + addedHeight - > parameters.visibleBottom); - _expandDirection = up ? ExpandDirection::Up : ExpandDirection::Down; -} - -void Button::updateGeometry(Fn update) { - const auto added = int(base::SafeRound( - _heightAnimation.value(_finalHeight) - )) - _collapsed.height(); - if (!added && _state != State::Inside) { - _scroll = 0; - } - const auto geometry = _collapsed.marginsAdded({ - 0, - (_expandDirection == ExpandDirection::Up) ? added : 0, - 0, - (_expandDirection == ExpandDirection::Down) ? added : 0, - }); - if (_geometry != geometry) { - if (update) { - update(_geometry); - } - _geometry = geometry; - if (update) { - update(_geometry); - } - } -} - -void Button::applyState(State state) { - applyState(state, _update); -} - -void Button::applyState(State state, Fn update) { - if (state == State::Hidden) { - _expandTimer.cancel(); - _hideTimer.cancel(); - } - const auto finalHeight = (state == State::Hidden) - ? _heightAnimation.value(_finalHeight) - : (state == State::Inside) - ? _expandedHeight - : _collapsed.height(); - if (_finalHeight != finalHeight) { - if (state == State::Hidden) { - _heightAnimation.stop(); - } else { - if (!_heightAnimation.animating()) { - _collapseType = (_scroll < st::reactionCollapseFadeThreshold) - ? CollapseType::Scroll - : CollapseType::Fade; - } - _heightAnimation.start( - [=] { updateGeometry(_update); }, - _finalHeight, - finalHeight, - (state == State::Inside - ? kExpandDuration - : kCollapseDuration), - anim::easeOutCirc); - } - _finalHeight = finalHeight; - } - updateGeometry(update); - if (_state == state) { - return; - } - const auto duration = (state == State::Hidden || _state == State::Hidden) - ? kToggleDuration - : kActivateDuration; - const auto finalScale = ScaleForState(state); - _opacityAnimation.start( - [=] { _update(_geometry); }, - OpacityForScale(ScaleForState(_state)), - OpacityForScale(ScaleForState(state)), - duration, - anim::sineInOut); - if (state != State::Hidden && _finalScale != finalScale) { - _scaleAnimation.start( - [=] { _update(_geometry); }, - _finalScale, - finalScale, - duration, - anim::sineInOut); - _finalScale = finalScale; - } - _state = state; - _toggleExpanded(false); -} - -float64 Button::ScaleForState(State state) { - switch (state) { - case State::Hidden: return 1. / 3; - case State::Shown: return 2. / 3; - case State::Active: - case State::Inside: return 1.; - } - Unexpected("State in ReactionButton::ScaleForState."); -} - -float64 Button::OpacityForScale(float64 scale) { - return std::min( - ((scale - ScaleForState(State::Hidden)) - / (ScaleForState(State::Shown) - ScaleForState(State::Hidden))), - 1.); -} - -float64 Button::currentScale() const { - return _scaleAnimation.value(_finalScale); -} - -float64 Button::currentOpacity() const { - return _opacityAnimation.value(OpacityForScale(ScaleForState(_state))); -} - -Manager::Manager( - QWidget *wheelEventsTarget, - rpl::producer uniqueLimitValue, - Fn buttonUpdate, - IconFactory iconFactory) -: _iconFactory(std::move(iconFactory)) -, _outer(CountOuterSize()) -, _inner(QRect({}, st::reactionCornerSize)) -, _cachedRound( - st::reactionCornerSize, - st::reactionCornerShadow, - _inner.width()) -, _uniqueLimit(std::move(uniqueLimitValue)) -, _buttonShowTimer([=] { showButtonDelayed(); }) -, _buttonUpdate(std::move(buttonUpdate)) { - _inner.translate(QRect({}, _outer).center() - _inner.center()); - - _emojiParts = _cachedRound.PrepareFramesCache(_outer); - _expandedBuffer = _cachedRound.PrepareImage(QSize( - _outer.width(), - _outer.height() + st::reactionCornerAddedHeightMax)); - if (wheelEventsTarget) { - stealWheelEvents(wheelEventsTarget); - } - - _uniqueLimit.changes( - ) | rpl::start_with_next([=] { - applyListFilters(); - }, _lifetime); - - _createChooseCallback = [=](ReactionId id) { - return [=] { - if (auto chosen = lookupChosen(id)) { - _chosen.fire(std::move(chosen)); - } - }; - }; -} - -ChosenReaction Manager::lookupChosen(const ReactionId &id) const { - auto result = ChosenReaction{ - .context = _buttonContext, - .id = id, - }; - const auto button = _button.get(); - const auto i = ranges::find(_icons, id, &ReactionIcons::id); - if (i == end(_icons) || !button) { - return result; - } - const auto &icon = *i; - if (const auto &appear = icon->appear; appear && appear->animating()) { - result.icon = CreateIcon( - icon->appearAnimation->activeMediaView().get(), - appear->width(), - appear->frameIndex()); - } else if (const auto &select = icon->select) { - result.icon = CreateIcon( - icon->selectAnimation->activeMediaView().get(), - select->width(), - select->frameIndex()); - } - const auto index = (i - begin(_icons)); - const auto between = st::reactionCornerSkip; - const auto oneHeight = (st::reactionCornerSize.height() + between); - const auto expanded = (_icons.size() > 1); - const auto skip = (expanded ? st::reactionExpandedSkip : 0); - const auto scroll = button->scroll(); - const auto local = skip + index * oneHeight - scroll; - const auto geometry = button->geometry(); - const auto top = button->expandUp() - ? (geometry.height() - local - _outer.height()) - : local; - const auto rect = QRect(geometry.topLeft() + QPoint(0, top), _outer); - const auto imageSize = int(base::SafeRound( - st::reactionCornerImage * kHoverScale)); - result.geometry = QRect( - rect.x() + (rect.width() - imageSize) / 2, - rect.y() + (rect.height() - imageSize) / 2, - imageSize, - imageSize); - return result; -} - -bool Manager::applyUniqueLimit() const { - const auto limit = _uniqueLimit.current(); - return _buttonContext - && (limit > 0) - && (_buttonAlreadyNotMineCount >= limit); -} - -void Manager::applyListFilters() { - const auto limited = applyUniqueLimit(); - auto icons = std::vector>(); - icons.reserve(_list.size()); - auto showPremiumLock = (ReactionIcons*)nullptr; - auto favoriteIndex = -1; - for (auto &icon : _list) { - const auto &id = icon.id; - const auto add = limited - ? _buttonAlreadyList.contains(id) - : id.emoji().isEmpty() - ? _filter.customAllowed - : (!_filter.allowed || _filter.allowed->contains(id.emoji())); - if (add) { - if (icon.premium - && !_allowSendingPremium - && !_buttonAlreadyList.contains(id)) { - if (_premiumPossible) { - showPremiumLock = &icon; - } else { - clearStateForHidden(icon); - } - } else { - icon.premiumLock = false; - if (id == _favorite) { - favoriteIndex = int(icons.size()); - } - icons.push_back(&icon); - } - } else { - clearStateForHidden(icon); - } - } - if (showPremiumLock) { - showPremiumLock->premiumLock = true; - icons.push_back(showPremiumLock); - } - if (favoriteIndex > 0) { - const auto first = begin(icons); - std::rotate(first, first + favoriteIndex, first + favoriteIndex + 1); - } - if (!limited && _filter.customAllowed && icons.size() > 1) { - icons.erase(begin(icons) + 1, end(icons)); - } - if (_icons == icons) { - return; - } - const auto selected = _selectedIcon; - setSelectedIcon(-1); - _icons = std::move(icons); - setSelectedIcon((selected < _icons.size()) ? selected : -1); - resolveMainReactionIcon(); -} - -void Manager::stealWheelEvents(not_null target) { - base::install_event_filter(target, [=](not_null e) { - if (e->type() != QEvent::Wheel - || !consumeWheelEvent(static_cast(e.get()))) { - return base::EventFilterResult::Continue; - } - Ui::SendSynteticMouseEvent(target, QEvent::MouseMove, Qt::NoButton); - return base::EventFilterResult::Cancel; - }); -} - -Manager::~Manager() = default; - -void Manager::updateButton(ButtonParameters parameters) { - if (parameters.cursorLeft) { - if (_menu) { - return; - } else if (_externalSelectorShown) { - setSelectedIcon(-1); - return; - } - } - const auto contextChanged = (_buttonContext != parameters.context); - if (contextChanged) { - setSelectedIcon(-1); - if (_button) { - _button->applyState(ButtonState::Hidden); - _buttonHiding.push_back(std::move(_button)); - } - _buttonShowTimer.cancel(); - _scheduledParameters = std::nullopt; - } - _buttonContext = parameters.context; - parameters.reactionsCount = _icons.size(); - if (!_buttonContext || !parameters.reactionsCount) { - return; - } else if (_button) { - _button->applyParameters(parameters); - if (_button->geometry().height() == _outer.height()) { - clearAppearAnimations(); - } - return; - } else if (parameters.outside) { - _buttonShowTimer.cancel(); - _scheduledParameters = std::nullopt; - return; - } - const auto globalPositionChanged = _scheduledParameters - && (_scheduledParameters->globalPointer != parameters.globalPointer); - const auto positionChanged = _scheduledParameters - && (_scheduledParameters->pointer != parameters.pointer); - _scheduledParameters = parameters; - if ((_buttonShowTimer.isActive() && positionChanged) - || globalPositionChanged) { - _buttonShowTimer.callOnce(kButtonShowDelay); - } -} - -void Manager::toggleExpanded(bool expanded) { - if (!_button || !_buttonContext) { - } else if (!expanded || (_filter.customAllowed && !applyUniqueLimit())) { - _expandSelectorRequests.fire({ - .context = _buttonContext, - .button = _button->geometry().marginsRemoved( - st::reactionCornerShadow), - .expanded = expanded, - }); - } else { - _button->expandWithoutCustom(); - } -} - -void Manager::setExternalSelectorShown(rpl::producer shown) { - std::move(shown) | rpl::start_with_next([=](bool shown) { - _externalSelectorShown = shown; - }, _lifetime); -} - -void Manager::showButtonDelayed() { - clearAppearAnimations(); - _button = std::make_unique