diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 06c7f6f8b3..0bcb434fdd 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -957,6 +957,8 @@ PRIVATE history/view/history_view_reaction_preview.h history/view/history_view_reply.cpp history/view/history_view_reply.h + history/view/history_view_reply_button.cpp + history/view/history_view_reply_button.h history/view/history_view_requests_bar.cpp history/view/history_view_requests_bar.h history/view/history_view_schedule_box.cpp diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index 33cf8dcd2f..88ab641fa0 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_common.h" #include "data/data_peer_values.h" #include "data/data_channel.h" +#include "data/data_changes.h" #include "data/data_chat.h" #include "data/data_user.h" #include "base/unixtime.h" @@ -872,10 +873,17 @@ void EditRestrictedBox::prepare() { restrictWeak->closeBox(); } }; + const auto savedUser = user(); + const auto savedPeer = peer(); const auto done = [=]( - ChatAdminRightsInfo, - const QString &) { + ChatAdminRightsInfo newRights, + const QString &rank) { closeBoth(); + savedUser->session().changes().chatAdminChanged( + savedPeer, + savedUser, + newRights.flags, + rank); }; const auto fail = closeBoth; adminBox->setSaveCallback( diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index a72a3f4540..3a5a276690 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -2013,6 +2013,11 @@ void ParticipantsBoxController::editAdminDone( if (_editParticipantBox) { _editParticipantBox->closeBox(); } + + _additional.applyAdminLocally(user, rights, rank); + recomputeTypeFor(user); + refreshRows(); + const auto flags = rights.flags; user->session().changes().chatAdminChanged(_peer, user, flags, rank); } diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp b/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp index 192937b6e4..32a2eff1f8 100644 --- a/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp +++ b/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp @@ -22,12 +22,12 @@ ResolveWindow ResolveWindowDefault() { return [](not_null session) -> Window::SessionController* { const auto check = [&](Window::Controller *window) { - if (const auto controller = window->sessionController()) { - if (&controller->session() == session) { - return controller; - } - } - return (Window::SessionController*)nullptr; + const auto controller = window + ? window->sessionController() + : nullptr; + return (controller && (&controller->session() == session)) + ? controller + : nullptr; }; auto &app = Core::App(); const auto account = not_null(&session->account()); diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 5169bb7bce..557d873d83 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_message.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_cursor_state.h" +#include "history/view/history_view_reply_button.h" #include "history/view/history_view_context_menu.h" #include "history/view/history_view_reaction_preview.h" #include "history/view/history_view_quick_action.h" @@ -331,6 +332,9 @@ HistoryInner::HistoryInner( std::make_unique( this, [=](QRect updated) { update(updated); })) +, _replyButtonManager( + std::make_unique( + [=](QRect updated) { update(updated); })) , _touchSelectTimer([=] { onTouchSelect(); }) , _touchScrollTimer([=] { onTouchScrollTimer(); }) , _middleClickAutoscroll( @@ -1475,6 +1479,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { return true; }); + _replyButtonManager->paint(p, context); _reactionsManager->paint(p, context); } @@ -2086,6 +2091,7 @@ void HistoryInner::performDrag() { if (auto mimeData = prepareDrag()) { // This call enters event loop and can destroy any QObject. _reactionsManager->updateButton({}); + _replyButtonManager->updateButton({}); _controller->widget()->launchDrag( std::move(mimeData), crl::guard(this, [=] { mouseActionUpdate(QCursor::pos()); })); @@ -2104,6 +2110,7 @@ void HistoryInner::itemRemoved(not_null item) { } _animatedStickersPlayed.remove(item); _reactionsManager->remove(item->fullId()); + _replyButtonManager->remove(item->fullId()); auto i = _selected.find(item); if (i != _selected.cend()) { @@ -3937,6 +3944,7 @@ void HistoryInner::enterEventHook(QEnterEvent *e) { void HistoryInner::leaveEventHook(QEvent *e) { _reactionsManager->updateButton({ .cursorLeft = true }); + _replyButtonManager->updateButton({}); if (auto item = Element::Hovered()) { repaintItem(item); Element::Hovered(nullptr); @@ -4293,7 +4301,28 @@ auto HistoryInner::reactionButtonParameters( auto result = view->reactionButtonParameters( position, reactionState - ).translated({ 0, itemTop(view) }); + ).translated({ 0, top }); + result.visibleTop = _visibleAreaTop; + result.visibleBottom = _visibleAreaBottom; + result.globalPointer = _mousePosition; + return result; +} + +auto HistoryInner::replyButtonParameters( + not_null view, + QPoint position, + const HistoryView::TextState &replyState) const +-> HistoryView::ReplyButton::ButtonParameters { + const auto top = itemTop(view); + if (top < 0 + || _mouseAction == MouseAction::Dragging + || inSelectionMode().inSelectionMode) { + return {}; + } + auto result = view->replyButtonParameters( + position, + replyState + ).translated({ 0, top }); result.visibleTop = _visibleAreaTop; result.visibleBottom = _visibleAreaBottom; result.globalPointer = _mousePosition; @@ -4315,8 +4344,15 @@ void HistoryInner::mouseActionUpdate() { const auto reactionState = _reactionsManager->buttonTextState(point); const auto reactionItem = session().data().message(reactionState.itemId); const auto reactionView = viewByItem(reactionItem); + const auto replyBtnState = reactionView + ? HistoryView::TextState() + : _replyButtonManager->buttonTextState(point); + const auto replyBtnItem = session().data().message(replyBtnState.itemId); + const auto replyBtnView = viewByItem(replyBtnItem); const auto view = reactionView ? reactionView + : replyBtnView + ? replyBtnView : (_aboutView && _aboutView->view() && point.y() >= _aboutView->top @@ -4342,6 +4378,10 @@ void HistoryInner::mouseActionUpdate() { view, m, reactionState)); + _replyButtonManager->updateButton(replyButtonParameters( + view, + m, + replyBtnState)); if (changed) { _reactionsItem = item; } @@ -4361,6 +4401,7 @@ void HistoryInner::mouseActionUpdate() { Element::Moused(nullptr); } _reactionsManager->updateButton({}); + _replyButtonManager->updateButton({}); } if (_mouseActionItem && !viewByItem(_mouseActionItem)) { mouseActionCancel(); @@ -4374,9 +4415,13 @@ void HistoryInner::mouseActionUpdate() { && !_selected.empty() && (_selected.cbegin()->second != FullSelection); const auto overReaction = reactionView && reactionState.link; + const auto overReplyBtn = replyBtnView && replyBtnState.link; if (overReaction) { dragState = reactionState; lnkhost = reactionView; + } else if (overReplyBtn) { + dragState = replyBtnState; + lnkhost = replyBtnView; } else if (item) { if (item != _mouseActionItem || ((m + selectionViewOffset) - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) { if (_mouseAction == MouseAction::PrepareDrag) { diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 4141fb5b67..062fc800c5 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -51,6 +51,11 @@ struct ChosenReaction; struct ButtonParameters; } // namespace HistoryView::Reactions +namespace HistoryView::ReplyButton { +class Manager; +struct ButtonParameters; +} // namespace HistoryView::ReplyButton + namespace Window { class SessionController; } // namespace Window @@ -439,6 +444,11 @@ private: QPoint position, const HistoryView::TextState &reactionState) const -> HistoryView::Reactions::ButtonParameters; + [[nodiscard]] auto replyButtonParameters( + not_null view, + QPoint position, + const HistoryView::TextState &replyState) const + -> HistoryView::ReplyButton::ButtonParameters; void toggleFavoriteReaction(not_null view) const; void reactionChosen(const ChosenReaction &reaction); @@ -512,6 +522,7 @@ private: std::unique_ptr _reactionsManager; rpl::variable _reactionsItem; + std::unique_ptr _replyButtonManager; HistoryItem *_pinnedItem = nullptr; MouseAction _mouseAction = MouseAction::None; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index d9502f9bfa..c195695689 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_no_forwards_request.h" #include "history/view/media/history_view_suggest_decision.h" #include "history/view/reactions/history_view_reactions_button.h" +#include "history/view/history_view_reply_button.h" #include "history/view/reactions/history_view_reactions.h" #include "history/view/history_view_cursor_state.h" #include "history/view/history_view_reply.h" @@ -2800,6 +2801,12 @@ Reactions::ButtonParameters Element::reactionButtonParameters( return {}; } +ReplyButton::ButtonParameters Element::replyButtonParameters( + QPoint position, + const TextState &replyState) const { + return {}; +} + int Element::reactionsOptimalWidth() const { return 0; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 596db502be..3da8a11ba5 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -46,6 +46,10 @@ struct ButtonParameters; class InlineList; } // namespace HistoryView::Reactions +namespace HistoryView::ReplyButton { +struct ButtonParameters; +} // namespace HistoryView::ReplyButton + namespace HistoryView { using PaintContext = Ui::ChatPaintContext; @@ -542,6 +546,9 @@ public: [[nodiscard]] virtual auto reactionButtonParameters( QPoint position, const TextState &reactionState) const -> Reactions::ButtonParameters; + [[nodiscard]] virtual auto replyButtonParameters( + QPoint position, + const TextState &replyState) const -> ReplyButton::ButtonParameters; [[nodiscard]] virtual int reactionsOptimalWidth() const; // ClickHandlerHost interface. diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 701a8c32a0..714399ddc6 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_sticker.h" #include "history/view/reactions/history_view_reactions.h" #include "history/view/reactions/history_view_reactions_button.h" +#include "history/view/history_view_reply_button.h" #include "history/view/reactions/history_view_reactions_selector.h" #include "history/view/history_view_context_menu.h" #include "history/view/history_view_element.h" @@ -405,6 +406,8 @@ ListWidget::ListWidget( , _reactionsManager(_delegate->listMakeReactionsManager( this, [=](QRect updated) { update(updated); })) +, _replyButtonManager(std::make_unique( + [=](QRect updated) { update(updated); })) , _translateTracker(MaybeTranslateTracker(_delegate->listTranslateHistory())) , _scrollDateCheck([this] { scrollDateCheck(); }) , _applyUpdatedScrollState([this] { applyUpdatedScrollState(); }) @@ -2384,6 +2387,9 @@ void ListWidget::paintEvent(QPaintEvent *e) { paintUserpics(p, context, clip); paintDates(p, context, clip); + if (_replyButtonManager) { + _replyButtonManager->paint(p, context); + } if (_reactionsManager) { _reactionsManager->paint(p, context); } @@ -3298,6 +3304,9 @@ void ListWidget::leaveEventHook(QEvent *e) { if (_reactionsManager) { _reactionsManager->updateButton({ .cursorLeft = true }); } + if (_replyButtonManager) { + _replyButtonManager->updateButton({}); + } if (const auto view = _overElement) { if (_overState.pointState != PointState::Outside) { repaintItem(view); @@ -3604,7 +3613,27 @@ Reactions::ButtonParameters ListWidget::reactionButtonParameters( auto result = view->reactionButtonParameters( position, reactionState - ).translated({ 0, itemTop(view) }); + ).translated({ 0, top }); + result.visibleTop = _visibleTop; + result.visibleBottom = _visibleBottom; + result.globalPointer = _mousePosition; + return result; +} + +ReplyButton::ButtonParameters ListWidget::replyButtonParameters( + not_null view, + QPoint position, + const TextState &replyState) const { + const auto top = itemTop(view); + if (top < 0 + || _mouseAction == MouseAction::Dragging + || inSelectionMode().inSelectionMode) { + return {}; + } + auto result = view->replyButtonParameters( + position, + replyState + ).translated({ 0, top }); result.visibleTop = _visibleTop; result.visibleBottom = _visibleBottom; result.globalPointer = _mousePosition; @@ -3757,8 +3786,15 @@ void ListWidget::mouseActionUpdate() { : TextState(); const auto reactionItem = session().data().message(reactionState.itemId); const auto reactionView = viewForItem(reactionItem); + const auto replyBtnState = (_replyButtonManager && !reactionView) + ? _replyButtonManager->buttonTextState(point) + : TextState(); + const auto replyBtnItem = session().data().message(replyBtnState.itemId); + const auto replyBtnView = viewForItem(replyBtnItem); const auto view = reactionView ? reactionView + : replyBtnView + ? replyBtnView : strictFindItemByY(point.y()); const auto item = view ? view->data().get() : nullptr; if (view) { @@ -3785,6 +3821,11 @@ void ListWidget::mouseActionUpdate() { reactionState) : Reactions::ButtonParameters()); } + if (_replyButtonManager) { + _replyButtonManager->updateButton(view + ? replyButtonParameters(view, itemPoint, replyBtnState) + : ReplyButton::ButtonParameters()); + } if (viewChanged && view) { _reactionsItem = item; } @@ -3795,10 +3836,14 @@ void ListWidget::mouseActionUpdate() { && (_overState.itemId == _pressState.itemId) && hasSelectedText(); auto dragStateUserpic = false; + const auto overReplyBtn = replyBtnView && replyBtnState.link; const auto overReaction = reactionView && reactionState.link; if (overReaction) { dragState = reactionState; lnkhost = reactionView; + } else if (overReplyBtn) { + dragState = replyBtnState; + lnkhost = replyBtnView; } else if (view) { auto cursorDeltaLength = [&] { auto cursorDelta = (_overState.point - _pressState.point); @@ -4301,6 +4346,9 @@ void ListWidget::itemRemoved(not_null item) { if (_reactionsManager) { _reactionsManager->remove(item->fullId()); } + if (_replyButtonManager) { + _replyButtonManager->remove(item->fullId()); + } updateItemsGeometry(); } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index cbb6a260ba..20099c7ecc 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -53,6 +53,11 @@ struct ChosenReaction; struct ButtonParameters; } // namespace HistoryView::Reactions +namespace HistoryView::ReplyButton { +class Manager; +struct ButtonParameters; +} // namespace HistoryView::ReplyButton + namespace Window { struct SectionShow; } // namespace Window @@ -388,6 +393,10 @@ public: not_null view, QPoint position, const TextState &reactionState) const; + [[nodiscard]] ReplyButton::ButtonParameters replyButtonParameters( + not_null view, + QPoint position, + const TextState &replyState) const; void toggleFavoriteReaction(not_null view) const; @@ -781,6 +790,8 @@ private: rpl::variable _reactionsItem; bool _useCornerReaction = false; + std::unique_ptr _replyButtonManager; + std::unique_ptr _translateTracker; int _minHeight = 0; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 1be6fddd74..6ffb0ec71b 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_suggest_decision.h" #include "history/view/reactions/history_view_reactions.h" #include "history/view/reactions/history_view_reactions_button.h" +#include "history/view/history_view_reply_button.h" #include "history/view/history_view_group_call_bar.h" // UserpicInRow. #include "history/view/history_view_reply.h" #include "history/view/history_view_transcribe_button.h" @@ -63,19 +64,6 @@ constexpr auto kSummarizeThreshold = 512; constexpr auto kPlayStatusLimit = 2; const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_"; -QString FastForwardText() { - return u"Forward"_q; -} - -QString FastReplyText() { - return tr::lng_fast_reply(tr::now); -} - -bool ShowFastForwardFor(const QString &username) { - return !username.compare(u"ReviewInsightsBot"_q, Qt::CaseInsensitive) - || !username.compare(u"reviews_bot"_q, Qt::CaseInsensitive); -} - [[nodiscard]] ClickHandlerPtr MakeTopicButtonLink( not_null topic, MsgId messageId) { @@ -754,16 +742,8 @@ QSize Message::performCountOptimalSize() { namew += st::msgServiceFont->spacew + via->maxWidth + (_fromNameStatus ? st::msgServiceFont->spacew : 0); } - const auto replyWidth = hasFastForward() - ? st::msgFont->width(FastForwardText()) - : hasFastReply() - ? st::msgFont->width(FastReplyText()) - : 0; if (Has()) { - namew += st::msgPadding.right() - + std::max(rightBadgeWidth(), replyWidth); - } else if (replyWidth) { - namew += st::msgPadding.right() + replyWidth; + namew += st::msgPadding.right() + rightBadgeWidth(); } accumulate_max(maxWidth, namew); } else if (via && !displayForwardedFrom()) { @@ -1649,21 +1629,10 @@ void Message::paintFromName( return; } const auto badgeWidth = rightBadgeWidth(); - const auto replyWidth = [&] { - if (isUnderCursor()) { - if (displayFastForward()) { - return st::msgFont->width(FastForwardText()); - } else if (displayFastReply()) { - return st::msgFont->width(FastReplyText()); - } - } - return 0; - }(); - const auto rightWidth = replyWidth ? replyWidth : badgeWidth; auto availableLeft = trect.left(); auto availableWidth = trect.width(); - if (rightWidth) { - availableWidth -= st::msgPadding.right() + rightWidth; + if (badgeWidth) { + availableWidth -= st::msgPadding.right() + badgeWidth; } const auto stm = context.messageStyle(); @@ -1746,17 +1715,9 @@ void Message::paintFromName( availableLeft += skipWidth; availableWidth -= skipWidth; } - if (rightWidth) { + if (badgeWidth) { p.setPen(stm->msgDateFg); - if (replyWidth) { - p.setFont(ClickHandler::showAsActive(_fastReplyLink) - ? st::msgFont->underline() - : st::msgFont); - p.drawText( - trect.left() + trect.width() - rightWidth, - trect.top() + st::msgFont->ascent, - hasFastForward() ? FastForwardText() : FastReplyText()); - } else if (const auto badge = Get()) { + if (const auto badge = Get()) { const auto badgeColor = (badge->role == BadgeRole::Creator) ? st::rankOwnerFg->c : (badge->role == BadgeRole::Admin) @@ -2779,29 +2740,12 @@ bool Message::getStateFromName( if (!displayFromName()) { return false; } - const auto replyWidth = [&] { - if (isUnderCursor()) { - if (displayFastForward()) { - return st::msgFont->width(FastForwardText()); - } else if (displayFastReply()) { - return st::msgFont->width(FastReplyText()); - } - } - return 0; - }(); - if (replyWidth - && point.x() >= trect.left() + trect.width() - replyWidth - && point.x() < trect.left() + trect.width() + st::msgPadding.right() - && point.y() >= trect.top() - st::msgPadding.top() - && point.y() < trect.top() + st::msgServiceFont->height) { - outResult->link = fastReplyLink(); - return true; - } if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) { auto availableLeft = trect.left(); auto availableWidth = trect.width(); - if (replyWidth) { - availableWidth -= st::msgPadding.right() + replyWidth; + const auto badgeWidth = rightBadgeWidth(); + if (badgeWidth) { + availableWidth -= st::msgPadding.right() + badgeWidth; } const auto item = data(); const auto from = item->displayFrom(); @@ -2849,6 +2793,50 @@ bool Message::getStateFromName( outResult->link = via->link; return true; } + if (badgeWidth) { + const auto badge = Get(); + const auto badgeLeft = trect.left() + + trect.width() + - badgeWidth; + const auto badgeRight = trect.left() + + trect.width() + + st::msgPadding.right(); + const auto boostTextWidth = (badge && !badge->boosts.isEmpty()) + ? badge->boosts.maxWidth() + : 0; + const auto boostLeft = boostTextWidth + ? (trect.left() + trect.width() - boostTextWidth) + : 0; + if (boostTextWidth + && point.x() >= boostLeft + && point.x() < badgeRight) { + if (!badge->boostsLink) { + badge->boostsLink = std::make_shared([=]( + ClickContext context) { + if (const auto controller = ExtractController(context)) { + controller->showToast(u"Boosts clicked"_q); + } + }); + } + outResult->link = badge->boostsLink; + return true; + } + const auto tagRight = boostTextWidth + ? (boostLeft - st::msgTagBadgeBoostSkip) + : badgeRight; + if (point.x() >= badgeLeft && point.x() < tagRight) { + if (!badge->tagLink) { + badge->tagLink = std::make_shared([=]( + ClickContext context) { + if (const auto controller = ExtractController(context)) { + controller->showToast(u"Tag clicked"_q); + } + }); + } + outResult->link = badge->tagLink; + return true; + } + } } trect.setTop(trect.top() + st::msgNameFont->height); return false; @@ -3400,6 +3388,31 @@ Reactions::ButtonParameters Message::reactionButtonParameters( return result; } +ReplyButton::ButtonParameters Message::replyButtonParameters( + QPoint position, + const TextState &replyState) const { + using namespace ReplyButton; + if (!displayFastReply() || unwrapped()) { + return {}; + } + auto result = ButtonParameters{ .context = data()->fullId() }; + const auto geometry = countGeometry(); + result.pointer = position; + const auto reactionInnerRight = st::reactionCornerCenter.x() + + st::reactionCornerSize.width() / 2; + const auto replyInnerWidth = ReplyButton::ComputeInnerWidth(); + const auto relativeCenter = QPoint( + geometry.width() + reactionInnerRight - replyInnerWidth, + st::replyCornerCenter.y()); + result.center = geometry.topLeft() + relativeCenter; + if (replyState.itemId != result.context + && !geometry.contains(position)) { + result.outside = true; + } + result.link = fastReplyLink(); + return result; +} + int Message::reactionsOptimalWidth() const { return _reactions ? _reactions->countNiceWidth() : 0; } @@ -3856,22 +3869,6 @@ bool Message::hasFastReply() const { return !hasOutLayout() && (peer->isChat() || peer->isMegagroup()); } -bool Message::hasFastForward() const { - if (context() != Context::History) { - return false; - } - const auto item = data(); - const auto from = item->from()->asUser(); - if (!from || !from->isBot() || !ShowFastForwardFor(from->username())) { - return false; - } - const auto peer = item->history()->peer; - if (!peer->isChat() && !peer->isMegagroup()) { - return false; - } - return !hasOutLayout(); -} - bool Message::displayFastReply() const { const auto canSendAnything = [&] { const auto item = data(); @@ -3888,12 +3885,6 @@ bool Message::displayFastReply() const { && !delegate()->elementInSelectionMode(this).inSelectionMode; } -bool Message::displayFastForward() const { - return hasFastForward() - && data()->allowsForward() - && !delegate()->elementInSelectionMode(this).inSelectionMode; -} - bool Message::displayRightActionComments() const { return !isPinnedContext() && (context() != Context::SavedSublist) @@ -4178,22 +4169,9 @@ ClickHandlerPtr Message::fastReplyLink() const { return _fastReplyLink; } const auto itemId = data()->fullId(); - const auto sessionId = data()->history()->session().uniqueId(); - _fastReplyLink = hasFastForward() - ? std::make_shared([=](ClickContext context) { - const auto controller = ExtractController(context); - const auto session = controller - ? &controller->session() - : nullptr; - if (!session || session->uniqueId() != sessionId) { - return; - } else if (const auto item = session->data().message(itemId)) { - FastShareMessage(controller, item); - } - }) - : std::make_shared(crl::guard(this, [=] { - delegate()->elementReplyTo({ itemId }); - })); + _fastReplyLink = std::make_shared(crl::guard(this, [=] { + delegate()->elementReplyTo({ itemId }); + })); return _fastReplyLink; } @@ -4282,16 +4260,8 @@ void Message::updateMediaInBubbleState() { void Message::fromNameUpdated(int width) const { const auto item = data(); - const auto replyWidth = hasFastForward() - ? st::msgFont->width(FastForwardText()) - : hasFastReply() - ? st::msgFont->width(FastReplyText()) - : 0; if (Has()) { - width -= st::msgPadding.right() - + std::max(rightBadgeWidth(), replyWidth); - } else if (replyWidth) { - width -= st::msgPadding.right() + replyWidth; + width -= st::msgPadding.right() + rightBadgeWidth(); } const auto from = item->displayFrom(); validateFromNameText(from); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 5afb46fdcd..d7768b2086 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -37,6 +37,10 @@ namespace Reactions { class InlineList; } // namespace Reactions +namespace ReplyButton { +struct ButtonParameters; +} // namespace ReplyButton + // Special type of Component for the channel actions log. struct LogEntryOriginal : RuntimeComponent { LogEntryOriginal(); @@ -68,6 +72,8 @@ enum class BadgeRole : uchar { struct RightBadge : RuntimeComponent { Ui::Text::String tag; Ui::Text::String boosts; + mutable ClickHandlerPtr tagLink; + mutable ClickHandlerPtr boostsLink; int width = 0; BadgeRole role = BadgeRole::User; bool overridden = false; @@ -126,6 +132,9 @@ public: Reactions::ButtonParameters reactionButtonParameters( QPoint position, const TextState &reactionState) const override; + ReplyButton::ButtonParameters replyButtonParameters( + QPoint position, + const TextState &replyState) const override; int reactionsOptimalWidth() const override; void unloadHeavyPart() override; @@ -300,9 +309,7 @@ private: [[nodiscard]] bool needInfoDisplay() const; [[nodiscard]] bool invertMedia() const; [[nodiscard]] bool hasFastReply() const; - [[nodiscard]] bool hasFastForward() const; [[nodiscard]] bool displayFastReply() const; - [[nodiscard]] bool displayFastForward() const; [[nodiscard]] bool isPinnedContext() const; [[nodiscard]] bool isCommentsRootView() const; diff --git a/Telegram/SourceFiles/history/view/history_view_reply_button.cpp b/Telegram/SourceFiles/history/view/history_view_reply_button.cpp new file mode 100644 index 0000000000..8b2d5c3446 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_reply_button.cpp @@ -0,0 +1,350 @@ +/* +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_reply_button.h" + +#include "history/view/history_view_cursor_state.h" +#include "ui/chat/chat_style.h" +#include "lang/lang_keys.h" +#include "styles/style_chat.h" + +namespace HistoryView::ReplyButton { +namespace { + +constexpr auto kToggleDuration = crl::time(120); +constexpr auto kButtonShowDelay = crl::time(300); +constexpr auto kButtonHideDelay = crl::time(300); + +[[nodiscard]] float64 ScaleForState(ButtonState state) { + switch (state) { + case ButtonState::Hidden: return 0.; + case ButtonState::Shown: + case ButtonState::Active: + case ButtonState::Inside: return 1.; + } + Unexpected("State in ReplyButton::ScaleForState."); +} + +[[nodiscard]] float64 OpacityForScale(float64 scale) { + return scale; +} + +[[nodiscard]] QSize ComputeInnerSize() { + return QSize(ComputeInnerWidth(), st::replyCornerHeight); +} + +[[nodiscard]] QSize ComputeOuterSize() { + return QRect( + QPoint(), + ComputeInnerSize() + ).marginsAdded(st::replyCornerShadow).size(); +} + +} // namespace + +int ComputeInnerWidth() { + struct Cached { + QString text; + int result = 0; + }; + static auto cached = Cached(); + const auto &text = tr::lng_fast_reply(tr::now); + if (cached.text != text) { + const auto &padding = st::replyCornerTextPadding; + const auto textWidth = st::msgDateTextStyle.font->width(text); + cached.result = padding.left() + textWidth + padding.right(); + cached.text = text; + } + return cached.result; +} + +Button::Button( + Fn update, + ButtonParameters parameters, + Fn hide, + QSize outer) +: _update(std::move(update)) +, _finalScale(ScaleForState(_state)) +, _collapsed(QPoint(), outer) +, _geometry(_collapsed) +, _hideTimer(hide) { + applyParameters(parameters, nullptr); +} + +Button::~Button() = default; + +bool Button::isHidden() const { + return (_state == ButtonState::Hidden) + && !_opacityAnimation.animating(); +} + +QRect Button::geometry() const { + return _geometry; +} + +float64 Button::currentScale() const { + return _scaleAnimation.value(_finalScale); +} + +float64 Button::currentOpacity() const { + return _opacityAnimation.value( + OpacityForScale(ScaleForState(_state))); +} + +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); + applyState(ButtonState::Shown, update); + if (parameters.outside) { + _hideTimer.callOnce(kButtonHideDelay); + } else { + _hideTimer.cancel(); + } +} + +void Button::updateGeometry(Fn update) { + if (_geometry != _collapsed) { + if (update) { + update(_geometry); + } + _geometry = _collapsed; + if (update) { + update(_geometry); + } + } +} + +void Button::applyState(ButtonState state) { + applyState(state, _update); +} + +void Button::applyState(ButtonState state, Fn update) { + if (state == ButtonState::Hidden) { + _hideTimer.cancel(); + } + updateGeometry(update); + if (_state == state) { + return; + } + const auto finalScale = ScaleForState(state); + _opacityAnimation.start( + [=] { _update(_geometry); }, + OpacityForScale(ScaleForState(_state)), + OpacityForScale(ScaleForState(state)), + kToggleDuration, + anim::sineInOut); + if (state != ButtonState::Hidden && _finalScale != finalScale) { + _scaleAnimation.start( + [=] { _update(_geometry); }, + _finalScale, + finalScale, + kToggleDuration, + anim::sineInOut); + _finalScale = finalScale; + } + _state = state; +} + +Manager::Manager(Fn buttonUpdate) +: _outer(ComputeOuterSize()) +, _inner(QRect(QPoint(), ComputeInnerSize())) +, _cachedRound( + ComputeInnerSize(), + st::replyCornerShadow, + ComputeInnerSize().height()) +, _buttonShowTimer([=] { showButtonDelayed(); }) +, _buttonUpdate(std::move(buttonUpdate)) +, _text(st::msgDateTextStyle.font->width(tr::lng_fast_reply(tr::now))) { + _text.setText(st::msgDateTextStyle, tr::lng_fast_reply(tr::now)); + _inner.translate( + QRect(QPoint(), _outer).center() - _inner.center()); +} + +Manager::~Manager() = default; + +void Manager::updateButton(ButtonParameters parameters) { + const auto contextChanged = (_buttonContext != parameters.context); + if (contextChanged) { + if (_button) { + _button->applyState(ButtonState::Hidden); + _buttonHiding.push_back(std::move(_button)); + } + _buttonShowTimer.cancel(); + _scheduledParameters = std::nullopt; + } + _buttonContext = parameters.context; + if (parameters.link) { + _link = parameters.link; + } + if (!_buttonContext) { + return; + } else if (_button) { + _button->applyParameters(parameters); + 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::showButtonDelayed() { + clearAppearAnimations(); + _button = std::make_unique