feat: add Go to message action in various places (#3953)
* feat: add `Go to message` action in search popup * chore: add changelog entry * fix: only scroll if the scrollbar is shown * fix: go to message when view isn't focused * feat: animate highlighted message * fix: missing includes * fix: order of initialization * fix: add `ChannelView::mayContainMessage` to filter messages * feat: add `Go to message` action in `/mentions` * fix: ignore any mentions channel when searching for split * feat: add `Go to message` action in reply-threads * fix: remove redundant `source` parameter * feat: add `Go to message` action in user-cards * feat: add link to deleted message * fix: set current time to 0 when starting animation * chore: update changelog * fix: add default case (unreachable) * chore: removed unused variable * fix: search in mentions * fix: always attempt to focus split * fix: rename `Link::MessageId` to `Link::JumpToMessage` * fix: rename `selectAndScrollToMessage` to `scrollToMessage` * fix: rename internal `scrollToMessage` to `scrollToMessageLayout` * fix: deleted message link in search popup * chore: reword explanation * fix: use for-loop instead of `std::find_if` * refactor: define highlight colors in `BaseTheme` * core: replace `iff` with `if` * fix: only return if the message found * Reword/phrase/dot changelog entries Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
#include "ChannelView.hpp"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QColor>
|
||||
#include <QDate>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QEasingCurve>
|
||||
#include <QGraphicsBlurEffect>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QScreen>
|
||||
#include <QVariantAnimation>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
@@ -118,12 +121,23 @@ namespace {
|
||||
addPageLink("FFZ");
|
||||
}
|
||||
}
|
||||
|
||||
// Current function: https://www.desmos.com/calculator/vdyamchjwh
|
||||
qreal highlightEasingFunction(qreal progress)
|
||||
{
|
||||
if (progress <= 0.1)
|
||||
{
|
||||
return 1.0 - pow(10.0 * progress, 3.0);
|
||||
}
|
||||
return 1.0 + pow((20.0 / 9.0) * (0.5 * progress - 0.5), 3.0);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ChannelView::ChannelView(BaseWidget *parent, Split *split, Context context)
|
||||
: BaseWidget(parent)
|
||||
, split_(split)
|
||||
, scrollBar_(new Scrollbar(this))
|
||||
, highlightAnimation_(this)
|
||||
, context_(context)
|
||||
{
|
||||
this->setMouseTracking(true);
|
||||
@@ -164,6 +178,12 @@ ChannelView::ChannelView(BaseWidget *parent, Split *split, Context context)
|
||||
// of any place where you can, or where it would make sense,
|
||||
// to tab to a ChannelVieChannelView
|
||||
this->setFocusPolicy(Qt::FocusPolicy::ClickFocus);
|
||||
|
||||
this->setupHighlightAnimationColors();
|
||||
this->highlightAnimation_.setDuration(1500);
|
||||
auto curve = QEasingCurve();
|
||||
curve.setCustomType(highlightEasingFunction);
|
||||
this->highlightAnimation_.setEasingCurve(curve);
|
||||
}
|
||||
|
||||
void ChannelView::initializeLayout()
|
||||
@@ -339,9 +359,18 @@ void ChannelView::themeChangedEvent()
|
||||
{
|
||||
BaseWidget::themeChangedEvent();
|
||||
|
||||
this->setupHighlightAnimationColors();
|
||||
this->queueLayout();
|
||||
}
|
||||
|
||||
void ChannelView::setupHighlightAnimationColors()
|
||||
{
|
||||
this->highlightAnimation_.setStartValue(
|
||||
this->theme->messages.highlightAnimationStart);
|
||||
this->highlightAnimation_.setEndValue(
|
||||
this->theme->messages.highlightAnimationEnd);
|
||||
}
|
||||
|
||||
void ChannelView::scaleChangedEvent(float scale)
|
||||
{
|
||||
BaseWidget::scaleChangedEvent(scale);
|
||||
@@ -392,7 +421,8 @@ void ChannelView::performLayout(bool causedByScrollbar)
|
||||
auto &messages = this->getMessagesSnapshot();
|
||||
|
||||
this->showingLatestMessages_ =
|
||||
this->scrollBar_->isAtBottom() || !this->scrollBar_->isVisible();
|
||||
this->scrollBar_->isAtBottom() ||
|
||||
(!this->scrollBar_->isVisible() && !causedByScrollbar);
|
||||
|
||||
/// Layout visible messages
|
||||
this->layoutVisibleMessages(messages);
|
||||
@@ -475,6 +505,7 @@ void ChannelView::updateScrollbar(
|
||||
{
|
||||
this->scrollBar_->setDesiredValue(0);
|
||||
}
|
||||
this->showScrollBar_ = showScrollbar;
|
||||
|
||||
this->scrollBar_->setMaximum(messages.size());
|
||||
|
||||
@@ -1088,6 +1119,86 @@ MessageElementFlags ChannelView::getFlags() const
|
||||
return flags;
|
||||
}
|
||||
|
||||
bool ChannelView::scrollToMessage(const MessagePtr &message)
|
||||
{
|
||||
if (!this->mayContainMessage(message))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &messagesSnapshot = this->getMessagesSnapshot();
|
||||
if (messagesSnapshot.size() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Figure out if we can somehow binary-search here.
|
||||
// Currently, a message only sometimes stores a QDateTime,
|
||||
// but always a QTime (inaccurate on midnight).
|
||||
//
|
||||
// We're searching from the bottom since it's more likely for a user
|
||||
// wanting to go to a message that recently scrolled out of view.
|
||||
size_t messageIdx = messagesSnapshot.size() - 1;
|
||||
for (; messageIdx < SIZE_MAX; messageIdx--)
|
||||
{
|
||||
if (messagesSnapshot[messageIdx]->getMessagePtr() == message)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (messageIdx == SIZE_MAX)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->scrollToMessageLayout(messagesSnapshot[messageIdx].get(), messageIdx);
|
||||
getApp()->windows->select(this->split_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChannelView::scrollToMessageId(const QString &messageId)
|
||||
{
|
||||
auto &messagesSnapshot = this->getMessagesSnapshot();
|
||||
if (messagesSnapshot.size() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// We're searching from the bottom since it's more likely for a user
|
||||
// wanting to go to a message that recently scrolled out of view.
|
||||
size_t messageIdx = messagesSnapshot.size() - 1;
|
||||
for (; messageIdx < SIZE_MAX; messageIdx--)
|
||||
{
|
||||
if (messagesSnapshot[messageIdx]->getMessagePtr()->id == messageId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (messageIdx == SIZE_MAX)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->scrollToMessageLayout(messagesSnapshot[messageIdx].get(), messageIdx);
|
||||
getApp()->windows->select(this->split_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChannelView::scrollToMessageLayout(MessageLayout *layout,
|
||||
size_t messageIdx)
|
||||
{
|
||||
this->highlightedMessage_ = layout;
|
||||
this->highlightAnimation_.setCurrentTime(0);
|
||||
this->highlightAnimation_.start(QAbstractAnimation::KeepWhenStopped);
|
||||
|
||||
if (this->showScrollBar_)
|
||||
{
|
||||
this->getScrollBar().setDesiredValue(messageIdx);
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelView::paintEvent(QPaintEvent * /*event*/)
|
||||
{
|
||||
// BenchmarkGuard benchmark("paint");
|
||||
@@ -1144,6 +1255,17 @@ void ChannelView::drawMessages(QPainter &painter)
|
||||
layout->paint(painter, DRAW_WIDTH, y, i, this->selection_,
|
||||
isLastMessage, windowFocused, isMentions);
|
||||
|
||||
if (this->highlightedMessage_ == layout)
|
||||
{
|
||||
painter.fillRect(
|
||||
0, y, layout->getWidth(), layout->getHeight(),
|
||||
this->highlightAnimation_.currentValue().value<QColor>());
|
||||
if (this->highlightAnimation_.state() == QVariantAnimation::Stopped)
|
||||
{
|
||||
this->highlightedMessage_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
y += layout->getHeight();
|
||||
|
||||
end = layout;
|
||||
@@ -2070,6 +2192,46 @@ void ChannelView::addMessageContextMenuItems(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool isSearch = this->context_ == Context::Search;
|
||||
bool isReplyOrUserCard = (this->context_ == Context::ReplyThread ||
|
||||
this->context_ == Context::UserCard) &&
|
||||
this->split_;
|
||||
bool isMentions =
|
||||
this->channel()->getType() == Channel::Type::TwitchMentions;
|
||||
if (isSearch || isMentions || isReplyOrUserCard)
|
||||
{
|
||||
const auto &messagePtr = layout->getMessagePtr();
|
||||
menu.addAction("Go to message", [this, &messagePtr, isSearch,
|
||||
isMentions, isReplyOrUserCard] {
|
||||
if (isSearch)
|
||||
{
|
||||
if (const auto &search =
|
||||
dynamic_cast<SearchPopup *>(this->parentWidget()))
|
||||
{
|
||||
search->goToMessage(messagePtr);
|
||||
}
|
||||
}
|
||||
else if (isMentions)
|
||||
{
|
||||
getApp()->windows->scrollToMessage(messagePtr);
|
||||
}
|
||||
else if (isReplyOrUserCard)
|
||||
{
|
||||
// If the thread is in the mentions channel,
|
||||
// we need to find the original split.
|
||||
if (this->split_->getChannel()->getType() ==
|
||||
Channel::Type::TwitchMentions)
|
||||
{
|
||||
getApp()->windows->scrollToMessage(messagePtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->split_->getChannelView().scrollToMessage(messagePtr);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelView::addTwitchLinkContextMenuItems(
|
||||
@@ -2321,6 +2483,30 @@ void ChannelView::showUserInfoPopup(const QString &userName,
|
||||
userPopup->show();
|
||||
}
|
||||
|
||||
bool ChannelView::mayContainMessage(const MessagePtr &message)
|
||||
{
|
||||
switch (this->channel()->getType())
|
||||
{
|
||||
case Channel::Type::Direct:
|
||||
case Channel::Type::Twitch:
|
||||
case Channel::Type::TwitchWatching:
|
||||
case Channel::Type::Irc:
|
||||
return this->channel()->getName() == message->channelName;
|
||||
case Channel::Type::TwitchWhispers:
|
||||
return message->flags.has(MessageFlag::Whisper);
|
||||
case Channel::Type::TwitchMentions:
|
||||
return message->flags.has(MessageFlag::Highlighted);
|
||||
case Channel::Type::TwitchLive:
|
||||
return message->flags.has(MessageFlag::System);
|
||||
case Channel::Type::TwitchEnd: // TODO: not used?
|
||||
case Channel::Type::None: // Unspecific
|
||||
case Channel::Type::Misc: // Unspecific
|
||||
return true;
|
||||
default:
|
||||
return true; // unreachable
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
|
||||
MessageLayout *layout)
|
||||
{
|
||||
@@ -2442,6 +2628,21 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
|
||||
this->showReplyThreadPopup(layout->getMessagePtr());
|
||||
}
|
||||
break;
|
||||
case Link::JumpToMessage: {
|
||||
if (this->context_ == Context::Search)
|
||||
{
|
||||
if (auto search =
|
||||
dynamic_cast<SearchPopup *>(this->parentWidget()))
|
||||
{
|
||||
search->goToMessageId(link.value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this->scrollToMessageId(link.value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user