Highlight reply quote in original message.

This commit is contained in:
John Preston 2023-10-30 21:54:00 +04:00
parent 8615a25cd1
commit d1c310de00
29 changed files with 342 additions and 166 deletions

View File

@ -579,11 +579,6 @@ bool InnerWidget::elementUnderCursor(
return (Element::Hovered() == view); return (Element::Hovered() == view);
} }
float64 InnerWidget::elementHighlightOpacity(
not_null<const HistoryItem*> item) const {
return 0.;
}
bool InnerWidget::elementInSelectionMode() { bool InnerWidget::elementInSelectionMode() {
return false; return false;
} }

View File

@ -93,8 +93,6 @@ public:
HistoryView::Context elementContext() override; HistoryView::Context elementContext() override;
bool elementUnderCursor( bool elementUnderCursor(
not_null<const HistoryView::Element*> view) override; not_null<const HistoryView::Element*> view) override;
[[nodiscard]] float64 elementHighlightOpacity(
not_null<const HistoryItem*> item) const override;
bool elementInSelectionMode() override; bool elementInSelectionMode() override;
bool elementIntersectsRange( bool elementIntersectsRange(
not_null<const HistoryView::Element*> view, not_null<const HistoryView::Element*> view,

View File

@ -202,10 +202,6 @@ public:
not_null<const Element*> view) override { not_null<const Element*> view) override {
return (Element::Moused() == view); return (Element::Moused() == view);
} }
[[nodiscard]] float64 elementHighlightOpacity(
not_null<const HistoryItem*> item) const override {
return _widget ? _widget->elementHighlightOpacity(item) : 0.;
}
bool elementInSelectionMode() override { bool elementInSelectionMode() override {
return _widget ? _widget->inSelectionMode() : false; return _widget ? _widget->inSelectionMode() : false;
} }
@ -1060,6 +1056,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto clip = e->rect(); auto clip = e->rect();
auto context = preparePaintContext(clip); auto context = preparePaintContext(clip);
context.highlightPathCache = &_highlightPathCache;
_pathGradient->startFrame( _pathGradient->startFrame(
0, 0,
width(), width(),
@ -1143,7 +1140,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
} else if (item->isUnreadMention() } else if (item->isUnreadMention()
&& !item->isUnreadMedia()) { && !item->isUnreadMedia()) {
readContents.insert(item); readContents.insert(item);
_widget->enqueueMessageHighlight(view); _widget->enqueueMessageHighlight(view, {});
} }
} }
session().data().reactions().poll(item, context.now); session().data().reactions().poll(item, context.now);
@ -1185,6 +1182,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
view, view,
selfromy - mtop, selfromy - mtop,
seltoy - mtop); seltoy - mtop);
context.highlight = _widget->itemHighlight(view->data());
view->draw(p, context); view->draw(p, context);
processPainted(view, top, height); processPainted(view, top, height);
@ -1219,9 +1217,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
const auto &sendingAnimation = _controller->sendingAnimation(); const auto &sendingAnimation = _controller->sendingAnimation();
while (top < drawToY) { while (top < drawToY) {
const auto height = view->height(); const auto height = view->height();
const auto item = view->data();
if ((context.clip.y() < height) if ((context.clip.y() < height)
&& (hdrawtop < top + height) && (hdrawtop < top + height)
&& !sendingAnimation.hasAnimatedMessage(view->data())) { && !sendingAnimation.hasAnimatedMessage(item)) {
context.reactionInfo context.reactionInfo
= _reactionsManager->currentReactionPaintInfo(); = _reactionsManager->currentReactionPaintInfo();
context.outbg = view->hasOutLayout(); context.outbg = view->hasOutLayout();
@ -1229,6 +1228,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
view, view,
selfromy - htop, selfromy - htop,
seltoy - htop); seltoy - htop);
context.highlight = _widget->itemHighlight(item);
view->draw(p, context); view->draw(p, context);
processPainted(view, top, height); processPainted(view, top, height);
} }
@ -2410,6 +2410,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_menu->addAction(text, [=] { _menu->addAction(text, [=] {
if (canSendReply) { if (canSendReply) {
_widget->replyToMessage({ itemId, quote }); _widget->replyToMessage({ itemId, quote });
if (!quote.empty()) {
_widget->clearSelected();
}
} else { } else {
HistoryView::Controls::ShowReplyToChatBox( HistoryView::Controls::ShowReplyToChatBox(
controller->uiShow(), controller->uiShow(),
@ -3474,11 +3477,6 @@ void HistoryInner::elementStartStickerLoop(
_animatedStickersPlayed.emplace(view->data()); _animatedStickersPlayed.emplace(view->data());
} }
float64 HistoryInner::elementHighlightOpacity(
not_null<const HistoryItem*> item) const {
return _widget->highlightOpacity(item);
}
void HistoryInner::elementShowPollResults( void HistoryInner::elementShowPollResults(
not_null<PollData*> poll, not_null<PollData*> poll,
FullMsgId context) { FullMsgId context) {

View File

@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "history/view/history_view_top_bar_widget.h" #include "history/view/history_view_top_bar_widget.h"
#include <QtGui/QPainterPath>
struct ClickContext; struct ClickContext;
struct ClickHandlerContext; struct ClickHandlerContext;
@ -136,8 +138,6 @@ public:
int from, int from,
int till) const; int till) const;
void elementStartStickerLoop(not_null<const Element*> view); void elementStartStickerLoop(not_null<const Element*> view);
[[nodiscard]] float64 elementHighlightOpacity(
not_null<const HistoryItem*> item) const;
void elementShowPollResults( void elementShowPollResults(
not_null<PollData*> poll, not_null<PollData*> poll,
FullMsgId context); FullMsgId context);
@ -462,6 +462,7 @@ private:
std::optional<Ui::ReportReason> _chooseForReportReason; std::optional<Ui::ReportReason> _chooseForReportReason;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient; const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
QPainterPath _highlightPathCache;
bool _isChatWide = false; bool _isChatWide = false;
base::flat_set<not_null<const HistoryItem*>> _animatedStickersPlayed; base::flat_set<not_null<const HistoryItem*>> _animatedStickersPlayed;

View File

@ -630,7 +630,10 @@ void HistoryMessageReply::setLinkFrom(
} }
}; };
_link = resolvedMessage _link = resolvedMessage
? JumpToMessageClickHandler(resolvedMessage.get(), holder->fullId()) ? JumpToMessageClickHandler(
resolvedMessage.get(),
holder->fullId(),
_fields.quote)
: resolvedStory : resolvedStory
? JumpToStoryClickHandler(resolvedStory.get()) ? JumpToStoryClickHandler(resolvedStory.get())
: (external && !_fields.messageId) : (external && !_fields.messageId)

View File

@ -259,17 +259,20 @@ bool IsItemScheduledUntilOnline(not_null<const HistoryItem*> item) {
ClickHandlerPtr JumpToMessageClickHandler( ClickHandlerPtr JumpToMessageClickHandler(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
FullMsgId returnToId) { FullMsgId returnToId,
TextWithEntities highlightPart) {
return JumpToMessageClickHandler( return JumpToMessageClickHandler(
item->history()->peer, item->history()->peer,
item->id, item->id,
returnToId); returnToId,
std::move(highlightPart));
} }
ClickHandlerPtr JumpToMessageClickHandler( ClickHandlerPtr JumpToMessageClickHandler(
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId msgId, MsgId msgId,
FullMsgId returnToId) { FullMsgId returnToId,
TextWithEntities highlightPart) {
return std::make_shared<LambdaClickHandler>([=] { return std::make_shared<LambdaClickHandler>([=] {
const auto separate = Core::App().separateWindowForPeer(peer); const auto separate = Core::App().separateWindowForPeer(peer);
const auto controller = separate const auto controller = separate
@ -279,6 +282,7 @@ ClickHandlerPtr JumpToMessageClickHandler(
auto params = Window::SectionShow{ auto params = Window::SectionShow{
Window::SectionShow::Way::Forward Window::SectionShow::Way::Forward
}; };
params.highlightPart = highlightPart;
params.origin = Window::SectionShow::OriginMessage{ params.origin = Window::SectionShow::OriginMessage{
returnToId returnToId
}; };

View File

@ -120,10 +120,12 @@ struct SendingErrorRequest {
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler( [[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId msgId, MsgId msgId,
FullMsgId returnToId = FullMsgId()); FullMsgId returnToId = FullMsgId(),
TextWithEntities highlightPart = {});
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler( [[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
FullMsgId returnToId = FullMsgId()); FullMsgId returnToId = FullMsgId(),
TextWithEntities highlightPart = {});
[[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler( [[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler(
not_null<Data::Story*> story); not_null<Data::Story*> story);
ClickHandlerPtr JumpToStoryClickHandler( ClickHandlerPtr JumpToStoryClickHandler(

View File

@ -10,12 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "ui/chat/chat_style.h"
namespace HistoryView { namespace HistoryView {
constexpr auto kAnimationFirstPart = st::activeFadeInDuration
/ float64(st::activeFadeInDuration + st::activeFadeOutDuration);
ElementHighlighter::ElementHighlighter( ElementHighlighter::ElementHighlighter(
not_null<Data::Session*> data, not_null<Data::Session*> data,
ViewForItem viewForItem, ViewForItem viewForItem,
@ -26,14 +24,15 @@ ElementHighlighter::ElementHighlighter(
, _animation(*this) { , _animation(*this) {
} }
void ElementHighlighter::enqueue(not_null<Element*> view) { void ElementHighlighter::enqueue(
not_null<Element*> view,
TextSelection part) {
const auto item = view->data(); const auto item = view->data();
const auto fullId = item->fullId(); const auto data = Highlight{ item->fullId(), part };
if (_queue.empty() && !_animation.animating()) { if (_queue.empty() && !_animation.animating()) {
highlight(fullId); highlight(data.itemId, data.part);
} else if (_highlightedMessageId != fullId } else if (_highlighted != data && !base::contains(_queue, data)) {
&& !base::contains(_queue, fullId)) { _queue.push_back(data);
_queue.push_back(fullId);
checkNextHighlight(); checkNextHighlight();
} }
} }
@ -42,47 +41,46 @@ void ElementHighlighter::checkNextHighlight() {
if (_animation.animating()) { if (_animation.animating()) {
return; return;
} }
const auto nextHighlight = [&] { const auto next = [&] {
while (!_queue.empty()) { while (!_queue.empty()) {
const auto fullId = _queue.front(); const auto highlight = _queue.front();
_queue.pop_front(); _queue.pop_front();
if (const auto item = _data->message(fullId)) { if (const auto item = _data->message(highlight.itemId)) {
if (_viewForItem(item)) { if (_viewForItem(item)) {
return fullId; return highlight;
} }
} }
} }
return FullMsgId(); return Highlight();
}(); }();
if (!nextHighlight) { if (!next) {
return; return;
} }
highlight(nextHighlight); highlight(next.itemId, next.part);
} }
float64 ElementHighlighter::progress( Ui::ChatPaintHighlight ElementHighlighter::state(
not_null<const HistoryItem*> item) const { not_null<const HistoryItem*> item) const {
if (item->fullId() == _highlightedMessageId) { if (item->fullId() == _highlighted.itemId) {
const auto progress = _animation.progress(); auto result = _animation.state();
return std::min(progress / kAnimationFirstPart, 1.) result.range = _highlighted.part;
- ((progress - kAnimationFirstPart) / (1. - kAnimationFirstPart)); return result;
} }
return 0.; return {};
} }
void ElementHighlighter::highlight(FullMsgId itemId) { void ElementHighlighter::highlight(FullMsgId itemId, TextSelection part) {
if (const auto item = _data->message(itemId)) { if (const auto item = _data->message(itemId)) {
if (const auto view = _viewForItem(item)) { if (const auto view = _viewForItem(item)) {
if (_highlightedMessageId if (_highlighted && _highlighted.itemId != itemId) {
&& _highlightedMessageId != itemId) { if (const auto was = _data->message(_highlighted.itemId)) {
if (const auto was = _data->message(_highlightedMessageId)) {
if (const auto view = _viewForItem(was)) { if (const auto view = _viewForItem(was)) {
repaintHighlightedItem(view); repaintHighlightedItem(view);
} }
} }
} }
_highlightedMessageId = itemId; _highlighted = { itemId, part };
_animation.start(); _animation.start(!part.empty());
repaintHighlightedItem(view); repaintHighlightedItem(view);
} }
@ -105,7 +103,7 @@ void ElementHighlighter::repaintHighlightedItem(
} }
void ElementHighlighter::updateMessage() { void ElementHighlighter::updateMessage() {
if (const auto item = _data->message(_highlightedMessageId)) { if (const auto item = _data->message(_highlighted.itemId)) {
if (const auto view = _viewForItem(item)) { if (const auto view = _viewForItem(item)) {
repaintHighlightedItem(view); repaintHighlightedItem(view);
} }
@ -114,7 +112,7 @@ void ElementHighlighter::updateMessage() {
void ElementHighlighter::clear() { void ElementHighlighter::clear() {
_animation.cancel(); _animation.cancel();
_highlightedMessageId = FullMsgId(); _highlighted = {};
_lastHighlightedMessageId = FullMsgId(); _lastHighlightedMessageId = FullMsgId();
_queue.clear(); _queue.clear();
} }
@ -125,60 +123,117 @@ ElementHighlighter::AnimationManager::AnimationManager(
} }
bool ElementHighlighter::AnimationManager::animating() const { bool ElementHighlighter::AnimationManager::animating() const {
if (anim::Disabled()) { if (_timer && _timer->isActive()) {
return (_timer && _timer->isActive()); return true;
} else { } else if (!anim::Disabled()) {
return _simple.animating(); return _simple.animating();
} }
return false;
} }
float64 ElementHighlighter::AnimationManager::progress() const { Ui::ChatPaintHighlight ElementHighlighter::AnimationManager::state() const {
if (anim::Disabled()) { if (anim::Disabled()) {
return (_timer && _timer->isActive()) ? kAnimationFirstPart : 0.; return {
} else { .opacity = !_timer ? 0. : 1.,
return _simple.value(0.); .collapsion = !_timer ? 0. : _fadingOut ? 1. : 0.,
};
} }
return {
.opacity = ((!_fadingOut && _collapsing)
? 1.
: _simple.value(_fadingOut ? 0. : 1.)),
.collapsion = ((!_withTextPart || !_collapsing)
? 0.
: _fadingOut
? 1.
: _simple.value(1.)),
};
} }
MsgId ElementHighlighter::latestSingleHighlightedMsgId() const { MsgId ElementHighlighter::latestSingleHighlightedMsgId() const {
return _highlightedMessageId return _highlighted.itemId
? _highlightedMessageId.msg ? _highlighted.itemId.msg
: _lastHighlightedMessageId.msg; : _lastHighlightedMessageId.msg;
} }
void ElementHighlighter::AnimationManager::start() { void ElementHighlighter::AnimationManager::start(bool withTextPart) {
_withTextPart = withTextPart;
const auto finish = [=] { const auto finish = [=] {
cancel(); cancel();
_parent._lastHighlightedMessageId = base::take( _parent._lastHighlightedMessageId = base::take(
_parent._highlightedMessageId); _parent._highlighted.itemId);
_parent.checkNextHighlight(); _parent.checkNextHighlight();
}; };
cancel(); cancel();
if (anim::Disabled()) { if (anim::Disabled()) {
_timer.emplace([=] { _timer.emplace([=] {
_parent.updateMessage(); _parent.updateMessage();
finish(); if (_withTextPart && !_fadingOut) {
_fadingOut = true;
_timer->callOnce(st::activeFadeOutDuration);
} else {
finish();
}
}); });
_timer->callOnce(st::activeFadeOutDuration); _timer->callOnce(_withTextPart
? st::activeFadeInDuration
: st::activeFadeOutDuration);
_parent.updateMessage(); _parent.updateMessage();
} else { } else {
const auto to = 1.;
_simple.start( _simple.start(
[=](float64 value) { [=](float64 value) {
_parent.updateMessage(); _parent.updateMessage();
if (value == to) { if (value == 1.) {
finish(); if (_withTextPart) {
_timer.emplace([=] {
_parent.updateMessage();
if (_collapsing) {
_fadingOut = true;
} else {
_collapsing = true;
}
_simple.start([=](float64 value) {
_parent.updateMessage();
if (_fadingOut && value == 0.) {
finish();
} else if (!_fadingOut && value == 1.) {
_timer->callOnce(
st::activeFadeOutDuration);
}
},
_fadingOut ? 1. : 0.,
_fadingOut ? 0. : 1.,
(_fadingOut
? st::activeFadeInDuration
: st::fadeWrapDuration));
});
_timer->callOnce(st::activeFadeInDuration);
} else {
_fadingOut = true;
_simple.start([=](float64 value) {
_parent.updateMessage();
if (value == 0.) {
finish();
}
},
1.,
0.,
st::activeFadeOutDuration);
}
} }
}, },
0., 0.,
to, 1.,
st::activeFadeInDuration + st::activeFadeOutDuration); st::activeFadeInDuration);
} }
} }
void ElementHighlighter::AnimationManager::cancel() { void ElementHighlighter::AnimationManager::cancel() {
_simple.stop(); _simple.stop();
_timer.reset(); _timer.reset();
_fadingOut = false;
_collapsed = false;
_collapsing = false;
} }
} // namespace HistoryView } // namespace HistoryView

View File

@ -16,6 +16,10 @@ namespace Data {
class Session; class Session;
} // namespace Data } // namespace Data
namespace Ui {
struct ChatPaintHighlight;
} // namespace Ui
namespace HistoryView { namespace HistoryView {
class Element; class Element;
@ -29,40 +33,56 @@ public:
ViewForItem viewForItem, ViewForItem viewForItem,
RepaintView repaintView); RepaintView repaintView);
void enqueue(not_null<Element*> view); void enqueue(not_null<Element*> view, TextSelection part);
void highlight(FullMsgId itemId); void highlight(FullMsgId itemId, TextSelection part);
void clear(); void clear();
[[nodiscard]] float64 progress(not_null<const HistoryItem*> item) const; [[nodiscard]] Ui::ChatPaintHighlight state(
not_null<const HistoryItem*> item) const;
[[nodiscard]] MsgId latestSingleHighlightedMsgId() const; [[nodiscard]] MsgId latestSingleHighlightedMsgId() const;
private: private:
void checkNextHighlight();
void repaintHighlightedItem(not_null<const Element*> view);
void updateMessage();
class AnimationManager final { class AnimationManager final {
public: public:
AnimationManager(ElementHighlighter &parent); AnimationManager(ElementHighlighter &parent);
[[nodiscard]] bool animating() const; [[nodiscard]] bool animating() const;
[[nodiscard]] float64 progress() const; [[nodiscard]] Ui::ChatPaintHighlight state() const;
void start(); void start(bool withTextPart);
void cancel(); void cancel();
private: private:
ElementHighlighter &_parent; ElementHighlighter &_parent;
Ui::Animations::Simple _simple; Ui::Animations::Simple _simple;
std::optional<base::Timer> _timer; std::optional<base::Timer> _timer;
bool _withTextPart = false;
bool _collapsing = false;
bool _collapsed = false;
bool _fadingOut = false;
}; };
struct Highlight {
FullMsgId itemId;
TextSelection part;
explicit operator bool() const {
return itemId.operator bool();
}
friend inline auto operator<=>(Highlight, Highlight) = default;
friend inline bool operator==(Highlight, Highlight) = default;
};
void checkNextHighlight();
void repaintHighlightedItem(not_null<const Element*> view);
void updateMessage();
const not_null<Data::Session*> _data; const not_null<Data::Session*> _data;
const ViewForItem _viewForItem; const ViewForItem _viewForItem;
const RepaintView _repaintView; const RepaintView _repaintView;
FullMsgId _highlightedMessageId; Highlight _highlighted;
FullMsgId _lastHighlightedMessageId; FullMsgId _lastHighlightedMessageId;
std::deque<FullMsgId> _queue; std::deque<Highlight> _queue;
AnimationManager _animation; AnimationManager _animation;

View File

@ -1072,7 +1072,7 @@ void HistoryWidget::initTabbedSelector() {
if (!data.recipientOverride) { if (!data.recipientOverride) {
return true; return true;
} else if (data.recipientOverride != _peer) { } else if (data.recipientOverride != _peer) {
showHistory(data.recipientOverride->id, ShowAtTheEndMsgId); showHistory(data.recipientOverride->id, ShowAtTheEndMsgId, {});
} }
return (data.recipientOverride == _peer); return (data.recipientOverride == _peer);
}) | rpl::start_with_next([=](ChatHelpers::InlineChosen data) { }) | rpl::start_with_next([=](ChatHelpers::InlineChosen data) {
@ -1267,13 +1267,14 @@ void HistoryWidget::scrollToAnimationCallback(
} }
void HistoryWidget::enqueueMessageHighlight( void HistoryWidget::enqueueMessageHighlight(
not_null<HistoryView::Element*> view) { not_null<HistoryView::Element*> view,
_highlighter.enqueue(view); TextSelection part) {
_highlighter.enqueue(view, part);
} }
float64 HistoryWidget::highlightOpacity( Ui::ChatPaintHighlight HistoryWidget::itemHighlight(
not_null<const HistoryItem*> item) const { not_null<const HistoryItem*> item) const {
return _highlighter.progress(item); return _highlighter.state(item);
} }
int HistoryWidget::itemTopForHighlight( int HistoryWidget::itemTopForHighlight(
@ -1971,9 +1972,10 @@ bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
void HistoryWidget::showHistory( void HistoryWidget::showHistory(
const PeerId &peerId, const PeerId &peerId,
MsgId showAtMsgId, MsgId showAtMsgId,
bool reload) { const TextWithEntities &highlightPart) {
_pinnedClickedId = FullMsgId(); _pinnedClickedId = FullMsgId();
_minPinnedId = std::nullopt; _minPinnedId = std::nullopt;
_showAtMsgHighlightPart = {};
const auto wasDialogsEntryState = computeDialogsEntryState(); const auto wasDialogsEntryState = computeDialogsEntryState();
const auto startBot = (showAtMsgId == ShowAndStartBotMsgId); const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
@ -1985,7 +1987,7 @@ void HistoryWidget::showHistory(
controller()->sendingAnimation().clear(); controller()->sendingAnimation().clear();
_topToast.hide(anim::type::instant); _topToast.hide(anim::type::instant);
if (_history) { if (_history) {
if (_peer->id == peerId && !reload) { if (_peer->id == peerId) {
updateForwarding(); updateForwarding();
if (showAtMsgId == ShowAtUnreadMsgId if (showAtMsgId == ShowAtUnreadMsgId
@ -2021,10 +2023,10 @@ void HistoryWidget::showHistory(
).arg(_history->inboxReadTillId().bare ).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom()) ).arg(Logs::b(_history->loadedAtBottom())
).arg(showAtMsgId.bare)); ).arg(showAtMsgId.bare));
delayedShowAt(showAtMsgId); delayedShowAt(showAtMsgId, highlightPart);
} else if (_showAtMsgId != showAtMsgId) { } else if (_showAtMsgId != showAtMsgId) {
clearAllLoadRequests(); clearAllLoadRequests();
setMsgId(showAtMsgId); setMsgId(showAtMsgId, highlightPart);
firstLoadMessages(); firstLoadMessages();
doneShow(); doneShow();
} }
@ -2044,7 +2046,7 @@ void HistoryWidget::showHistory(
_cornerButtons.skipReplyReturn(skipId); _cornerButtons.skipReplyReturn(skipId);
} }
setMsgId(showAtMsgId); setMsgId(showAtMsgId, highlightPart);
if (_historyInited) { if (_historyInited) {
DEBUG_LOG(("JumpToEnd(%1, %2, %3): " DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
"Showing instant at %4." "Showing instant at %4."
@ -2147,6 +2149,7 @@ void HistoryWidget::showHistory(
clearInlineBot(); clearInlineBot();
_showAtMsgId = showAtMsgId; _showAtMsgId = showAtMsgId;
_showAtMsgHighlightPart = highlightPart;
_historyInited = false; _historyInited = false;
_contactStatus = nullptr; _contactStatus = nullptr;
@ -3301,7 +3304,7 @@ void HistoryWidget::messagesReceived(
} }
_delayedShowAtRequest = 0; _delayedShowAtRequest = 0;
setMsgId(_delayedShowAtMsgId); setMsgId(_delayedShowAtMsgId, _delayedShowAtMsgHighlightPart);
historyLoaded(); historyLoaded();
} }
if (session().supportMode()) { if (session().supportMode()) {
@ -3523,9 +3526,16 @@ void HistoryWidget::loadMessagesDown() {
}); });
} }
void HistoryWidget::delayedShowAt(MsgId showAtMsgId) { void HistoryWidget::delayedShowAt(
if (!_history MsgId showAtMsgId,
|| (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId)) { const TextWithEntities &highlightPart) {
if (!_history) {
return;
}
if (_delayedShowAtMsgHighlightPart != highlightPart) {
_delayedShowAtMsgHighlightPart = highlightPart;
}
if (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId) {
return; return;
} }
@ -4102,7 +4112,12 @@ PeerData *HistoryWidget::peer() const {
} }
// Sometimes _showAtMsgId is set directly. // Sometimes _showAtMsgId is set directly.
void HistoryWidget::setMsgId(MsgId showAtMsgId) { void HistoryWidget::setMsgId(
MsgId showAtMsgId,
const TextWithEntities &highlightPart) {
if (_showAtMsgHighlightPart != highlightPart) {
_showAtMsgHighlightPart = highlightPart;
}
if (_showAtMsgId != showAtMsgId) { if (_showAtMsgId != showAtMsgId) {
_showAtMsgId = showAtMsgId; _showAtMsgId = showAtMsgId;
if (_history) { if (_history) {
@ -4223,11 +4238,11 @@ void HistoryWidget::cornerButtonsShowAtPosition(
).arg(_history->peer->name() ).arg(_history->peer->name()
).arg(_history->inboxReadTillId().bare ).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom()))); ).arg(Logs::b(_history->loadedAtBottom())));
showHistory(_peer->id, ShowAtUnreadMsgId); showHistory(_peer->id, ShowAtUnreadMsgId, {});
} else if (_peer && position.fullId.peer == _peer->id) { } else if (_peer && position.fullId.peer == _peer->id) {
showHistory(_peer->id, position.fullId.msg); showHistory(_peer->id, position.fullId.msg, {});
} else if (_migrated && position.fullId.peer == _migrated->peer->id) { } else if (_migrated && position.fullId.peer == _migrated->peer->id) {
showHistory(_peer->id, -position.fullId.msg); showHistory(_peer->id, -position.fullId.msg, {});
} }
} }
@ -5678,14 +5693,17 @@ int HistoryWidget::countInitialScrollTop() {
const auto item = getItemFromHistoryOrMigrated(_showAtMsgId); const auto item = getItemFromHistoryOrMigrated(_showAtMsgId);
const auto itemTop = _list->itemTop(item); const auto itemTop = _list->itemTop(item);
if (itemTop < 0) { if (itemTop < 0) {
setMsgId(0); setMsgId(ShowAtUnreadMsgId);
controller()->showToast(tr::lng_message_not_found(tr::now)); controller()->showToast(tr::lng_message_not_found(tr::now));
return countInitialScrollTop(); return countInitialScrollTop();
} else { } else {
const auto view = item->mainView(); const auto view = item->mainView();
Assert(view != nullptr); Assert(view != nullptr);
enqueueMessageHighlight(view); enqueueMessageHighlight(
view,
view->selectionFromQuote(
base::take(_showAtMsgHighlightPart)));
const auto result = itemTopForHighlight(view); const auto result = itemTopForHighlight(view);
createUnreadBarIfBelowVisibleArea(result); createUnreadBarIfBelowVisibleArea(result);
return result; return result;
@ -6377,7 +6395,8 @@ void HistoryWidget::handlePeerMigration() {
if (_peer != channel) { if (_peer != channel) {
showHistory( showHistory(
channel->id, channel->id,
(_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId); (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId,
{});
channel->session().api().chatParticipants().requestCountDelayed( channel->session().api().chatParticipants().requestCountDelayed(
channel); channel);
} else { } else {
@ -6473,7 +6492,7 @@ bool HistoryWidget::showSlowmodeError() {
if (const auto item = _history->latestSendingMessage()) { if (const auto item = _history->latestSendingMessage()) {
if (const auto view = item->mainView()) { if (const auto view = item->mainView()) {
animatedScrollToItem(item->id); animatedScrollToItem(item->id);
enqueueMessageHighlight(view); enqueueMessageHighlight(view, {});
} }
return tr::lng_slowmode_no_many(tr::now); return tr::lng_slowmode_no_many(tr::now);
} }

View File

@ -75,6 +75,7 @@ class SpoilerAnimation;
enum class ReportReason; enum class ReportReason;
class ChooseThemeController; class ChooseThemeController;
class ContinuousScroll; class ContinuousScroll;
struct ChatPaintHighlight;
} // namespace Ui } // namespace Ui
namespace Window { namespace Window {
@ -146,7 +147,9 @@ public:
void loadMessages(); void loadMessages();
void loadMessagesDown(); void loadMessagesDown();
void firstLoadMessages(); void firstLoadMessages();
void delayedShowAt(MsgId showAtMsgId); void delayedShowAt(
MsgId showAtMsgId,
const TextWithEntities &highlightPart);
bool updateReplaceMediaButton(); bool updateReplaceMediaButton();
void updateFieldPlaceholder(); void updateFieldPlaceholder();
@ -160,7 +163,9 @@ public:
History *history() const; History *history() const;
PeerData *peer() const; PeerData *peer() const;
void setMsgId(MsgId showAtMsgId); void setMsgId(
MsgId showAtMsgId,
const TextWithEntities &highlightPart = {});
MsgId msgId() const; MsgId msgId() const;
bool hasTopBarShadow() const { bool hasTopBarShadow() const {
@ -177,8 +182,10 @@ public:
bool touchScroll(const QPoint &delta); bool touchScroll(const QPoint &delta);
void enqueueMessageHighlight(not_null<HistoryView::Element*> view); void enqueueMessageHighlight(
[[nodiscard]] float64 highlightOpacity( not_null<HistoryView::Element*> view,
TextSelection part);
[[nodiscard]] Ui::ChatPaintHighlight itemHighlight(
not_null<const HistoryItem*> item) const; not_null<const HistoryItem*> item) const;
MessageIdsList getSelectedItems() const; MessageIdsList getSelectedItems() const;
@ -218,7 +225,10 @@ public:
void fastShowAtEnd(not_null<History*> history); void fastShowAtEnd(not_null<History*> history);
bool applyDraft( bool applyDraft(
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false); void showHistory(
const PeerId &peer,
MsgId showAtMsgId,
const TextWithEntities &highlightPart);
void setChooseReportMessagesDetails( void setChooseReportMessagesDetails(
Ui::ReportReason reason, Ui::ReportReason reason,
Fn<void(MessageIdsList)> callback); Fn<void(MessageIdsList)> callback);
@ -684,12 +694,14 @@ private:
bool _canSendMessages = false; bool _canSendMessages = false;
bool _canSendTexts = false; bool _canSendTexts = false;
MsgId _showAtMsgId = ShowAtUnreadMsgId; MsgId _showAtMsgId = ShowAtUnreadMsgId;
TextWithEntities _showAtMsgHighlightPart;
int _firstLoadRequest = 0; // Not real mtpRequestId. int _firstLoadRequest = 0; // Not real mtpRequestId.
int _preloadRequest = 0; // Not real mtpRequestId. int _preloadRequest = 0; // Not real mtpRequestId.
int _preloadDownRequest = 0; // Not real mtpRequestId. int _preloadDownRequest = 0; // Not real mtpRequestId.
MsgId _delayedShowAtMsgId = -1; MsgId _delayedShowAtMsgId = -1;
TextWithEntities _delayedShowAtMsgHighlightPart;
int _delayedShowAtRequest = 0; // Not real mtpRequestId. int _delayedShowAtRequest = 0; // Not real mtpRequestId.
History *_supportPreloadHistory = nullptr; History *_supportPreloadHistory = nullptr;

View File

@ -111,11 +111,6 @@ bool DefaultElementDelegate::elementUnderCursor(
return false; return false;
} }
float64 DefaultElementDelegate::elementHighlightOpacity(
not_null<const HistoryItem*> item) const {
return 0.;
}
bool DefaultElementDelegate::elementInSelectionMode() { bool DefaultElementDelegate::elementInSelectionMode() {
return false; return false;
} }
@ -605,18 +600,13 @@ void Element::paintCustomHighlight(
int y, int y,
int height, int height,
not_null<const HistoryItem*> item) const { not_null<const HistoryItem*> item) const {
const auto opacity = delegate()->elementHighlightOpacity(item); const auto opacity = context.highlight.opacity;
if (opacity == 0.) { if (opacity == 0.) {
return; return;
} }
const auto o = p.opacity(); const auto o = p.opacity();
p.setOpacity(o * opacity); p.setOpacity(o * opacity);
p.fillRect( p.fillRect(0, y, width(), height, context.st->msgSelectOverlay());
0,
y,
width(),
height,
context.st->msgSelectOverlay());
p.setOpacity(o); p.setOpacity(o);
} }

View File

@ -69,8 +69,6 @@ class ElementDelegate {
public: public:
virtual Context elementContext() = 0; virtual Context elementContext() = 0;
virtual bool elementUnderCursor(not_null<const Element*> view) = 0; virtual bool elementUnderCursor(not_null<const Element*> view) = 0;
[[nodiscard]] virtual float64 elementHighlightOpacity(
not_null<const HistoryItem*> item) const = 0;
virtual bool elementInSelectionMode() = 0; virtual bool elementInSelectionMode() = 0;
virtual bool elementIntersectsRange( virtual bool elementIntersectsRange(
not_null<const Element*> view, not_null<const Element*> view,
@ -120,8 +118,6 @@ public:
class DefaultElementDelegate : public ElementDelegate { class DefaultElementDelegate : public ElementDelegate {
public: public:
bool elementUnderCursor(not_null<const Element*> view) override; bool elementUnderCursor(not_null<const Element*> view) override;
[[nodiscard]] float64 elementHighlightOpacity(
not_null<const HistoryItem*> item) const override;
bool elementInSelectionMode() override; bool elementInSelectionMode() override;
bool elementIntersectsRange( bool elementIntersectsRange(
not_null<const Element*> view, not_null<const Element*> view,

View File

@ -707,8 +707,15 @@ bool ListWidget::isBelowPosition(Data::MessagePosition position) const {
return _items.front()->data()->position() > position; return _items.front()->data()->position() > position;
} }
void ListWidget::highlightMessage(FullMsgId itemId) { void ListWidget::highlightMessage(
_highlighter.highlight(itemId); FullMsgId itemId,
const TextWithEntities &highlightPart) {
const auto view = !highlightPart.empty()
? viewForItem(itemId)
: nullptr;
_highlighter.highlight(
itemId,
view ? view->selectionFromQuote(highlightPart) : TextSelection());
} }
void ListWidget::showAroundPosition( void ListWidget::showAroundPosition(
@ -741,12 +748,12 @@ bool ListWidget::jumpToBottomInsteadOfUnread() const {
void ListWidget::showAtPosition( void ListWidget::showAtPosition(
Data::MessagePosition position, Data::MessagePosition position,
anim::type animated, const Window::SectionShow &params,
Fn<void(bool found)> done) { Fn<void(bool found)> done) {
const auto showAtUnread = (position == Data::UnreadMessagePosition); const auto showAtUnread = (position == Data::UnreadMessagePosition);
if (showAtUnread && jumpToBottomInsteadOfUnread()) { if (showAtUnread && jumpToBottomInsteadOfUnread()) {
showAtPosition(Data::MaxMessagePosition, animated, std::move(done)); showAtPosition(Data::MaxMessagePosition, params, std::move(done));
return; return;
} }
@ -766,24 +773,24 @@ void ListWidget::showAtPosition(
_bar = {}; _bar = {};
} }
checkUnreadBarCreation(); checkUnreadBarCreation();
return showAtPositionNow(position, animated, done); return showAtPositionNow(position, params, done);
}); });
} else if (!showAtPositionNow(position, animated, done)) { } else if (!showAtPositionNow(position, params, done)) {
showAroundPosition(position, [=] { showAroundPosition(position, [=] {
return showAtPositionNow(position, animated, done); return showAtPositionNow(position, params, done);
}); });
} }
} }
bool ListWidget::showAtPositionNow( bool ListWidget::showAtPositionNow(
Data::MessagePosition position, Data::MessagePosition position,
anim::type animated, const Window::SectionShow &params,
Fn<void(bool found)> done) { Fn<void(bool found)> done) {
if (const auto scrollTop = scrollTopForPosition(position)) { if (const auto scrollTop = scrollTopForPosition(position)) {
computeScrollTo(*scrollTop, position, animated); computeScrollTo(*scrollTop, position, params.animated);
if (position != Data::MaxMessagePosition if (position != Data::MaxMessagePosition
&& position != Data::UnreadMessagePosition) { && position != Data::UnreadMessagePosition) {
highlightMessage(position.fullId); highlightMessage(position.fullId, params.highlightPart);
} }
if (done) { if (done) {
const auto found = !position.fullId.peer const auto found = !position.fullId.peer
@ -1655,11 +1662,6 @@ bool ListWidget::elementUnderCursor(
return (_overElement == view); return (_overElement == view);
} }
float64 ListWidget::elementHighlightOpacity(
not_null<const HistoryItem*> item) const {
return _highlighter.progress(item);
}
bool ListWidget::elementInSelectionMode() { bool ListWidget::elementInSelectionMode() {
return inSelectionMode(); return inSelectionMode();
} }
@ -2108,6 +2110,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
= _reactionsManager->currentReactionPaintInfo(); = _reactionsManager->currentReactionPaintInfo();
context.outbg = view->hasOutLayout(); context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(view); context.selection = itemRenderSelection(view);
context.highlight = _highlighter.state(item);
view->draw(p, context); view->draw(p, context);
} }
if (_translateTracker) { if (_translateTracker) {
@ -2142,7 +2145,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
} else if (item->isUnreadMention() } else if (item->isUnreadMention()
&& !item->isUnreadMedia()) { && !item->isUnreadMedia()) {
readContents.insert(item); readContents.insert(item);
_highlighter.enqueue(view); _highlighter.enqueue(view, {});
} }
} }
session->data().reactions().poll(item, context.now); session->data().reactions().poll(item, context.now);

View File

@ -48,6 +48,10 @@ struct ChosenReaction;
struct ButtonParameters; struct ButtonParameters;
} // namespace HistoryView::Reactions } // namespace HistoryView::Reactions
namespace Window {
struct SectionShow;
} // namespace Window
namespace HistoryView { namespace HistoryView {
struct TextState; struct TextState;
@ -227,11 +231,13 @@ public:
[[nodiscard]] bool animatedScrolling() const; [[nodiscard]] bool animatedScrolling() const;
bool isAbovePosition(Data::MessagePosition position) const; bool isAbovePosition(Data::MessagePosition position) const;
bool isBelowPosition(Data::MessagePosition position) const; bool isBelowPosition(Data::MessagePosition position) const;
void highlightMessage(FullMsgId itemId); void highlightMessage(
FullMsgId itemId,
const TextWithEntities &highlightPart);
void showAtPosition( void showAtPosition(
Data::MessagePosition position, Data::MessagePosition position,
anim::type animated = anim::type::normal, const Window::SectionShow &params,
Fn<void(bool found)> done = nullptr); Fn<void(bool found)> done = nullptr);
void refreshViewer(); void refreshViewer();
@ -292,8 +298,6 @@ public:
// ElementDelegate interface. // ElementDelegate interface.
Context elementContext() override; Context elementContext() override;
bool elementUnderCursor(not_null<const Element*> view) override; bool elementUnderCursor(not_null<const Element*> view) override;
[[nodiscard]] float64 elementHighlightOpacity(
not_null<const HistoryItem*> item) const override;
bool elementInSelectionMode() override; bool elementInSelectionMode() override;
bool elementIntersectsRange( bool elementIntersectsRange(
not_null<const Element*> view, not_null<const Element*> view,
@ -430,7 +434,7 @@ private:
Fn<bool()> overrideInitialScroll); Fn<bool()> overrideInitialScroll);
bool showAtPositionNow( bool showAtPositionNow(
Data::MessagePosition position, Data::MessagePosition position,
anim::type animated, const Window::SectionShow &params,
Fn<void(bool found)> done); Fn<void(bool found)> done);
Ui::ChatPaintContext preparePaintContext(const QRect &clip) const; Ui::ChatPaintContext preparePaintContext(const QRect &clip) const;

View File

@ -1018,6 +1018,10 @@ void Message::draw(Painter &p, const PaintContext &context) const {
p.translate(-reactionsPosition); p.translate(-reactionsPosition);
} }
if (context.highlightPathCache) {
context.highlightInterpolateTo = g;
context.highlightPathCache->clear();
}
if (bubble) { if (bubble) {
if (displayFromName() if (displayFromName()
&& item->displayFrom() && item->displayFrom()
@ -1110,6 +1114,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
- (_bottomInfo.height() - st::msgDateFont->height)); - (_bottomInfo.height() - st::msgDateFont->height));
} }
auto textSelection = context.selection; auto textSelection = context.selection;
auto highlightRange = context.highlight.range;
const auto mediaHeight = mediaDisplayed ? media->height() : 0; const auto mediaHeight = mediaDisplayed ? media->height() : 0;
const auto paintMedia = [&](int top) { const auto paintMedia = [&](int top) {
if (!mediaDisplayed) { if (!mediaDisplayed) {
@ -1130,6 +1135,10 @@ void Message::draw(Painter &p, const PaintContext &context) const {
context.reactionInfo->effectOffset -= add; context.reactionInfo->effectOffset -= add;
} }
} }
if (context.highlightPathCache
&& !context.highlightPathCache->isEmpty()) {
context.highlightPathCache->translate(mediaPosition);
}
p.translate(-mediaPosition); p.translate(-mediaPosition);
}; };
if (mediaDisplayed && _invertMedia) { if (mediaDisplayed && _invertMedia) {
@ -1141,8 +1150,12 @@ void Message::draw(Painter &p, const PaintContext &context) const {
+ mediaHeight + mediaHeight
+ (mediaOnBottom ? 0 : st::mediaInBubbleSkip)); + (mediaOnBottom ? 0 : st::mediaInBubbleSkip));
textSelection = media->skipSelection(textSelection); textSelection = media->skipSelection(textSelection);
highlightRange = media->skipSelection(highlightRange);
} }
paintText(p, trect, context.withSelection(textSelection)); auto copy = context;
copy.selection = textSelection;
copy.highlight.range = highlightRange;
paintText(p, trect, copy);
if (mediaDisplayed && !_invertMedia) { if (mediaDisplayed && !_invertMedia) {
paintMedia(trect.y() + trect.height() - mediaHeight); paintMedia(trect.y() + trect.height() - mediaHeight);
if (context.reactionInfo && !displayInfo && !_reactions) { if (context.reactionInfo && !displayInfo && !_reactions) {
@ -1224,6 +1237,20 @@ void Message::draw(Painter &p, const PaintContext &context) const {
p.restoreTextPalette(); p.restoreTextPalette();
if (context.highlightPathCache
&& !context.highlightPathCache->isEmpty()) {
const auto alpha = int(0.25
* context.highlight.collapsion
* context.highlight.opacity
* 255);
if (alpha > 0) {
context.highlightPathCache->setFillRule(Qt::WindingFill);
auto color = context.messageStyle()->textPalette.linkFg->c;
color.setAlpha(alpha);
p.fillPath(*context.highlightPathCache, color);
}
}
if (roll) { if (roll) {
p.restore(); p.restore();
} }
@ -1651,6 +1678,7 @@ void Message::paintText(
width()); width());
trect.setY(trect.y() + botTop->height); trect.setY(trect.y() + botTop->height);
} }
auto highlightRequest = context.computeHighlightCache();
text().draw(p, { text().draw(p, {
.position = trect.topLeft(), .position = trect.topLeft(),
.availableWidth = trect.width(), .availableWidth = trect.width(),
@ -1663,6 +1691,7 @@ void Message::paintText(
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = context.selection, .selection = context.selection,
.highlight = highlightRequest ? &*highlightRequest : nullptr,
}); });
} }
@ -2732,10 +2761,13 @@ TextWithEntities Message::selectedQuote(
TextSelection Message::selectionFromQuote( TextSelection Message::selectionFromQuote(
const TextWithEntities &quote) const { const TextWithEntities &quote) const {
if (quote.empty()) {
return {};
}
const auto item = data(); const auto item = data();
const auto &translated = item->translatedText(); const auto &translated = item->translatedText();
const auto &original = item->originalText(); const auto &original = item->originalText();
if (&translated != &original || quote.empty()) { if (&translated != &original) {
return {}; return {};
} else if (hasVisibleText()) { } else if (hasVisibleText()) {
const auto media = this->media(); const auto media = this->media();

View File

@ -262,7 +262,7 @@ void PinnedWidget::showAtPosition(
FullMsgId originId) { FullMsgId originId) {
_inner->showAtPosition( _inner->showAtPosition(
position, position,
anim::type::normal, {},
_cornerButtons.doneJumpFrom(position.fullId, originId)); _cornerButtons.doneJumpFrom(position.fullId, originId));
} }
@ -346,7 +346,7 @@ void PinnedWidget::restoreState(not_null<PinnedMemento*> memento) {
? FullMsgId(_history->peer->id, highlight) ? FullMsgId(_history->peer->id, highlight)
: FullMsgId(_migratedPeer->id, -highlight)), : FullMsgId(_migratedPeer->id, -highlight)),
.date = TimeId(0), .date = TimeId(0),
}, anim::type::instant); }, { Window::SectionShow::Way::Forward, anim::type::instant });
} }
} }

View File

@ -1859,16 +1859,22 @@ void RepliesWidget::finishSending() {
refreshTopBarActiveChat(); refreshTopBarActiveChat();
} }
void RepliesWidget::showAtPosition(
Data::MessagePosition position,
FullMsgId originItemId) {
showAtPosition(position, originItemId, {});
}
void RepliesWidget::showAtPosition( void RepliesWidget::showAtPosition(
Data::MessagePosition position, Data::MessagePosition position,
FullMsgId originItemId, FullMsgId originItemId,
anim::type animated) { const Window::SectionShow &params) {
_lastShownAt = position.fullId; _lastShownAt = position.fullId;
controller()->setActiveChatEntry(activeChat()); controller()->setActiveChatEntry(activeChat());
const auto ignore = (position.fullId.msg == _rootId); const auto ignore = (position.fullId.msg == _rootId);
_inner->showAtPosition( _inner->showAtPosition(
position, position,
animated, params,
_cornerButtons.doneJumpFrom(position.fullId, originItemId, ignore)); _cornerButtons.doneJumpFrom(position.fullId, originItemId, ignore));
} }
@ -2030,7 +2036,7 @@ bool RepliesWidget::showMessage(
const auto originItemId = (_cornerButtons.replyReturn() != originMessage) const auto originItemId = (_cornerButtons.replyReturn() != originMessage)
? originMessage->fullId() ? originMessage->fullId()
: FullMsgId(); : FullMsgId();
showAtPosition(message->position(), originItemId); showAtPosition(message->position(), originItemId, params);
return true; return true;
} }
@ -2136,7 +2142,7 @@ void RepliesWidget::restoreState(not_null<RepliesMemento*> memento) {
showAtPosition(Data::MessagePosition{ showAtPosition(Data::MessagePosition{
.fullId = FullMsgId(_history->peer->id, highlight), .fullId = FullMsgId(_history->peer->id, highlight),
.date = TimeId(0), .date = TimeId(0),
}, {}, anim::type::instant); }, {}, { Window::SectionShow::Way::Forward, anim::type::instant });
} }
} }

View File

@ -208,8 +208,11 @@ private:
void showAtEnd(); void showAtEnd();
void showAtPosition( void showAtPosition(
Data::MessagePosition position, Data::MessagePosition position,
FullMsgId originItemId = {}, FullMsgId originItemId = {});
anim::type animated = anim::type::normal); void showAtPosition(
Data::MessagePosition position,
FullMsgId originItemId,
const Window::SectionShow &params);
void finishSending(); void finishSending();
void setupComposeControls(); void setupComposeControls();

View File

@ -858,7 +858,7 @@ void ScheduledWidget::showAtPosition(
FullMsgId originId) { FullMsgId originId) {
_inner->showAtPosition( _inner->showAtPosition(
position, position,
anim::type::normal, {},
_cornerButtons.doneJumpFrom(position.fullId, originId)); _cornerButtons.doneJumpFrom(position.fullId, originId));
} }

View File

@ -744,6 +744,7 @@ void Document::draw(
if (const auto captioned = Get<HistoryDocumentCaptioned>()) { if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
p.setPen(stm->historyTextFg); p.setPen(stm->historyTextFg);
_parent->prepareCustomEmojiPaint(p, context, captioned->caption); _parent->prepareCustomEmojiPaint(p, context, captioned->caption);
auto highlightRequest = context.computeHighlightCache();
captioned->caption.draw(p, { captioned->caption.draw(p, {
.position = { st::msgPadding.left(), captiontop }, .position = { st::msgPadding.left(), captiontop },
.availableWidth = captionw, .availableWidth = captionw,
@ -756,6 +757,7 @@ void Document::draw(
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = selection, .selection = selection,
.highlight = highlightRequest ? &*highlightRequest : nullptr,
}); });
} }
} }

View File

@ -229,6 +229,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const {
if (!_caption.isEmpty()) { if (!_caption.isEmpty()) {
p.setPen(stm->historyTextFg); p.setPen(stm->historyTextFg);
_parent->prepareCustomEmojiPaint(p, context, _caption); _parent->prepareCustomEmojiPaint(p, context, _caption);
auto highlightRequest = context.computeHighlightCache();
_caption.draw(p, { _caption.draw(p, {
.position = QPoint( .position = QPoint(
st::msgPadding.left(), st::msgPadding.left(),
@ -243,6 +244,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const {
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = context.selection, .selection = context.selection,
.highlight = highlightRequest ? &*highlightRequest : nullptr,
}); });
} else if (!inWebPage) { } else if (!inWebPage) {
auto fullRight = paintx + paintw; auto fullRight = paintx + paintw;

View File

@ -716,6 +716,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
_parent->width()); _parent->width());
top += botTop->height; top += botTop->height;
} }
auto highlightRequest = context.computeHighlightCache();
_caption.draw(p, { _caption.draw(p, {
.position = QPoint(st::msgPadding.left(), top), .position = QPoint(st::msgPadding.left(), top),
.availableWidth = captionw, .availableWidth = captionw,
@ -728,6 +729,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = context.selection, .selection = context.selection,
.highlight = highlightRequest ? &*highlightRequest : nullptr,
}); });
} else if (!inWebPage && !skipDrawingSurrounding) { } else if (!inWebPage && !skipDrawingSurrounding) {
auto fullRight = paintx + usex + usew; auto fullRight = paintx + usex + usew;

View File

@ -325,12 +325,13 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
: IsGroupItemSelection(selection, i) : IsGroupItemSelection(selection, i)
? FullSelection ? FullSelection
: TextSelection()); : TextSelection());
const auto highlightOpacity = IsGroupItemSelection(
context.highlight.range,
i
) ? context.highlight.opacity : 0.;
if (textSelection) { if (textSelection) {
selection = part.content->skipSelection(selection); selection = part.content->skipSelection(selection);
} }
const auto highlightOpacity = (_mode == Mode::Grid)
? _parent->delegate()->elementHighlightOpacity(part.item)
: 0.;
if (!part.cache.isNull()) { if (!part.cache.isNull()) {
wasCache = true; wasCache = true;
} }
@ -361,6 +362,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
const auto stm = context.messageStyle(); const auto stm = context.messageStyle();
p.setPen(stm->historyTextFg); p.setPen(stm->historyTextFg);
_parent->prepareCustomEmojiPaint(p, context, _caption); _parent->prepareCustomEmojiPaint(p, context, _caption);
auto highlightRequest = context.computeHighlightCache();
_caption.draw(p, { _caption.draw(p, {
.position = QPoint( .position = QPoint(
st::msgPadding.left(), st::msgPadding.left(),
@ -375,6 +377,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = context.selection, .selection = context.selection,
.highlight = highlightRequest ? &*highlightRequest : nullptr,
}); });
} else if (_parent->media() == this) { } else if (_parent->media() == this) {
auto fullRight = width(); auto fullRight = width();

View File

@ -401,6 +401,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
_parent->width()); _parent->width());
top += botTop->height; top += botTop->height;
} }
auto highlightRequest = context.computeHighlightCache();
_caption.draw(p, { _caption.draw(p, {
.position = QPoint(st::msgPadding.left(), top), .position = QPoint(st::msgPadding.left(), top),
.availableWidth = captionw, .availableWidth = captionw,
@ -413,6 +414,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = context.selection, .selection = context.selection,
.highlight = highlightRequest ? &*highlightRequest : nullptr,
}); });
} else if (!inWebPage) { } else if (!inWebPage) {
auto fullRight = paintx + paintw; auto fullRight = paintx + paintw;

View File

@ -1411,7 +1411,7 @@ void MainWidget::showHistory(
&& way != Way::Forward) { && way != Way::Forward) {
clearBotStartToken(_history->peer()); clearBotStartToken(_history->peer());
} }
_history->showHistory(peerId, showAtMsgId); _history->showHistory(peerId, showAtMsgId, params.highlightPart);
if (alreadyThatPeer && params.reapplyLocalDraft) { if (alreadyThatPeer && params.reapplyLocalDraft) {
_history->applyDraft(HistoryWidget::FieldHistoryAction::NewEntry); _history->applyDraft(HistoryWidget::FieldHistoryAction::NewEntry);
} }
@ -1772,7 +1772,7 @@ void MainWidget::showNewSection(
} else { } else {
_mainSection = std::move(newMainSection); _mainSection = std::move(newMainSection);
_history->finishAnimating(); _history->finishAnimating();
_history->showHistory(0, 0); _history->showHistory(0, 0, {});
if (const auto entry = _mainSection->activeChat(); entry.key) { if (const auto entry = _mainSection->activeChat(); entry.key) {
_controller->setActiveChatEntry(entry); _controller->setActiveChatEntry(entry);

View File

@ -142,6 +142,12 @@ struct BackgroundEmojiData {
uint8 colorIndexPlusOne); uint8 colorIndexPlusOne);
}; };
struct ChatPaintHighlight {
float64 opacity = 0.;
float64 collapsion = 0.;
TextSelection range;
};
struct ChatPaintContext { struct ChatPaintContext {
not_null<const ChatStyle*> st; not_null<const ChatStyle*> st;
const BubblePattern *bubblesPattern = nullptr; const BubblePattern *bubblesPattern = nullptr;
@ -149,11 +155,15 @@ struct ChatPaintContext {
QRect viewport; QRect viewport;
QRect clip; QRect clip;
TextSelection selection; TextSelection selection;
ChatPaintHighlight highlight;
QPainterPath *highlightPathCache = nullptr;
mutable QRect highlightInterpolateTo;
crl::time now = 0; crl::time now = 0;
void translate(int x, int y) { void translate(int x, int y) {
viewport.translate(x, y); viewport.translate(x, y);
clip.translate(x, y); clip.translate(x, y);
highlightInterpolateTo.translate(x, y);
} }
void translate(QPoint point) { void translate(QPoint point) {
translate(point.x(), point.y()); translate(point.x(), point.y());
@ -181,6 +191,19 @@ struct ChatPaintContext {
result.selection = selection; result.selection = selection;
return result; return result;
} }
[[nodiscard]] auto computeHighlightCache() const
-> std::optional<Ui::Text::HighlightInfoRequest> {
if (highlight.range.empty() || highlight.collapsion <= 0.) {
return {};
}
return Ui::Text::HighlightInfoRequest{
.range = highlight.range,
.interpolateTo = highlightInterpolateTo,
.interpolateProgress = (1. - highlight.collapsion),
.outPath = highlightPathCache,
};
};
// This is supported only in unwrapped media for now. // This is supported only in unwrapped media for now.
enum class SkipDrawingParts { enum class SkipDrawingParts {

View File

@ -166,6 +166,7 @@ struct SectionShow {
return copy; return copy;
} }
TextWithEntities highlightPart;
Way way = Way::Forward; Way way = Way::Forward;
anim::type animated = anim::type::normal; anim::type animated = anim::type::normal;
anim::activation activation = anim::activation::normal; anim::activation activation = anim::activation::normal;

@ -1 +1 @@
Subproject commit dc8313f6ae6c87572512424818b9e24e7ef2383e Subproject commit 6dc93b53a1c4d237dea0a458d2b02c4171529f18