mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Support nice Reply button in the corner.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ ResolveWindow ResolveWindowDefault() {
|
||||
return [](not_null<Main::Session*> 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());
|
||||
|
||||
@@ -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<HistoryView::Reactions::Manager>(
|
||||
this,
|
||||
[=](QRect updated) { update(updated); }))
|
||||
, _replyButtonManager(
|
||||
std::make_unique<HistoryView::ReplyButton::Manager>(
|
||||
[=](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<const HistoryItem*> 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<const Element*> 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) {
|
||||
|
||||
@@ -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<const Element*> view,
|
||||
QPoint position,
|
||||
const HistoryView::TextState &replyState) const
|
||||
-> HistoryView::ReplyButton::ButtonParameters;
|
||||
void toggleFavoriteReaction(not_null<Element*> view) const;
|
||||
void reactionChosen(const ChosenReaction &reaction);
|
||||
|
||||
@@ -512,6 +522,7 @@ private:
|
||||
|
||||
std::unique_ptr<HistoryView::Reactions::Manager> _reactionsManager;
|
||||
rpl::variable<HistoryItem*> _reactionsItem;
|
||||
std::unique_ptr<HistoryView::ReplyButton::Manager> _replyButtonManager;
|
||||
HistoryItem *_pinnedItem = nullptr;
|
||||
|
||||
MouseAction _mouseAction = MouseAction::None;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<ReplyButton::Manager>(
|
||||
[=](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<const Element*> 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<const HistoryItem*> item) {
|
||||
if (_reactionsManager) {
|
||||
_reactionsManager->remove(item->fullId());
|
||||
}
|
||||
if (_replyButtonManager) {
|
||||
_replyButtonManager->remove(item->fullId());
|
||||
}
|
||||
updateItemsGeometry();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<const Element*> view,
|
||||
QPoint position,
|
||||
const TextState &reactionState) const;
|
||||
[[nodiscard]] ReplyButton::ButtonParameters replyButtonParameters(
|
||||
not_null<const Element*> view,
|
||||
QPoint position,
|
||||
const TextState &replyState) const;
|
||||
void toggleFavoriteReaction(not_null<Element*> view) const;
|
||||
|
||||
|
||||
@@ -781,6 +790,8 @@ private:
|
||||
rpl::variable<HistoryItem*> _reactionsItem;
|
||||
bool _useCornerReaction = false;
|
||||
|
||||
std::unique_ptr<ReplyButton::Manager> _replyButtonManager;
|
||||
|
||||
std::unique_ptr<TranslateTracker> _translateTracker;
|
||||
|
||||
int _minHeight = 0;
|
||||
|
||||
@@ -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<Data::ForumTopic*> 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<RightBadge>()) {
|
||||
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<RightBadge>()) {
|
||||
if (const auto badge = Get<RightBadge>()) {
|
||||
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<RightBadge>();
|
||||
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<LambdaClickHandler>([=](
|
||||
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<LambdaClickHandler>([=](
|
||||
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,20 +4169,7 @@ ClickHandlerPtr Message::fastReplyLink() const {
|
||||
return _fastReplyLink;
|
||||
}
|
||||
const auto itemId = data()->fullId();
|
||||
const auto sessionId = data()->history()->session().uniqueId();
|
||||
_fastReplyLink = hasFastForward()
|
||||
? std::make_shared<LambdaClickHandler>([=](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<LambdaClickHandler>(crl::guard(this, [=] {
|
||||
_fastReplyLink = std::make_shared<LambdaClickHandler>(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<RightBadge>()) {
|
||||
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);
|
||||
|
||||
@@ -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, Element> {
|
||||
LogEntryOriginal();
|
||||
@@ -68,6 +72,8 @@ enum class BadgeRole : uchar {
|
||||
struct RightBadge : RuntimeComponent<RightBadge, Element> {
|
||||
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;
|
||||
|
||||
@@ -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<void(QRect)> update,
|
||||
ButtonParameters parameters,
|
||||
Fn<void()> 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<void(QRect)> 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<void(QRect)> 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<void(QRect)> 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<void(QRect)> 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<Button>(
|
||||
_buttonUpdate,
|
||||
*_scheduledParameters,
|
||||
[=] { updateButton({}); },
|
||||
_outer);
|
||||
}
|
||||
|
||||
void Manager::paint(QPainter &p, const PaintContext &context) {
|
||||
removeStaleButtons();
|
||||
for (const auto &button : _buttonHiding) {
|
||||
paintButton(p, context, button.get());
|
||||
}
|
||||
if (const auto current = _button.get()) {
|
||||
if (context.gestureHorizontal.ratio) {
|
||||
current->applyState(ButtonState::Hidden);
|
||||
_buttonHiding.push_back(std::move(_button));
|
||||
}
|
||||
paintButton(p, context, current);
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::paintButton(
|
||||
QPainter &p,
|
||||
const PaintContext &context,
|
||||
not_null<Button*> button) {
|
||||
const auto geometry = button->geometry();
|
||||
if (!context.clip.intersects(geometry)) {
|
||||
return;
|
||||
}
|
||||
constexpr auto kFramesCount = Ui::RoundAreaWithShadow::kFramesCount;
|
||||
const auto scale = button->currentScale();
|
||||
const auto scaleMin = ScaleForState(ButtonState::Hidden);
|
||||
const auto scaleMax = ScaleForState(ButtonState::Shown);
|
||||
const auto progress = (scale - scaleMin) / (scaleMax - scaleMin);
|
||||
const auto frame = int(
|
||||
base::SafeRound(progress * (kFramesCount - 1)));
|
||||
const auto useScale = scaleMin
|
||||
+ (frame / float64(kFramesCount - 1))
|
||||
* (scaleMax - scaleMin);
|
||||
paintButton(p, context, button, frame, useScale);
|
||||
}
|
||||
|
||||
void Manager::paintButton(
|
||||
QPainter &p,
|
||||
const PaintContext &context,
|
||||
not_null<Button*> button,
|
||||
int frameIndex,
|
||||
float64 scale) {
|
||||
const auto opacity = button->currentOpacity();
|
||||
if (opacity == 0.) {
|
||||
return;
|
||||
}
|
||||
const auto geometry = button->geometry();
|
||||
const auto position = geometry.topLeft();
|
||||
if (opacity != 1.) {
|
||||
p.setOpacity(opacity);
|
||||
}
|
||||
const auto shadow = context.st->shadowFg()->c;
|
||||
const auto background = context.st->windowBg()->c;
|
||||
_cachedRound.setShadowColor(shadow);
|
||||
_cachedRound.setBackgroundColor(background);
|
||||
const auto radius = _inner.height() / 2.;
|
||||
const auto frame = _cachedRound.validateFrame(
|
||||
frameIndex,
|
||||
scale,
|
||||
radius);
|
||||
p.drawImage(position, *frame.image, frame.rect);
|
||||
|
||||
const auto textLeft = position.x()
|
||||
+ _inner.x()
|
||||
+ st::replyCornerTextPadding.left();
|
||||
const auto textTop = position.y()
|
||||
+ _inner.y()
|
||||
+ (_inner.height() - st::msgDateTextStyle.font->height) / 2;
|
||||
const auto &incomingStyle = context.st->messageStyle(false, false);
|
||||
p.setPen(incomingStyle.msgDateFg);
|
||||
p.setFont(st::msgDateTextStyle.font);
|
||||
_text.draw(p, {
|
||||
.position = QPoint(textLeft, textTop),
|
||||
.availableWidth = _text.maxWidth(),
|
||||
});
|
||||
if (opacity != 1.) {
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
}
|
||||
|
||||
TextState Manager::buttonTextState(QPoint position) const {
|
||||
if (overCurrentButton(position)) {
|
||||
auto result = TextState(nullptr, _link);
|
||||
result.itemId = _buttonContext;
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Manager::overCurrentButton(QPoint position) const {
|
||||
if (!_button) {
|
||||
return false;
|
||||
}
|
||||
return buttonInner().contains(position);
|
||||
}
|
||||
|
||||
QMargins Manager::innerMargins() const {
|
||||
return {
|
||||
_inner.x(),
|
||||
_inner.y(),
|
||||
_outer.width() - _inner.x() - _inner.width(),
|
||||
_outer.height() - _inner.y() - _inner.height(),
|
||||
};
|
||||
}
|
||||
|
||||
QRect Manager::buttonInner() const {
|
||||
return buttonInner(_button.get());
|
||||
}
|
||||
|
||||
QRect Manager::buttonInner(not_null<Button*> button) const {
|
||||
return button->geometry().marginsRemoved(innerMargins());
|
||||
}
|
||||
|
||||
void Manager::remove(FullMsgId context) {
|
||||
if (_buttonContext == context) {
|
||||
_buttonContext = {};
|
||||
_button = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Manager::removeStaleButtons() {
|
||||
_buttonHiding.erase(
|
||||
ranges::remove_if(_buttonHiding, &Button::isHidden),
|
||||
end(_buttonHiding));
|
||||
}
|
||||
|
||||
void Manager::clearAppearAnimations() {
|
||||
_buttonHiding.clear();
|
||||
}
|
||||
|
||||
} // namespace HistoryView::ReplyButton
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/round_area_with_shadow.h"
|
||||
#include "ui/text/text.h"
|
||||
|
||||
namespace Ui {
|
||||
struct ChatPaintContext;
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView {
|
||||
using PaintContext = Ui::ChatPaintContext;
|
||||
struct TextState;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace HistoryView::ReplyButton {
|
||||
|
||||
struct ButtonParameters {
|
||||
[[nodiscard]] ButtonParameters translated(QPoint delta) const {
|
||||
auto result = *this;
|
||||
result.center += delta;
|
||||
result.pointer += delta;
|
||||
return result;
|
||||
}
|
||||
|
||||
FullMsgId context;
|
||||
QPoint center;
|
||||
QPoint pointer;
|
||||
QPoint globalPointer;
|
||||
ClickHandlerPtr link;
|
||||
int visibleTop = 0;
|
||||
int visibleBottom = 0;
|
||||
bool outside = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] int ComputeInnerWidth();
|
||||
|
||||
enum class ButtonState {
|
||||
Hidden,
|
||||
Shown,
|
||||
Active,
|
||||
Inside,
|
||||
};
|
||||
|
||||
class Button final {
|
||||
public:
|
||||
Button(
|
||||
Fn<void(QRect)> update,
|
||||
ButtonParameters parameters,
|
||||
Fn<void()> hide,
|
||||
QSize outer);
|
||||
~Button();
|
||||
|
||||
void applyParameters(ButtonParameters parameters);
|
||||
void applyState(ButtonState state);
|
||||
|
||||
[[nodiscard]] bool isHidden() const;
|
||||
[[nodiscard]] QRect geometry() const;
|
||||
[[nodiscard]] float64 currentScale() const;
|
||||
[[nodiscard]] float64 currentOpacity() const;
|
||||
|
||||
private:
|
||||
void updateGeometry(Fn<void(QRect)> update);
|
||||
void applyState(ButtonState state, Fn<void(QRect)> update);
|
||||
void applyParameters(
|
||||
ButtonParameters parameters,
|
||||
Fn<void(QRect)> update);
|
||||
|
||||
const Fn<void(QRect)> _update;
|
||||
|
||||
ButtonState _state = ButtonState::Hidden;
|
||||
float64 _finalScale = 0.;
|
||||
Ui::Animations::Simple _scaleAnimation;
|
||||
Ui::Animations::Simple _opacityAnimation;
|
||||
|
||||
QRect _collapsed;
|
||||
QRect _geometry;
|
||||
|
||||
base::Timer _hideTimer;
|
||||
|
||||
};
|
||||
|
||||
class Manager final : public base::has_weak_ptr {
|
||||
public:
|
||||
Manager(Fn<void(QRect)> buttonUpdate);
|
||||
~Manager();
|
||||
|
||||
void updateButton(ButtonParameters parameters);
|
||||
void paint(QPainter &p, const PaintContext &context);
|
||||
[[nodiscard]] TextState buttonTextState(QPoint position) const;
|
||||
void remove(FullMsgId context);
|
||||
|
||||
private:
|
||||
void showButtonDelayed();
|
||||
[[nodiscard]] bool overCurrentButton(QPoint position) const;
|
||||
void paintButton(
|
||||
QPainter &p,
|
||||
const PaintContext &context,
|
||||
not_null<Button*> button);
|
||||
void paintButton(
|
||||
QPainter &p,
|
||||
const PaintContext &context,
|
||||
not_null<Button*> button,
|
||||
int frame,
|
||||
float64 scale);
|
||||
void removeStaleButtons();
|
||||
void clearAppearAnimations();
|
||||
[[nodiscard]] QMargins innerMargins() const;
|
||||
[[nodiscard]] QRect buttonInner() const;
|
||||
[[nodiscard]] QRect buttonInner(not_null<Button*> button) const;
|
||||
|
||||
QSize _outer;
|
||||
QRect _inner;
|
||||
Ui::RoundAreaWithShadow _cachedRound;
|
||||
|
||||
ClickHandlerPtr _link;
|
||||
FullMsgId _buttonContext;
|
||||
|
||||
std::optional<ButtonParameters> _scheduledParameters;
|
||||
base::Timer _buttonShowTimer;
|
||||
const Fn<void(QRect)> _buttonUpdate;
|
||||
std::unique_ptr<Button> _button;
|
||||
std::vector<std::unique_ptr<Button>> _buttonHiding;
|
||||
|
||||
Ui::Text::String _text;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView::ReplyButton
|
||||
@@ -218,7 +218,7 @@ UnwrappedMedia::SurroundingInfo UnwrappedMedia::surroundingInfo(
|
||||
panelHeight += reply->height()
|
||||
- ((forwarded || via) ? 0 : replyMargins.top())
|
||||
- replyMargins.bottom();
|
||||
} else {
|
||||
} else if (panelHeight) {
|
||||
panelHeight += st::msgReplyPadding.bottom();
|
||||
}
|
||||
const auto total = (topicSize.isEmpty() ? 0 : topicSize.height())
|
||||
|
||||
@@ -916,6 +916,11 @@ reactionMainAppearShift: 20px;
|
||||
reactionCollapseFadeThreshold: 40px;
|
||||
reactionFlyUp: 50px;
|
||||
|
||||
replyCornerHeight: 20px;
|
||||
replyCornerShadow: margins(4px, 8px, 4px, 8px);
|
||||
replyCornerCenter: point(7px, -2px);
|
||||
replyCornerTextPadding: margins(6px, 0px, 6px, 0px);
|
||||
|
||||
effectInfoImage: 12px;
|
||||
|
||||
searchInChatMultiSelectItem: MultiSelectItem(defaultMultiSelectItem) {
|
||||
|
||||
Reference in New Issue
Block a user