I BROKE EVERYTHING

refactored the rendering process
This commit is contained in:
fourtf
2018-01-11 20:16:25 +01:00
parent c240d6f7c2
commit 10850c0ec7
62 changed files with 2155 additions and 2117 deletions
+345
View File
@@ -0,0 +1,345 @@
#include "messages/layouts/messagelayout.hpp"
#include "singletons/emotemanager.hpp"
#include "singletons/settingsmanager.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 {
MessageLayout::MessageLayout(MessagePtr _message)
: message(_message)
, buffer(nullptr)
{
if (_message->hasFlags(Message::Collapsed)) {
this->addFlags(MessageLayout::Collapsed);
}
}
Message *MessageLayout::getMessage()
{
return this->message.get();
}
// Height
int MessageLayout::getHeight() const
{
return container.getHeight();
}
// Flags
MessageLayout::Flags MessageLayout::getFlags() const
{
return this->flags;
}
bool MessageLayout::hasFlags(Flags _flags) const
{
return this->flags & _flags;
}
void MessageLayout::addFlags(Flags _flags)
{
this->flags = (Flags)((MessageLayoutFlagsType)this->flags | (MessageLayoutFlagsType)_flags);
}
void MessageLayout::removeFlags(Flags _flags)
{
this->flags = (Flags)((MessageLayoutFlagsType)this->flags & ~((MessageLayoutFlagsType)_flags));
}
// Layout
// return true if redraw is required
bool MessageLayout::layout(int width, float scale)
{
auto &emoteManager = singletons::EmoteManager::getInstance();
bool layoutRequired = false;
// check if width changed
bool widthChanged = width != this->currentLayoutWidth;
layoutRequired |= widthChanged;
this->currentLayoutWidth = width;
// check if emotes changed
bool imagesChanged = this->emoteGeneration != emoteManager.getGeneration();
layoutRequired |= imagesChanged;
this->emoteGeneration = emoteManager.getGeneration();
// check if text changed
bool textChanged =
this->fontGeneration != singletons::FontManager::getInstance().getGeneration();
layoutRequired |= textChanged;
this->fontGeneration = singletons::FontManager::getInstance().getGeneration();
// check if work mask changed
bool wordMaskChanged =
this->currentWordTypes != singletons::SettingManager::getInstance().getWordTypeMask();
layoutRequired |= wordMaskChanged;
this->currentWordTypes = singletons::SettingManager::getInstance().getWordTypeMask();
// check if dpi changed
bool scaleChanged = this->scale != scale;
layoutRequired |= scaleChanged;
this->scale = scale;
imagesChanged |= scaleChanged;
textChanged |= scaleChanged;
// update word sizes if needed
if (imagesChanged) {
// fourtf: update images
this->addFlags(MessageLayout::RequiresBufferUpdate);
}
if (textChanged) {
// fourtf: update text
this->addFlags(MessageLayout::RequiresBufferUpdate);
}
if (widthChanged || wordMaskChanged) {
this->deleteBuffer();
}
// return if no layout is required
if (!layoutRequired) {
return false;
}
this->actuallyLayout(width);
this->invalidateBuffer();
return true;
}
void MessageLayout::actuallyLayout(int width)
{
this->container.clear();
this->container.width = width;
this->container.scale = this->scale;
for (const std::unique_ptr<MessageElement> &element : this->message->getElements()) {
element->addToContainer(this->container, MessageElement::Default);
}
if (this->height != this->container.getHeight()) {
this->deleteBuffer();
}
this->container.finish();
this->height = this->container.getHeight();
}
// Painting
void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection &selection)
{
QPixmap *pixmap = this->buffer.get();
// create new buffer if required
if (!pixmap) {
#ifdef Q_OS_MACOS
pixmap =
new QPixmap((int)(this->container.getWidth() * painter.device()->devicePixelRatioF()),
(int)(this->container.getHeight() * painter.device()->devicePixelRatioF()));
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
#else
pixmap = new QPixmap(this->container.width, std::max(16, this->container.getHeight()));
#endif
this->buffer = std::shared_ptr<QPixmap>(pixmap);
this->bufferValid = false;
}
if (!this->bufferValid) {
this->updateBuffer(pixmap, messageIndex, selection);
}
// draw on buffer
painter.drawPixmap(0, y, pixmap->width(), pixmap->height(), *pixmap);
this->bufferValid = true;
}
void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &selection)
{
singletons::ThemeManager &themeManager = singletons::ThemeManager::getInstance();
QPainter painter(buffer);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
// draw background
painter.fillRect(buffer->rect(),
this->message->hasFlags(Message::Highlighted)
? themeManager.messages.backgrounds.highlighted
: themeManager.messages.backgrounds.regular);
// draw selection
if (!selection.isEmpty()) {
this->container.paintSelection(painter, messageIndex, selection);
}
// draw message
this->container.paintElements(painter);
// 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.width - 3, 1000),
QString::number(++this->bufferUpdatedCount), option);
}
void MessageLayout::invalidateBuffer()
{
this->bufferValid = false;
}
void MessageLayout::deleteBuffer()
{
this->buffer = nullptr;
}
// 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);
}
// XXX(pajlada): This is probably not the optimal way to calculate this
int MessageLayout::getLastCharacterIndex() const
{
// fourtf: xD
// // find out in which line the cursor is
// int lineNumber = 0, lineStart = 0, lineEnd = 0;
// for (size_t i = 0; i < this->wordParts.size(); i++) {
// const LayoutItem &part = this->wordParts[i];
// if (part.getLineNumber() != lineNumber) {
// lineStart = i;
// lineNumber = part.getLineNumber();
// }
// lineEnd = i + 1;
// }
// // count up to the cursor
// int index = 0;
// for (int i = 0; i < lineStart; i++) {
// const LayoutItem &part = this->wordParts[i];
// index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
// }
// for (int i = lineStart; i < lineEnd; i++) {
// const LayoutItem &part = this->wordParts[i];
// index += part.getCharacterLength();
// }
// return index;
return 0;
}
int MessageLayout::getSelectionIndex(QPoint position)
{
// if (this->wordParts.size() == 0) {
// return 0;
// }
// // find out in which line the cursor is
// int lineNumber = 0, lineStart = 0, lineEnd = 0;
// for (size_t i = 0; i < this->wordParts.size(); i++) {
// LayoutItem &part = this->wordParts[i];
// if (part.getLineNumber() != 0 && position.y() < part.getY()) {
// break;
// }
// if (part.getLineNumber() != lineNumber) {
// lineStart = i;
// lineNumber = part.getLineNumber();
// }
// lineEnd = i + 1;
// }
// // count up to the cursor
// int index = 0;
// for (int i = 0; i < lineStart; i++) {
// LayoutItem &part = this->wordParts[i];
// index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
// }
// for (int i = lineStart; i < lineEnd; i++) {
// LayoutItem &part = this->wordParts[i];
// // curser is left of the word part
// if (position.x() < part.getX()) {
// break;
// }
// // cursor is right of the word part
// if (position.x() > part.getX() + part.getWidth()) {
// index += part.getCharacterLength();
// continue;
// }
// // cursor is over the word part
// if (part.getWord().isImage()) {
// if (position.x() - part.getX() > part.getWidth() / 2) {
// index++;
// }
// } else {
// // TODO: use word.getCharacterWidthCache();
// auto text = part.getText();
// int x = part.getX();
// for (int j = 0; j < text.length(); j++) {
// if (x > position.x()) {
// break;
// }
// index++;
// x = part.getX() + part.getWord().getFontMetrics(this->scale).width(text, j +
// 1);
// }
// }
// break;
// }
// return index;
return 0;
}
} // namespace layouts
} // namespace messages
} // namespace chatterino
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include "messages/layouts/messagelayoutcontainer.hpp"
#include "messages/layouts/messagelayoutelement.hpp"
#include "messages/message.hpp"
#include "messages/selection.hpp"
#include <QPixmap>
#include <boost/noncopyable.hpp>
#include <cinttypes>
#include <memory>
namespace chatterino {
namespace messages {
namespace layouts {
class MessageLayout;
typedef std::shared_ptr<MessageLayout> MessageLayoutPtr;
typedef uint8_t MessageLayoutFlagsType;
class MessageLayout : boost::noncopyable
{
public:
enum Flags : MessageLayoutFlagsType { Collapsed, RequiresBufferUpdate, RequiresLayout };
MessageLayout(MessagePtr message);
Message *getMessage();
// Height
int getHeight() const;
// Flags
Flags getFlags() const;
bool hasFlags(Flags flags) const;
void addFlags(Flags flags);
void removeFlags(Flags flags);
// Layout
bool layout(int width, float scale);
// Painting
void paint(QPainter &painter, int y, int messageIndex, Selection &selection);
void invalidateBuffer();
void deleteBuffer();
// Elements
const MessageLayoutElement *getElementAt(QPoint point);
int getLastCharacterIndex() const;
int getSelectionIndex(QPoint position);
// Misc
bool isDisabled() const;
private:
// variables
MessagePtr message;
MessageLayoutContainer container;
std::shared_ptr<QPixmap> buffer = nullptr;
bool bufferValid = false;
Flags flags;
int height = 0;
int currentLayoutWidth = -1;
int fontGeneration = -1;
int emoteGeneration = -1;
float scale = -1;
unsigned int bufferUpdatedCount = 0;
MessageElement::Flags currentWordTypes = MessageElement::None;
int collapsedHeight = 32;
// methods
void actuallyLayout(int width);
void updateBuffer(QPixmap *pixmap, int messageIndex, Selection &selection);
};
} // namespace layouts
} // namespace messages
} // namespace chatterino
@@ -0,0 +1,157 @@
#include "messagelayoutcontainer.hpp"
#include "messagelayoutelement.hpp"
#include "messages/selection.hpp"
#include "singletons/settingsmanager.hpp"
#include <QPainter>
#define COMPACT_EMOTES_OFFSET 6
namespace chatterino {
namespace messages {
namespace layouts {
MessageLayoutContainer::MessageLayoutContainer()
: scale(1)
, margin(4, 8, 4, 8)
, centered(false)
{
this->clear();
}
int MessageLayoutContainer::getHeight() const
{
return this->height;
}
// methods
void MessageLayoutContainer::clear()
{
this->elements.clear();
this->height = 0;
this->line = 0;
this->currentX = 0;
this->currentY = 0;
this->lineStart = 0;
this->lineHeight = 0;
}
void MessageLayoutContainer::addElement(MessageLayoutElement *element)
{
if (!this->fitsInLine(element->getRect().width())) {
this->breakLine();
}
this->_addElement(element);
}
void MessageLayoutContainer::addElementNoLineBreak(MessageLayoutElement *element)
{
this->_addElement(element);
}
void MessageLayoutContainer::_addElement(MessageLayoutElement *element)
{
if (this->elements.size() == 0) {
this->currentY = this->margin.top * this->scale;
}
int newLineHeight = element->getRect().height();
// fourtf: xD
// bool compactEmotes = true;
// if (compactEmotes && element->word.isImage() && word.getFlags() &
// MessageElement::EmoteImages) {
// newLineHeight -= COMPACT_EMOTES_OFFSET * this->scale;
// }
this->lineHeight = std::max(this->lineHeight, newLineHeight);
element->setPosition(QPoint(this->currentX, this->currentY - element->getRect().height()));
this->elements.push_back(std::unique_ptr<MessageLayoutElement>(element));
this->currentX += element->getRect().width();
if (element->hasTrailingSpace()) {
this->currentX += this->spaceWidth;
}
}
void MessageLayoutContainer::breakLine()
{
int xOffset = 0;
if (this->centered && this->elements.size() > 0) {
xOffset = (width - this->elements.at(this->elements.size() - 1)->getRect().right()) / 2;
}
for (size_t i = lineStart; i < this->elements.size(); i++) {
MessageLayoutElement *element = this->elements.at(i).get();
bool isCompactEmote = false;
// fourtf: xD
// this->enableCompactEmotes && element->getWord().isImage() &&
// element->getWord().getFlags() &
// MessageElement::EmoteImages;
int yExtra = 0;
if (isCompactEmote) {
yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale;
}
element->setPosition(QPoint(element->getRect().x() + xOffset + this->margin.left,
element->getRect().y() + this->lineHeight + yExtra));
}
this->lineStart = this->elements.size();
this->currentX = 0;
this->currentY += this->lineHeight;
this->height = this->currentY + (this->margin.bottom * this->scale);
this->lineHeight = 0;
}
bool MessageLayoutContainer::atStartOfLine()
{
return this->lineStart == this->elements.size();
}
bool MessageLayoutContainer::fitsInLine(int _width)
{
return this->currentX + _width <= this->width - this->margin.left - this->margin.right;
}
void MessageLayoutContainer::finish()
{
if (!this->atStartOfLine()) {
this->breakLine();
}
}
MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point)
{
for (std::unique_ptr<MessageLayoutElement> &element : this->elements) {
if (element->getRect().contains(point)) {
return element.get();
}
}
return nullptr;
}
// painting
void MessageLayoutContainer::paintElements(QPainter &painter)
{
for (const std::unique_ptr<MessageLayoutElement> &element : this->elements) {
element->paint(painter);
}
}
void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
Selection &selection)
{
}
} // namespace layouts
}
}
@@ -0,0 +1,84 @@
#pragma once
#include <memory>
#include <vector>
#include <QPoint>
class QPainter;
namespace chatterino {
namespace messages {
class Selection;
namespace layouts {
class MessageLayoutElement;
struct Margin {
int top;
int right;
int bottom;
int left;
Margin()
: Margin(0)
{
}
Margin(int value)
: Margin(value, value, value, value)
{
}
Margin(int _top, int _right, int _bottom, int _left)
: top(_top)
, right(_right)
, bottom(_bottom)
, left(_left)
{
}
};
class MessageLayoutContainer
{
public:
MessageLayoutContainer();
float scale;
Margin margin;
bool centered;
bool enableCompactEmotes;
int width;
int getHeight() const;
// methods
void clear();
void addElement(MessageLayoutElement *element);
void addElementNoLineBreak(MessageLayoutElement *element);
void breakLine();
bool atStartOfLine();
bool fitsInLine(int width);
void finish();
MessageLayoutElement *getElementAt(QPoint point);
// painting
void paintElements(QPainter &painter);
void paintSelection(QPainter &painter, int messageIndex, Selection &selection);
private:
// helpers
void _addElement(MessageLayoutElement *element);
// variables
int line;
int height;
int currentX, currentY;
size_t lineStart = 0;
int lineHeight = 0;
int spaceWidth = 4;
std::vector<std::unique_ptr<MessageLayoutElement>> elements;
};
} // namespace layouts
}
}
@@ -0,0 +1,132 @@
#include "messages/layouts/messagelayoutelement.hpp"
#include "messages/messageelement.hpp"
#include <QPainter>
namespace chatterino {
namespace messages {
namespace layouts {
const QRect &MessageLayoutElement::getRect() const
{
return this->rect;
}
MessageLayoutElement::MessageLayoutElement(MessageElement &_creator, const QSize &size)
: creator(_creator)
{
this->rect.setSize(size);
}
MessageElement &MessageLayoutElement::getCreator() const
{
return this->creator;
}
void MessageLayoutElement::setPosition(QPoint point)
{
this->rect.moveTopLeft(point);
}
bool MessageLayoutElement::hasTrailingSpace() const
{
return this->trailingSpace;
}
MessageLayoutElement *MessageLayoutElement::setTrailingSpace(bool value)
{
this->trailingSpace = value;
return this;
}
//
// IMAGE
//
ImageLayoutElement::ImageLayoutElement(MessageElement &_creator, Image &_image, QSize _size)
: MessageLayoutElement(_creator, _size)
, image(_image)
{
this->trailingSpace = _creator.hasTrailingSpace();
}
void ImageLayoutElement::addCopyTextToString(QString &str, int from, int to) const
{
str += "<image>";
}
int ImageLayoutElement::getSelectionIndexCount()
{
return this->trailingSpace ? 2 : 1;
}
void ImageLayoutElement::paint(QPainter &painter)
{
const QPixmap *pixmap = this->image.getPixmap();
if (pixmap != nullptr && !this->image.isAnimated()) {
// fourtf: make it use qreal values
painter.drawPixmap(QRectF(this->getRect()), *pixmap, QRectF());
}
}
void ImageLayoutElement::paintAnimated(QPainter &painter)
{
if (this->image.isAnimated()) {
if (this->image.getPixmap() != nullptr) {
// fourtf: make it use qreal values
painter.drawPixmap(QRectF(this->getRect()), *this->image.getPixmap(), QRectF());
}
}
}
int ImageLayoutElement::getMouseOverIndex(const QPoint &abs)
{
return 0;
}
//
// TEXT
//
TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text, QSize _size,
QColor _color, FontStyle _style, float _scale)
: MessageLayoutElement(_creator, _size)
, text(_text)
, color(_color)
, style(_style)
, scale(_scale)
{
}
void TextLayoutElement::addCopyTextToString(QString &str, int from, int to) const
{
}
int TextLayoutElement::getSelectionIndexCount()
{
return this->text.length() + (this->trailingSpace ? 1 : 0);
}
void TextLayoutElement::paint(QPainter &painter)
{
painter.setPen(this->color);
painter.setFont(singletons::FontManager::getInstance().getFont(this->style, this->scale));
painter.drawText(QRectF(this->getRect().x(), this->getRect().y(), 10000, 10000), this->text,
QTextOption(Qt::AlignLeft | Qt::AlignTop));
}
void TextLayoutElement::paintAnimated(QPainter &painter)
{
}
int TextLayoutElement::getMouseOverIndex(const QPoint &abs)
{
return 0;
}
} // namespace layouts
} // namespace messages
} // namespace chatterino
@@ -0,0 +1,88 @@
#pragma once
#include <QPoint>
#include <QRect>
#include <QString>
#include <boost/noncopyable.hpp>
#include <climits>
#include "messages/messagecolor.hpp"
#include "singletons/fontmanager.hpp"
class QPainter;
namespace chatterino {
namespace messages {
class MessageElement;
class Image;
namespace layouts {
class MessageLayoutElement : boost::noncopyable
{
public:
MessageLayoutElement(MessageElement &creator, const QSize &size);
const QRect &getRect() const;
MessageElement &getCreator() const;
void setPosition(QPoint point);
bool hasTrailingSpace() const;
MessageLayoutElement *setTrailingSpace(bool value);
virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const = 0;
virtual int getSelectionIndexCount() = 0;
virtual void paint(QPainter &painter) = 0;
virtual void paintAnimated(QPainter &painter) = 0;
virtual int getMouseOverIndex(const QPoint &abs) = 0;
protected:
bool trailingSpace = true;
private:
QRect rect;
// bool isInNewLine;
MessageElement &creator;
};
// IMAGE
class ImageLayoutElement : public MessageLayoutElement
{
public:
ImageLayoutElement(MessageElement &creator, Image &image, QSize size);
protected:
virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override;
virtual int getSelectionIndexCount() override;
virtual void paint(QPainter &painter) override;
virtual void paintAnimated(QPainter &painter) override;
virtual int getMouseOverIndex(const QPoint &abs) override;
private:
Image &image;
};
// TEXT
class TextLayoutElement : public MessageLayoutElement
{
public:
TextLayoutElement(MessageElement &creator, QString &text, QSize size, QColor color,
FontStyle style, float scale);
protected:
virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override;
virtual int getSelectionIndexCount() override;
virtual void paint(QPainter &painter) override;
virtual void paintAnimated(QPainter &painter) override;
virtual int getMouseOverIndex(const QPoint &abs) override;
private:
QString text;
QColor color;
FontStyle style;
float scale;
};
} // namespace layouts
} // namespace messages
} // namespace chatterino