mirror-chatterino2/src/messages/layouts/MessageLayout.cpp
nerix 1043f9f803
Remove Unnecessary Includes in Headers (#4275)
* refactor: remove unnecessary includes in headers

* fix: formatting

* chore: changelog

* fix: scrollbar

* fix: suggestions and old appbase remains

* fix: suggestion

* fix: missing Qt forward declarations

* fix: another qt include

* fix: includes for precompiled-headers=off

* Add missing `<memory>` includes

* Add missing `#pragma once`

* Fix tests

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
2022-12-31 14:41:01 +00:00

471 lines
14 KiB
C++

#include "messages/layouts/MessageLayout.hpp"
#include "Application.hpp"
#include "debug/Benchmark.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp"
#include "messages/layouts/MessageLayoutElement.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "messages/Selection.hpp"
#include "providers/colors/ColorProvider.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp"
#include "util/DebugCount.hpp"
#include "util/StreamerMode.hpp"
#include <QApplication>
#include <QDebug>
#include <QPainter>
#include <QtGlobal>
#include <QThread>
#define MARGIN_LEFT (int)(8 * this->scale)
#define MARGIN_RIGHT (int)(8 * this->scale)
#define MARGIN_TOP (int)(4 * this->scale)
#define MARGIN_BOTTOM (int)(4 * this->scale)
#define COMPACT_EMOTES_OFFSET 6
namespace chatterino {
namespace {
QColor blendColors(const QColor &base, const QColor &apply)
{
const qreal &alpha = apply.alphaF();
QColor result;
result.setRgbF(base.redF() * (1 - alpha) + apply.redF() * alpha,
base.greenF() * (1 - alpha) + apply.greenF() * alpha,
base.blueF() * (1 - alpha) + apply.blueF() * alpha);
return result;
}
} // namespace
MessageLayout::MessageLayout(MessagePtr message)
: message_(std::move(message))
, container_(std::make_shared<MessageLayoutContainer>())
{
DebugCount::increase("message layout");
}
MessageLayout::~MessageLayout()
{
DebugCount::decrease("message layout");
}
const Message *MessageLayout::getMessage()
{
return this->message_.get();
}
const MessagePtr &MessageLayout::getMessagePtr() const
{
return this->message_;
}
// Height
int MessageLayout::getHeight() const
{
return container_->getHeight();
}
int MessageLayout::getWidth() const
{
return this->container_->getWidth();
}
// Layout
// return true if redraw is required
bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
{
// BenchmarkGuard benchmark("MessageLayout::layout()");
auto app = getApp();
bool layoutRequired = false;
// check if width changed
bool widthChanged = width != this->currentLayoutWidth_;
layoutRequired |= widthChanged;
this->currentLayoutWidth_ = width;
// check if layout state changed
if (this->layoutState_ != app->windows->getGeneration())
{
layoutRequired = true;
this->flags.set(MessageLayoutFlag::RequiresBufferUpdate);
this->layoutState_ = app->windows->getGeneration();
}
// check if work mask changed
layoutRequired |= this->currentWordFlags_ != flags;
this->currentWordFlags_ = flags; // getSettings()->getWordTypeMask();
// check if layout was requested manually
layoutRequired |= this->flags.has(MessageLayoutFlag::RequiresLayout);
this->flags.unset(MessageLayoutFlag::RequiresLayout);
// check if dpi changed
layoutRequired |= this->scale_ != scale;
this->scale_ = scale;
if (!layoutRequired)
{
return false;
}
int oldHeight = this->container_->getHeight();
this->actuallyLayout(width, flags);
if (widthChanged || this->container_->getHeight() != oldHeight)
{
this->deleteBuffer();
}
this->invalidateBuffer();
return true;
}
void MessageLayout::actuallyLayout(int width, MessageElementFlags flags)
{
this->layoutCount_++;
auto messageFlags = this->message_->flags;
if (this->flags.has(MessageLayoutFlag::Expanded) ||
(flags.has(MessageElementFlag::ModeratorTools) &&
!this->message_->flags.has(MessageFlag::Disabled)))
{
messageFlags.unset(MessageFlag::Collapsed);
}
bool hideModerated = getSettings()->hideModerated;
bool hideModerationActions = getSettings()->hideModerationActions;
bool hideSimilar = getSettings()->hideSimilar;
bool hideReplies = !flags.has(MessageElementFlag::RepliedMessage);
this->container_->begin(width, this->scale_, messageFlags);
for (const auto &element : this->message_->elements)
{
if (hideModerated && this->message_->flags.has(MessageFlag::Disabled))
{
continue;
}
if (this->message_->flags.has(MessageFlag::Timeout) ||
this->message_->flags.has(MessageFlag::Untimeout))
{
// This condition has been set up to execute isInStreamerMode() as the last thing
// as it could end up being expensive.
if (hideModerationActions ||
(getSettings()->streamerModeHideModActions &&
isInStreamerMode()))
{
continue;
}
}
if (hideSimilar && this->message_->flags.has(MessageFlag::Similar))
{
continue;
}
if (hideReplies &&
element->getFlags().has(MessageElementFlag::RepliedMessage))
{
continue;
}
element->addToContainer(*this->container_, flags);
}
if (this->height_ != this->container_->getHeight())
{
this->deleteBuffer();
}
this->container_->end();
this->height_ = this->container_->getHeight();
// collapsed state
this->flags.unset(MessageLayoutFlag::Collapsed);
if (this->container_->isCollapsed())
{
this->flags.set(MessageLayoutFlag::Collapsed);
}
}
// Painting
void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
Selection &selection, bool isLastReadMessage,
bool isWindowFocused, bool isMentions)
{
auto app = getApp();
QPixmap *pixmap = this->buffer_.get();
// create new buffer if required
if (!pixmap)
{
#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX)
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
int(container_->getHeight() *
painter.device()->devicePixelRatioF()));
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
#else
pixmap =
new QPixmap(width, std::max(16, this->container_->getHeight()));
#endif
this->buffer_ = std::shared_ptr<QPixmap>(pixmap);
this->bufferValid_ = false;
DebugCount::increase("message drawing buffers");
}
if (!this->bufferValid_ || !selection.isEmpty())
{
this->updateBuffer(pixmap, messageIndex, selection);
}
// draw on buffer
painter.drawPixmap(0, y, *pixmap);
// painter.drawPixmap(0, y, this->container.width,
// this->container.getHeight(), *pixmap);
// draw gif emotes
this->container_->paintAnimatedElements(painter, y);
// draw disabled
if (this->message_->flags.has(MessageFlag::Disabled))
{
painter.fillRect(0, y, pixmap->width(), pixmap->height(),
app->themes->messages.disabled);
// painter.fillRect(0, y, pixmap->width(), pixmap->height(),
// QBrush(QColor(64, 64, 64, 64)));
}
if (this->message_->flags.has(MessageFlag::RecentMessage))
{
painter.fillRect(0, y, pixmap->width(), pixmap->height(),
app->themes->messages.disabled);
}
if (!isMentions &&
(this->message_->flags.has(MessageFlag::RedeemedChannelPointReward) ||
this->message_->flags.has(MessageFlag::RedeemedHighlight)) &&
getSettings()->enableRedeemedHighlight.getValue())
{
painter.fillRect(
0, y, this->scale_ * 4, pixmap->height(),
*ColorProvider::instance().color(ColorType::RedeemedHighlight));
}
// draw selection
if (!selection.isEmpty())
{
this->container_->paintSelection(painter, messageIndex, selection, y);
}
// draw message seperation line
if (getSettings()->separateMessages.getValue())
{
painter.fillRect(0, y, this->container_->getWidth() + 64, 1,
app->themes->splits.messageSeperator);
}
// draw last read message line
if (isLastReadMessage)
{
QColor color;
if (getSettings()->lastMessageColor != QStringLiteral(""))
{
color = QColor(getSettings()->lastMessageColor.getValue());
}
else
{
color =
isWindowFocused
? app->themes->tabs.selected.backgrounds.regular.color()
: app->themes->tabs.selected.backgrounds.unfocused.color();
}
QBrush brush(color, static_cast<Qt::BrushStyle>(
getSettings()->lastMessagePattern.getValue()));
painter.fillRect(0, y + this->container_->getHeight() - 1,
pixmap->width(), 1, brush);
}
this->bufferValid_ = true;
}
void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
Selection & /*selection*/)
{
if (buffer->isNull())
return;
auto app = getApp();
auto settings = getSettings();
QPainter painter(buffer);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
// draw background
QColor backgroundColor = [this, &app] {
if (getSettings()->alternateMessages.getValue() &&
this->flags.has(MessageLayoutFlag::AlternateBackground))
{
return app->themes->messages.backgrounds.alternate;
}
else
{
return app->themes->messages.backgrounds.regular;
}
}();
if (this->message_->flags.has(MessageFlag::ElevatedMessage) &&
getSettings()->enableElevatedMessageHighlight.getValue())
{
backgroundColor = blendColors(backgroundColor,
*ColorProvider::instance().color(
ColorType::ElevatedMessageHighlight));
}
else if (this->message_->flags.has(MessageFlag::FirstMessage) &&
getSettings()->enableFirstMessageHighlight.getValue())
{
backgroundColor = blendColors(
backgroundColor,
*ColorProvider::instance().color(ColorType::FirstMessageHighlight));
}
else if ((this->message_->flags.has(MessageFlag::Highlighted) ||
this->message_->flags.has(MessageFlag::HighlightedWhisper)) &&
!this->flags.has(MessageLayoutFlag::IgnoreHighlights))
{
// Blend highlight color with usual background color
backgroundColor =
blendColors(backgroundColor, *this->message_->highlightColor);
}
else if (this->message_->flags.has(MessageFlag::Subscription) &&
getSettings()->enableSubHighlight)
{
// Blend highlight color with usual background color
backgroundColor = blendColors(
backgroundColor,
*ColorProvider::instance().color(ColorType::Subscription));
}
else if ((this->message_->flags.has(MessageFlag::RedeemedHighlight) ||
this->message_->flags.has(
MessageFlag::RedeemedChannelPointReward)) &&
settings->enableRedeemedHighlight.getValue())
{
// Blend highlight color with usual background color
backgroundColor = blendColors(
backgroundColor,
*ColorProvider::instance().color(ColorType::RedeemedHighlight));
}
else if (this->message_->flags.has(MessageFlag::AutoMod))
{
backgroundColor = QColor("#404040");
}
else if (this->message_->flags.has(MessageFlag::Debug))
{
backgroundColor = QColor("#4A273D");
}
painter.fillRect(buffer->rect(), backgroundColor);
// draw message
this->container_->paintElements(painter);
#ifdef FOURTF
// debug
painter.setPen(QColor(255, 0, 0));
painter.drawRect(buffer->rect().x(), buffer->rect().y(),
buffer->rect().width() - 1, buffer->rect().height() - 1);
QTextOption option;
option.setAlignment(Qt::AlignRight | Qt::AlignTop);
painter.drawText(QRectF(1, 1, this->container_->getWidth() - 3, 1000),
QString::number(this->layoutCount_) + ", " +
QString::number(++this->bufferUpdatedCount_),
option);
#endif
}
void MessageLayout::invalidateBuffer()
{
this->bufferValid_ = false;
}
void MessageLayout::deleteBuffer()
{
if (this->buffer_ != nullptr)
{
DebugCount::decrease("message drawing buffers");
this->buffer_ = nullptr;
}
}
void MessageLayout::deleteCache()
{
this->deleteBuffer();
#ifdef XD
this->container_->clear();
#endif
}
// Elements
// assert(QThread::currentThread() == QApplication::instance()->thread());
// returns nullptr if none was found
// fourtf: this should return a MessageLayoutItem
const MessageLayoutElement *MessageLayout::getElementAt(QPoint point)
{
// go through all words and return the first one that contains the point.
return this->container_->getElementAt(point);
}
int MessageLayout::getLastCharacterIndex() const
{
return this->container_->getLastCharacterIndex();
}
int MessageLayout::getFirstMessageCharacterIndex() const
{
return this->container_->getFirstMessageCharacterIndex();
}
int MessageLayout::getSelectionIndex(QPoint position)
{
return this->container_->getSelectionIndex(position);
}
void MessageLayout::addSelectionText(QString &str, uint32_t from, uint32_t to,
CopyMode copymode)
{
this->container_->addSelectionText(str, from, to, copymode);
}
bool MessageLayout::isReplyable() const
{
if (this->message_->loginName.isEmpty())
{
return false;
}
if (this->message_->flags.hasAny(
{MessageFlag::System, MessageFlag::Subscription,
MessageFlag::Timeout, MessageFlag::Whisper}))
{
return false;
}
return true;
}
} // namespace chatterino