mirror-chatterino2/src/messages/layouts/messagelayout.cpp

306 lines
8.8 KiB
C++
Raw Normal View History

#include "messages/layouts/messagelayout.hpp"
#include "application.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/settingsmanager.hpp"
#include "util/benchmark.hpp"
#include <QApplication>
#include <QDebug>
#include <QPainter>
#include <QThread>
#include <QtGlobal>
#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 messages {
namespace layouts {
2018-05-25 13:02:14 +02:00
MessageLayout::MessageLayout(MessagePtr message)
: m_message(message)
, m_buffer(nullptr)
{
2018-04-06 16:37:30 +02:00
util::DebugCount::increase("message layout");
}
MessageLayout::~MessageLayout()
{
util::DebugCount::decrease("message layout");
}
Message *MessageLayout::getMessage()
{
2018-05-25 13:02:14 +02:00
return this->m_message.get();
}
// Height
int MessageLayout::getHeight() const
{
2018-05-25 13:02:14 +02:00
return m_container.getHeight();
}
// Layout
// return true if redraw is required
2018-01-17 16:52:51 +01:00
bool MessageLayout::layout(int width, float scale, MessageElement::Flags flags)
{
// BenchmarkGuard benchmark("MessageLayout::layout()");
auto app = getApp();
bool layoutRequired = false;
// check if width changed
2018-05-25 13:02:14 +02:00
bool widthChanged = width != this->m_currentLayoutWidth;
layoutRequired |= widthChanged;
2018-05-25 13:02:14 +02:00
this->m_currentLayoutWidth = width;
// check if emotes changed
2018-05-25 13:02:14 +02:00
bool imagesChanged = this->m_emoteGeneration != app->emotes->getGeneration();
layoutRequired |= imagesChanged;
2018-05-25 13:02:14 +02:00
this->m_emoteGeneration = app->emotes->getGeneration();
// check if text changed
2018-05-25 13:02:14 +02:00
bool textChanged = this->m_fontGeneration != app->fonts->getGeneration();
layoutRequired |= textChanged;
2018-05-25 13:02:14 +02:00
this->m_fontGeneration = app->fonts->getGeneration();
// check if work mask changed
2018-05-25 13:02:14 +02:00
bool wordMaskChanged = this->m_currentWordFlags != flags; // app->settings->getWordTypeMask();
layoutRequired |= wordMaskChanged;
2018-05-25 13:02:14 +02:00
this->m_currentWordFlags = flags; // app->settings->getWordTypeMask();
// check if timestamp format changed
2018-05-25 13:02:14 +02:00
bool timestampFormatChanged = this->m_timestampFormat != app->settings->timestampFormat;
this->m_timestampFormat = app->settings->timestampFormat.getValue();
layoutRequired |= timestampFormatChanged;
2018-05-25 12:45:18 +02:00
// check if layout was requested manually
layoutRequired |= bool(this->flags & RequiresLayout);
this->flags &= ~RequiresLayout;
// check if dpi changed
2018-05-25 13:02:14 +02:00
bool scaleChanged = this->m_scale != scale;
layoutRequired |= scaleChanged;
2018-05-25 13:02:14 +02:00
this->m_scale = scale;
imagesChanged |= scaleChanged;
textChanged |= scaleChanged;
2018-05-25 12:45:18 +02:00
// assert(layoutRequired);
2018-05-24 23:12:50 +02:00
// update word sizes if needed
if (imagesChanged) {
// this->container.updateImages();
2018-04-18 09:12:29 +02:00
this->flags |= MessageLayout::RequiresBufferUpdate;
}
if (textChanged) {
// this->container.updateText();
2018-04-18 09:12:29 +02:00
this->flags |= MessageLayout::RequiresBufferUpdate;
}
if (widthChanged || wordMaskChanged) {
this->deleteBuffer();
}
// return if no layout is required
2018-05-24 23:12:50 +02:00
if (!layoutRequired) {
return false;
}
2018-01-17 16:52:51 +01:00
this->actuallyLayout(width, flags);
this->invalidateBuffer();
return true;
}
void MessageLayout::actuallyLayout(int width, MessageElement::Flags _flags)
{
2018-05-25 13:02:14 +02:00
auto messageFlags = this->m_message->flags.value;
if (this->flags & MessageLayout::Expanded ||
(_flags & MessageElement::ModeratorTools &&
2018-05-25 13:02:14 +02:00
!(this->m_message->flags & Message::MessageFlags::Disabled))) {
2018-05-25 12:45:18 +02:00
messageFlags = Message::MessageFlags(messageFlags & ~Message::MessageFlags::Collapsed);
}
2018-05-25 13:02:14 +02:00
this->m_container.begin(width, this->m_scale, messageFlags);
2018-05-25 13:02:14 +02:00
for (const std::unique_ptr<MessageElement> &element : this->m_message->getElements()) {
element->addToContainer(this->m_container, _flags);
}
2018-05-25 13:02:14 +02:00
if (this->m_height != this->m_container.getHeight()) {
this->deleteBuffer();
}
2018-05-25 13:02:14 +02:00
this->m_container.end();
this->m_height = this->m_container.getHeight();
// collapsed state
this->flags &= ~Flags::Collapsed;
2018-05-25 13:02:14 +02:00
if (this->m_container.isCollapsed()) {
this->flags |= Flags::Collapsed;
}
}
// Painting
2018-01-23 22:48:33 +01:00
void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection &selection,
bool isLastReadMessage, bool isWindowFocused)
{
auto app = getApp();
2018-05-25 13:02:14 +02:00
QPixmap *pixmap = this->m_buffer.get();
// create new buffer if required
if (!pixmap) {
#ifdef Q_OS_MACOS
pixmap =
2018-05-25 16:48:35 +02:00
new QPixmap(int(this->m_container.getWidth() * painter.device()->devicePixelRatioF()),
int(this->m_container.getHeight() * painter.device()->devicePixelRatioF()));
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
#else
2018-05-25 13:02:14 +02:00
pixmap =
new QPixmap(this->m_container.getWidth(), std::max(16, this->m_container.getHeight()));
#endif
2018-05-25 13:02:14 +02:00
this->m_buffer = std::shared_ptr<QPixmap>(pixmap);
this->m_bufferValid = false;
util::DebugCount::increase("message drawing buffers");
}
2018-05-25 13:02:14 +02:00
if (!this->m_bufferValid || !selection.isEmpty()) {
this->updateBuffer(pixmap, messageIndex, selection);
}
// draw on buffer
2018-01-23 22:51:15 +01:00
painter.drawPixmap(0, y, *pixmap);
// painter.drawPixmap(0, y, this->container.width, this->container.getHeight(), *pixmap);
// draw gif emotes
2018-05-25 13:02:14 +02:00
this->m_container.paintAnimatedElements(painter, y);
// draw disabled
2018-05-25 13:02:14 +02:00
if (this->m_message->flags.HasFlag(Message::Disabled)) {
painter.fillRect(0, y, pixmap->width(), pixmap->height(), app->themes->messages.disabled);
}
2018-04-10 03:29:00 +02:00
// draw selection
if (!selection.isEmpty()) {
2018-05-25 13:02:14 +02:00
this->m_container.paintSelection(painter, messageIndex, selection, y);
2018-04-10 03:29:00 +02:00
}
// draw message seperation line
if (app->settings->seperateMessages.getValue()) {
2018-05-25 16:20:39 +02:00
painter.fillRect(0, y, this->m_container.getWidth(), 1,
app->themes->splits.messageSeperator);
}
2018-01-23 22:48:33 +01:00
// draw last read message line
if (isLastReadMessage) {
QColor color = isWindowFocused ? app->themes->tabs.selected.backgrounds.regular.color()
: app->themes->tabs.selected.backgrounds.unfocused.color();
2018-01-23 22:48:33 +01:00
2018-03-24 16:55:28 +01:00
QBrush brush(color, Qt::VerPattern);
2018-01-23 22:48:33 +01:00
2018-05-25 13:02:14 +02:00
painter.fillRect(0, y + this->m_container.getHeight() - 1, this->m_container.getWidth(), 1,
2018-01-28 16:29:47 +01:00
brush);
2018-01-23 22:48:33 +01:00
}
2018-05-25 13:02:14 +02:00
this->m_bufferValid = true;
}
2018-05-25 12:45:18 +02:00
void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, Selection & /*selection*/)
{
auto app = getApp();
QPainter painter(buffer);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
// draw background
QColor backgroundColor;
2018-05-25 13:02:14 +02:00
if (this->m_message->flags & Message::Highlighted) {
backgroundColor = app->themes->messages.backgrounds.highlighted;
} else if (app->settings->alternateMessageBackground.getValue() &&
this->flags & MessageLayout::AlternateBackground) {
backgroundColor = app->themes->messages.backgrounds.alternate;
} else {
backgroundColor = app->themes->messages.backgrounds.regular;
}
painter.fillRect(buffer->rect(), backgroundColor);
// draw message
2018-05-25 13:02:14 +02:00
this->m_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);
2018-04-10 02:42:41 +02:00
painter.drawText(QRectF(1, 1, this->container.getWidth() - 3, 1000),
QString::number(++this->bufferUpdatedCount), option);
2018-01-11 20:26:32 +01:00
#endif
}
void MessageLayout::invalidateBuffer()
{
2018-05-25 13:02:14 +02:00
this->m_bufferValid = false;
}
void MessageLayout::deleteBuffer()
{
2018-05-25 13:02:14 +02:00
if (this->m_buffer != nullptr) {
util::DebugCount::decrease("message drawing buffers");
2018-05-25 13:02:14 +02:00
this->m_buffer = nullptr;
}
}
2018-04-18 09:12:29 +02:00
void MessageLayout::deleteCache()
{
this->deleteBuffer();
#ifdef XD
2018-05-25 13:02:14 +02:00
this->m_container.clear();
2018-04-18 09:12:29 +02:00
#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.
2018-05-25 13:02:14 +02:00
return this->m_container.getElementAt(point);
}
int MessageLayout::getLastCharacterIndex() const
{
2018-05-25 13:02:14 +02:00
return this->m_container.getLastCharacterIndex();
}
int MessageLayout::getSelectionIndex(QPoint position)
{
2018-05-25 13:02:14 +02:00
return this->m_container.getSelectionIndex(position);
}
2018-01-16 02:39:31 +01:00
void MessageLayout::addSelectionText(QString &str, int from, int to)
{
2018-05-25 13:02:14 +02:00
this->m_container.addSelectionText(str, from, to);
2018-01-16 02:39:31 +01:00
}
2018-04-01 16:43:30 +02:00
} // namespace layouts
} // namespace messages
} // namespace chatterino