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);
}
float64 InnerWidget::elementHighlightOpacity(
not_null<const HistoryItem*> item) const {
return 0.;
}
bool InnerWidget::elementInSelectionMode() {
return false;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,6 +16,10 @@ namespace Data {
class Session;
} // namespace Data
namespace Ui {
struct ChatPaintHighlight;
} // namespace Ui
namespace HistoryView {
class Element;
@ -29,40 +33,56 @@ public:
ViewForItem viewForItem,
RepaintView repaintView);
void enqueue(not_null<Element*> view);
void highlight(FullMsgId itemId);
void enqueue(not_null<Element*> view, TextSelection part);
void highlight(FullMsgId itemId, TextSelection part);
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;
private:
void checkNextHighlight();
void repaintHighlightedItem(not_null<const Element*> view);
void updateMessage();
class AnimationManager final {
public:
AnimationManager(ElementHighlighter &parent);
[[nodiscard]] bool animating() const;
[[nodiscard]] float64 progress() const;
void start();
[[nodiscard]] Ui::ChatPaintHighlight state() const;
void start(bool withTextPart);
void cancel();
private:
ElementHighlighter &_parent;
Ui::Animations::Simple _simple;
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 ViewForItem _viewForItem;
const RepaintView _repaintView;
FullMsgId _highlightedMessageId;
Highlight _highlighted;
FullMsgId _lastHighlightedMessageId;
std::deque<FullMsgId> _queue;
std::deque<Highlight> _queue;
AnimationManager _animation;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -48,6 +48,10 @@ struct ChosenReaction;
struct ButtonParameters;
} // namespace HistoryView::Reactions
namespace Window {
struct SectionShow;
} // namespace Window
namespace HistoryView {
struct TextState;
@ -227,11 +231,13 @@ public:
[[nodiscard]] bool animatedScrolling() const;
bool isAbovePosition(Data::MessagePosition position) const;
bool isBelowPosition(Data::MessagePosition position) const;
void highlightMessage(FullMsgId itemId);
void highlightMessage(
FullMsgId itemId,
const TextWithEntities &highlightPart);
void showAtPosition(
Data::MessagePosition position,
anim::type animated = anim::type::normal,
const Window::SectionShow &params,
Fn<void(bool found)> done = nullptr);
void refreshViewer();
@ -292,8 +298,6 @@ public:
// ElementDelegate interface.
Context elementContext() override;
bool elementUnderCursor(not_null<const Element*> view) override;
[[nodiscard]] float64 elementHighlightOpacity(
not_null<const HistoryItem*> item) const override;
bool elementInSelectionMode() override;
bool elementIntersectsRange(
not_null<const Element*> view,
@ -430,7 +434,7 @@ private:
Fn<bool()> overrideInitialScroll);
bool showAtPositionNow(
Data::MessagePosition position,
anim::type animated,
const Window::SectionShow &params,
Fn<void(bool found)> done);
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);
}
if (context.highlightPathCache) {
context.highlightInterpolateTo = g;
context.highlightPathCache->clear();
}
if (bubble) {
if (displayFromName()
&& item->displayFrom()
@ -1110,6 +1114,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
- (_bottomInfo.height() - st::msgDateFont->height));
}
auto textSelection = context.selection;
auto highlightRange = context.highlight.range;
const auto mediaHeight = mediaDisplayed ? media->height() : 0;
const auto paintMedia = [&](int top) {
if (!mediaDisplayed) {
@ -1130,6 +1135,10 @@ void Message::draw(Painter &p, const PaintContext &context) const {
context.reactionInfo->effectOffset -= add;
}
}
if (context.highlightPathCache
&& !context.highlightPathCache->isEmpty()) {
context.highlightPathCache->translate(mediaPosition);
}
p.translate(-mediaPosition);
};
if (mediaDisplayed && _invertMedia) {
@ -1141,8 +1150,12 @@ void Message::draw(Painter &p, const PaintContext &context) const {
+ mediaHeight
+ (mediaOnBottom ? 0 : st::mediaInBubbleSkip));
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) {
paintMedia(trect.y() + trect.height() - mediaHeight);
if (context.reactionInfo && !displayInfo && !_reactions) {
@ -1224,6 +1237,20 @@ void Message::draw(Painter &p, const PaintContext &context) const {
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) {
p.restore();
}
@ -1651,6 +1678,7 @@ void Message::paintText(
width());
trect.setY(trect.y() + botTop->height);
}
auto highlightRequest = context.computeHighlightCache();
text().draw(p, {
.position = trect.topLeft(),
.availableWidth = trect.width(),
@ -1663,6 +1691,7 @@ void Message::paintText(
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = context.selection,
.highlight = highlightRequest ? &*highlightRequest : nullptr,
});
}
@ -2732,10 +2761,13 @@ TextWithEntities Message::selectedQuote(
TextSelection Message::selectionFromQuote(
const TextWithEntities &quote) const {
if (quote.empty()) {
return {};
}
const auto item = data();
const auto &translated = item->translatedText();
const auto &original = item->originalText();
if (&translated != &original || quote.empty()) {
if (&translated != &original) {
return {};
} else if (hasVisibleText()) {
const auto media = this->media();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -142,6 +142,12 @@ struct BackgroundEmojiData {
uint8 colorIndexPlusOne);
};
struct ChatPaintHighlight {
float64 opacity = 0.;
float64 collapsion = 0.;
TextSelection range;
};
struct ChatPaintContext {
not_null<const ChatStyle*> st;
const BubblePattern *bubblesPattern = nullptr;
@ -149,11 +155,15 @@ struct ChatPaintContext {
QRect viewport;
QRect clip;
TextSelection selection;
ChatPaintHighlight highlight;
QPainterPath *highlightPathCache = nullptr;
mutable QRect highlightInterpolateTo;
crl::time now = 0;
void translate(int x, int y) {
viewport.translate(x, y);
clip.translate(x, y);
highlightInterpolateTo.translate(x, y);
}
void translate(QPoint point) {
translate(point.x(), point.y());
@ -181,6 +191,19 @@ struct ChatPaintContext {
result.selection = selection;
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.
enum class SkipDrawingParts {

View File

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

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